Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/net/ethernet/netronome/nfp/bpf/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 125 kB image not shown  

Quelle  jit.c   Sprache: C

 
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
/* Copyright (C) 2016-2018 Netronome Systems, Inc. */

#define pr_fmt(fmt) "NFP net bpf: " fmt

#include <linux/bug.h>
#include <linux/bpf.h>
#include <linux/filter.h>
#include <linux/kernel.h>
#include <linux/pkt_cls.h>
#include <linux/reciprocal_div.h>
#include <linux/unistd.h>

#include "main.h"
#include "../nfp_asm.h"
#include "../nfp_net_ctrl.h"

/* --- NFP prog --- */
/* Foreach "multiple" entries macros provide pos and next<n> pointers.
 * It's safe to modify the next pointers (but not pos).
 */

#define nfp_for_each_insn_walk2(nfp_prog, pos, next)   \
 for (pos = list_first_entry(&(nfp_prog)->insns, typeof(*pos), l), \
      next = list_next_entry(pos, l);   \
      &(nfp_prog)->insns != &pos->l &&   \
      &(nfp_prog)->insns != &next->l;   \
      pos = nfp_meta_next(pos),    \
      next = nfp_meta_next(pos))

#define nfp_for_each_insn_walk3(nfp_prog, pos, next, next2)  \
 for (pos = list_first_entry(&(nfp_prog)->insns, typeof(*pos), l), \
      next = list_next_entry(pos, l),   \
      next2 = list_next_entry(next, l);   \
      &(nfp_prog)->insns != &pos->l &&   \
      &(nfp_prog)->insns != &next->l &&   \
      &(nfp_prog)->insns != &next2->l;   \
      pos = nfp_meta_next(pos),    \
      next = nfp_meta_next(pos),    \
      next2 = nfp_meta_next(next))

static bool
nfp_meta_has_prev(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 return meta->l.prev != &nfp_prog->insns;
}

static void nfp_prog_push(struct nfp_prog *nfp_prog, u64 insn)
{
 if (nfp_prog->__prog_alloc_len / sizeof(u64) == nfp_prog->prog_len) {
  pr_warn("instruction limit reached (%u NFP instructions)\n",
   nfp_prog->prog_len);
  nfp_prog->error = -ENOSPC;
  return;
 }

 nfp_prog->prog[nfp_prog->prog_len] = insn;
 nfp_prog->prog_len++;
}

static unsigned int nfp_prog_current_offset(struct nfp_prog *nfp_prog)
{
 return nfp_prog->prog_len;
}

static bool
nfp_prog_confirm_current_offset(struct nfp_prog *nfp_prog, unsigned int off)
{
 /* If there is a recorded error we may have dropped instructions;
 * that doesn't have to be due to translator bug, and the translation
 * will fail anyway, so just return OK.
 */

 if (nfp_prog->error)
  return true;
 return !WARN_ON_ONCE(nfp_prog_current_offset(nfp_prog) != off);
}

/* --- Emitters --- */
static void
__emit_cmd(struct nfp_prog *nfp_prog, enum cmd_tgt_map op,
    u8 mode, u8 xfer, u8 areg, u8 breg, u8 size, enum cmd_ctx_swap ctx,
    bool indir)
{
 u64 insn;

 insn = FIELD_PREP(OP_CMD_A_SRC, areg) |
  FIELD_PREP(OP_CMD_CTX, ctx) |
  FIELD_PREP(OP_CMD_B_SRC, breg) |
  FIELD_PREP(OP_CMD_TOKEN, cmd_tgt_act[op].token) |
  FIELD_PREP(OP_CMD_XFER, xfer) |
  FIELD_PREP(OP_CMD_CNT, size) |
  FIELD_PREP(OP_CMD_SIG, ctx != CMD_CTX_NO_SWAP) |
  FIELD_PREP(OP_CMD_TGT_CMD, cmd_tgt_act[op].tgt_cmd) |
  FIELD_PREP(OP_CMD_INDIR, indir) |
  FIELD_PREP(OP_CMD_MODE, mode);

 nfp_prog_push(nfp_prog, insn);
}

static void
emit_cmd_any(struct nfp_prog *nfp_prog, enum cmd_tgt_map op, u8 mode, u8 xfer,
      swreg lreg, swreg rreg, u8 size, enum cmd_ctx_swap ctx, bool indir)
{
 struct nfp_insn_re_regs reg;
 int err;

 err = swreg_to_restricted(reg_none(), lreg, rreg, ®, false);
 if (err) {
  nfp_prog->error = err;
  return;
 }
 if (reg.swap) {
  pr_err("cmd can't swap arguments\n");
  nfp_prog->error = -EFAULT;
  return;
 }
 if (reg.dst_lmextn || reg.src_lmextn) {
  pr_err("cmd can't use LMextn\n");
  nfp_prog->error = -EFAULT;
  return;
 }

 __emit_cmd(nfp_prog, op, mode, xfer, reg.areg, reg.breg, size, ctx,
     indir);
}

static void
emit_cmd(struct nfp_prog *nfp_prog, enum cmd_tgt_map op, u8 mode, u8 xfer,
  swreg lreg, swreg rreg, u8 size, enum cmd_ctx_swap ctx)
{
 emit_cmd_any(nfp_prog, op, mode, xfer, lreg, rreg, size, ctx, false);
}

static void
emit_cmd_indir(struct nfp_prog *nfp_prog, enum cmd_tgt_map op, u8 mode, u8 xfer,
        swreg lreg, swreg rreg, u8 size, enum cmd_ctx_swap ctx)
{
 emit_cmd_any(nfp_prog, op, mode, xfer, lreg, rreg, size, ctx, true);
}

static void
__emit_br(struct nfp_prog *nfp_prog, enum br_mask mask, enum br_ev_pip ev_pip,
   enum br_ctx_signal_state css, u16 addr, u8 defer)
{
 u16 addr_lo, addr_hi;
 u64 insn;

 addr_lo = addr & (OP_BR_ADDR_LO >> __bf_shf(OP_BR_ADDR_LO));
 addr_hi = addr != addr_lo;

 insn = OP_BR_BASE |
  FIELD_PREP(OP_BR_MASK, mask) |
  FIELD_PREP(OP_BR_EV_PIP, ev_pip) |
  FIELD_PREP(OP_BR_CSS, css) |
  FIELD_PREP(OP_BR_DEFBR, defer) |
  FIELD_PREP(OP_BR_ADDR_LO, addr_lo) |
  FIELD_PREP(OP_BR_ADDR_HI, addr_hi);

 nfp_prog_push(nfp_prog, insn);
}

static void
emit_br_relo(struct nfp_prog *nfp_prog, enum br_mask mask, u16 addr, u8 defer,
      enum nfp_relo_type relo)
{
 if (mask == BR_UNC && defer > 2) {
  pr_err("BUG: branch defer out of bounds %d\n", defer);
  nfp_prog->error = -EFAULT;
  return;
 }

 __emit_br(nfp_prog, mask,
    mask != BR_UNC ? BR_EV_PIP_COND : BR_EV_PIP_UNCOND,
    BR_CSS_NONE, addr, defer);

 nfp_prog->prog[nfp_prog->prog_len - 1] |=
  FIELD_PREP(OP_RELO_TYPE, relo);
}

static void
emit_br(struct nfp_prog *nfp_prog, enum br_mask mask, u16 addr, u8 defer)
{
 emit_br_relo(nfp_prog, mask, addr, defer, RELO_BR_REL);
}

static void
__emit_br_bit(struct nfp_prog *nfp_prog, u16 areg, u16 breg, u16 addr, u8 defer,
       bool set, bool src_lmextn)
{
 u16 addr_lo, addr_hi;
 u64 insn;

 addr_lo = addr & (OP_BR_BIT_ADDR_LO >> __bf_shf(OP_BR_BIT_ADDR_LO));
 addr_hi = addr != addr_lo;

 insn = OP_BR_BIT_BASE |
  FIELD_PREP(OP_BR_BIT_A_SRC, areg) |
  FIELD_PREP(OP_BR_BIT_B_SRC, breg) |
  FIELD_PREP(OP_BR_BIT_BV, set) |
  FIELD_PREP(OP_BR_BIT_DEFBR, defer) |
  FIELD_PREP(OP_BR_BIT_ADDR_LO, addr_lo) |
  FIELD_PREP(OP_BR_BIT_ADDR_HI, addr_hi) |
  FIELD_PREP(OP_BR_BIT_SRC_LMEXTN, src_lmextn);

 nfp_prog_push(nfp_prog, insn);
}

static void
emit_br_bit_relo(struct nfp_prog *nfp_prog, swreg src, u8 bit, u16 addr,
   u8 defer, bool set, enum nfp_relo_type relo)
{
 struct nfp_insn_re_regs reg;
 int err;

 /* NOTE: The bit to test is specified as an rotation amount, such that
 *  the bit to test will be placed on the MSB of the result when
 *  doing a rotate right. For bit X, we need right rotate X + 1.
 */

 bit += 1;

 err = swreg_to_restricted(reg_none(), src, reg_imm(bit), ®, false);
 if (err) {
  nfp_prog->error = err;
  return;
 }

 __emit_br_bit(nfp_prog, reg.areg, reg.breg, addr, defer, set,
        reg.src_lmextn);

 nfp_prog->prog[nfp_prog->prog_len - 1] |=
  FIELD_PREP(OP_RELO_TYPE, relo);
}

static void
emit_br_bset(struct nfp_prog *nfp_prog, swreg src, u8 bit, u16 addr, u8 defer)
{
 emit_br_bit_relo(nfp_prog, src, bit, addr, defer, true, RELO_BR_REL);
}

static void
__emit_br_alu(struct nfp_prog *nfp_prog, u16 areg, u16 breg, u16 imm_hi,
       u8 defer, bool dst_lmextn, bool src_lmextn)
{
 u64 insn;

 insn = OP_BR_ALU_BASE |
  FIELD_PREP(OP_BR_ALU_A_SRC, areg) |
  FIELD_PREP(OP_BR_ALU_B_SRC, breg) |
  FIELD_PREP(OP_BR_ALU_DEFBR, defer) |
  FIELD_PREP(OP_BR_ALU_IMM_HI, imm_hi) |
  FIELD_PREP(OP_BR_ALU_SRC_LMEXTN, src_lmextn) |
  FIELD_PREP(OP_BR_ALU_DST_LMEXTN, dst_lmextn);

 nfp_prog_push(nfp_prog, insn);
}

static void emit_rtn(struct nfp_prog *nfp_prog, swreg base, u8 defer)
{
 struct nfp_insn_ur_regs reg;
 int err;

 err = swreg_to_unrestricted(reg_none(), base, reg_imm(0), ®);
 if (err) {
  nfp_prog->error = err;
  return;
 }

 __emit_br_alu(nfp_prog, reg.areg, reg.breg, 0, defer, reg.dst_lmextn,
        reg.src_lmextn);
}

static void
__emit_immed(struct nfp_prog *nfp_prog, u16 areg, u16 breg, u16 imm_hi,
      enum immed_width width, bool invert,
      enum immed_shift shift, bool wr_both,
      bool dst_lmextn, bool src_lmextn)
{
 u64 insn;

 insn = OP_IMMED_BASE |
  FIELD_PREP(OP_IMMED_A_SRC, areg) |
  FIELD_PREP(OP_IMMED_B_SRC, breg) |
  FIELD_PREP(OP_IMMED_IMM, imm_hi) |
  FIELD_PREP(OP_IMMED_WIDTH, width) |
  FIELD_PREP(OP_IMMED_INV, invert) |
  FIELD_PREP(OP_IMMED_SHIFT, shift) |
  FIELD_PREP(OP_IMMED_WR_AB, wr_both) |
  FIELD_PREP(OP_IMMED_SRC_LMEXTN, src_lmextn) |
  FIELD_PREP(OP_IMMED_DST_LMEXTN, dst_lmextn);

 nfp_prog_push(nfp_prog, insn);
}

static void
emit_immed(struct nfp_prog *nfp_prog, swreg dst, u16 imm,
    enum immed_width width, bool invert, enum immed_shift shift)
{
 struct nfp_insn_ur_regs reg;
 int err;

 if (swreg_type(dst) == NN_REG_IMM) {
  nfp_prog->error = -EFAULT;
  return;
 }

 err = swreg_to_unrestricted(dst, dst, reg_imm(imm & 0xff), ®);
 if (err) {
  nfp_prog->error = err;
  return;
 }

 /* Use reg.dst when destination is No-Dest. */
 __emit_immed(nfp_prog,
       swreg_type(dst) == NN_REG_NONE ? reg.dst : reg.areg,
       reg.breg, imm >> 8, width, invert, shift,
       reg.wr_both, reg.dst_lmextn, reg.src_lmextn);
}

static void
__emit_shf(struct nfp_prog *nfp_prog, u16 dst, enum alu_dst_ab dst_ab,
    enum shf_sc sc, u8 shift,
    u16 areg, enum shf_op op, u16 breg, bool i8, bool sw, bool wr_both,
    bool dst_lmextn, bool src_lmextn)
{
 u64 insn;

 if (!FIELD_FIT(OP_SHF_SHIFT, shift)) {
  nfp_prog->error = -EFAULT;
  return;
 }

 /* NFP shift instruction has something special. If shift direction is
 * left then shift amount of 1 to 31 is specified as 32 minus the amount
 * to shift.
 *
 * But no need to do this for indirect shift which has shift amount be
 * 0. Even after we do this subtraction, shift amount 0 will be turned
 * into 32 which will eventually be encoded the same as 0 because only
 * low 5 bits are encoded, but shift amount be 32 will fail the
 * FIELD_PREP check done later on shift mask (0x1f), due to 32 is out of
 * mask range.
 */

 if (sc == SHF_SC_L_SHF && shift)
  shift = 32 - shift;

 insn = OP_SHF_BASE |
  FIELD_PREP(OP_SHF_A_SRC, areg) |
  FIELD_PREP(OP_SHF_SC, sc) |
  FIELD_PREP(OP_SHF_B_SRC, breg) |
  FIELD_PREP(OP_SHF_I8, i8) |
  FIELD_PREP(OP_SHF_SW, sw) |
  FIELD_PREP(OP_SHF_DST, dst) |
  FIELD_PREP(OP_SHF_SHIFT, shift) |
  FIELD_PREP(OP_SHF_OP, op) |
  FIELD_PREP(OP_SHF_DST_AB, dst_ab) |
  FIELD_PREP(OP_SHF_WR_AB, wr_both) |
  FIELD_PREP(OP_SHF_SRC_LMEXTN, src_lmextn) |
  FIELD_PREP(OP_SHF_DST_LMEXTN, dst_lmextn);

 nfp_prog_push(nfp_prog, insn);
}

static void
emit_shf(struct nfp_prog *nfp_prog, swreg dst,
  swreg lreg, enum shf_op op, swreg rreg, enum shf_sc sc, u8 shift)
{
 struct nfp_insn_re_regs reg;
 int err;

 err = swreg_to_restricted(dst, lreg, rreg, ®, true);
 if (err) {
  nfp_prog->error = err;
  return;
 }

 __emit_shf(nfp_prog, reg.dst, reg.dst_ab, sc, shift,
     reg.areg, op, reg.breg, reg.i8, reg.swap, reg.wr_both,
     reg.dst_lmextn, reg.src_lmextn);
}

static void
emit_shf_indir(struct nfp_prog *nfp_prog, swreg dst,
        swreg lreg, enum shf_op op, swreg rreg, enum shf_sc sc)
{
 if (sc == SHF_SC_R_ROT) {
  pr_err("indirect shift is not allowed on rotation\n");
  nfp_prog->error = -EFAULT;
  return;
 }

 emit_shf(nfp_prog, dst, lreg, op, rreg, sc, 0);
}

static void
__emit_alu(struct nfp_prog *nfp_prog, u16 dst, enum alu_dst_ab dst_ab,
    u16 areg, enum alu_op op, u16 breg, bool swap, bool wr_both,
    bool dst_lmextn, bool src_lmextn)
{
 u64 insn;

 insn = OP_ALU_BASE |
  FIELD_PREP(OP_ALU_A_SRC, areg) |
  FIELD_PREP(OP_ALU_B_SRC, breg) |
  FIELD_PREP(OP_ALU_DST, dst) |
  FIELD_PREP(OP_ALU_SW, swap) |
  FIELD_PREP(OP_ALU_OP, op) |
  FIELD_PREP(OP_ALU_DST_AB, dst_ab) |
  FIELD_PREP(OP_ALU_WR_AB, wr_both) |
  FIELD_PREP(OP_ALU_SRC_LMEXTN, src_lmextn) |
  FIELD_PREP(OP_ALU_DST_LMEXTN, dst_lmextn);

 nfp_prog_push(nfp_prog, insn);
}

static void
emit_alu(struct nfp_prog *nfp_prog, swreg dst,
  swreg lreg, enum alu_op op, swreg rreg)
{
 struct nfp_insn_ur_regs reg;
 int err;

 err = swreg_to_unrestricted(dst, lreg, rreg, ®);
 if (err) {
  nfp_prog->error = err;
  return;
 }

 __emit_alu(nfp_prog, reg.dst, reg.dst_ab,
     reg.areg, op, reg.breg, reg.swap, reg.wr_both,
     reg.dst_lmextn, reg.src_lmextn);
}

static void
__emit_mul(struct nfp_prog *nfp_prog, enum alu_dst_ab dst_ab, u16 areg,
    enum mul_type type, enum mul_step step, u16 breg, bool swap,
    bool wr_both, bool dst_lmextn, bool src_lmextn)
{
 u64 insn;

 insn = OP_MUL_BASE |
  FIELD_PREP(OP_MUL_A_SRC, areg) |
  FIELD_PREP(OP_MUL_B_SRC, breg) |
  FIELD_PREP(OP_MUL_STEP, step) |
  FIELD_PREP(OP_MUL_DST_AB, dst_ab) |
  FIELD_PREP(OP_MUL_SW, swap) |
  FIELD_PREP(OP_MUL_TYPE, type) |
  FIELD_PREP(OP_MUL_WR_AB, wr_both) |
  FIELD_PREP(OP_MUL_SRC_LMEXTN, src_lmextn) |
  FIELD_PREP(OP_MUL_DST_LMEXTN, dst_lmextn);

 nfp_prog_push(nfp_prog, insn);
}

static void
emit_mul(struct nfp_prog *nfp_prog, swreg lreg, enum mul_type type,
  enum mul_step step, swreg rreg)
{
 struct nfp_insn_ur_regs reg;
 u16 areg;
 int err;

 if (type == MUL_TYPE_START && step != MUL_STEP_NONE) {
  nfp_prog->error = -EINVAL;
  return;
 }

 if (step == MUL_LAST || step == MUL_LAST_2) {
  /* When type is step and step Number is LAST or LAST2, left
 * source is used as destination.
 */

  err = swreg_to_unrestricted(lreg, reg_none(), rreg, ®);
  areg = reg.dst;
 } else {
  err = swreg_to_unrestricted(reg_none(), lreg, rreg, ®);
  areg = reg.areg;
 }

 if (err) {
  nfp_prog->error = err;
  return;
 }

 __emit_mul(nfp_prog, reg.dst_ab, areg, type, step, reg.breg, reg.swap,
     reg.wr_both, reg.dst_lmextn, reg.src_lmextn);
}

static void
__emit_ld_field(struct nfp_prog *nfp_prog, enum shf_sc sc,
  u8 areg, u8 bmask, u8 breg, u8 shift, bool imm8,
  bool zero, bool swap, bool wr_both,
  bool dst_lmextn, bool src_lmextn)
{
 u64 insn;

 insn = OP_LDF_BASE |
  FIELD_PREP(OP_LDF_A_SRC, areg) |
  FIELD_PREP(OP_LDF_SC, sc) |
  FIELD_PREP(OP_LDF_B_SRC, breg) |
  FIELD_PREP(OP_LDF_I8, imm8) |
  FIELD_PREP(OP_LDF_SW, swap) |
  FIELD_PREP(OP_LDF_ZF, zero) |
  FIELD_PREP(OP_LDF_BMASK, bmask) |
  FIELD_PREP(OP_LDF_SHF, shift) |
  FIELD_PREP(OP_LDF_WR_AB, wr_both) |
  FIELD_PREP(OP_LDF_SRC_LMEXTN, src_lmextn) |
  FIELD_PREP(OP_LDF_DST_LMEXTN, dst_lmextn);

 nfp_prog_push(nfp_prog, insn);
}

static void
emit_ld_field_any(struct nfp_prog *nfp_prog, swreg dst, u8 bmask, swreg src,
    enum shf_sc sc, u8 shift, bool zero)
{
 struct nfp_insn_re_regs reg;
 int err;

 /* Note: ld_field is special as it uses one of the src regs as dst */
 err = swreg_to_restricted(dst, dst, src, ®, true);
 if (err) {
  nfp_prog->error = err;
  return;
 }

 __emit_ld_field(nfp_prog, sc, reg.areg, bmask, reg.breg, shift,
   reg.i8, zero, reg.swap, reg.wr_both,
   reg.dst_lmextn, reg.src_lmextn);
}

static void
emit_ld_field(struct nfp_prog *nfp_prog, swreg dst, u8 bmask, swreg src,
       enum shf_sc sc, u8 shift)
{
 emit_ld_field_any(nfp_prog, dst, bmask, src, sc, shift, false);
}

static void
__emit_lcsr(struct nfp_prog *nfp_prog, u16 areg, u16 breg, bool wr, u16 addr,
     bool dst_lmextn, bool src_lmextn)
{
 u64 insn;

 insn = OP_LCSR_BASE |
  FIELD_PREP(OP_LCSR_A_SRC, areg) |
  FIELD_PREP(OP_LCSR_B_SRC, breg) |
  FIELD_PREP(OP_LCSR_WRITE, wr) |
  FIELD_PREP(OP_LCSR_ADDR, addr / 4) |
  FIELD_PREP(OP_LCSR_SRC_LMEXTN, src_lmextn) |
  FIELD_PREP(OP_LCSR_DST_LMEXTN, dst_lmextn);

 nfp_prog_push(nfp_prog, insn);
}

static void emit_csr_wr(struct nfp_prog *nfp_prog, swreg src, u16 addr)
{
 struct nfp_insn_ur_regs reg;
 int err;

 /* This instruction takes immeds instead of reg_none() for the ignored
 * operand, but we can't encode 2 immeds in one instr with our normal
 * swreg infra so if param is an immed, we encode as reg_none() and
 * copy the immed to both operands.
 */

 if (swreg_type(src) == NN_REG_IMM) {
  err = swreg_to_unrestricted(reg_none(), src, reg_none(), ®);
  reg.breg = reg.areg;
 } else {
  err = swreg_to_unrestricted(reg_none(), src, reg_imm(0), ®);
 }
 if (err) {
  nfp_prog->error = err;
  return;
 }

 __emit_lcsr(nfp_prog, reg.areg, reg.breg, true, addr,
      false, reg.src_lmextn);
}

/* CSR value is read in following immed[gpr, 0] */
static void __emit_csr_rd(struct nfp_prog *nfp_prog, u16 addr)
{
 __emit_lcsr(nfp_prog, 0, 0, false, addr, falsefalse);
}

static void emit_nop(struct nfp_prog *nfp_prog)
{
 __emit_immed(nfp_prog, UR_REG_IMM, UR_REG_IMM, 0, 0, 0, 0, 0, 0, 0);
}

/* --- Wrappers --- */
static bool pack_immed(u32 imm, u16 *val, enum immed_shift *shift)
{
 if (!(imm & 0xffff0000)) {
  *val = imm;
  *shift = IMMED_SHIFT_0B;
 } else if (!(imm & 0xff0000ff)) {
  *val = imm >> 8;
  *shift = IMMED_SHIFT_1B;
 } else if (!(imm & 0x0000ffff)) {
  *val = imm >> 16;
  *shift = IMMED_SHIFT_2B;
 } else {
  return false;
 }

 return true;
}

static void wrp_immed(struct nfp_prog *nfp_prog, swreg dst, u32 imm)
{
 enum immed_shift shift;
 u16 val;

 if (pack_immed(imm, &val, &shift)) {
  emit_immed(nfp_prog, dst, val, IMMED_WIDTH_ALL, false, shift);
 } else if (pack_immed(~imm, &val, &shift)) {
  emit_immed(nfp_prog, dst, val, IMMED_WIDTH_ALL, true, shift);
 } else {
  emit_immed(nfp_prog, dst, imm & 0xffff, IMMED_WIDTH_ALL,
      false, IMMED_SHIFT_0B);
  emit_immed(nfp_prog, dst, imm >> 16, IMMED_WIDTH_WORD,
      false, IMMED_SHIFT_2B);
 }
}

static void
wrp_zext(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta, u8 dst)
{
 if (meta->flags & FLAG_INSN_DO_ZEXT)
  wrp_immed(nfp_prog, reg_both(dst + 1), 0);
}

static void
wrp_immed_relo(struct nfp_prog *nfp_prog, swreg dst, u32 imm,
        enum nfp_relo_type relo)
{
 if (imm > 0xffff) {
  pr_err("relocation of a large immediate!\n");
  nfp_prog->error = -EFAULT;
  return;
 }
 emit_immed(nfp_prog, dst, imm, IMMED_WIDTH_ALL, false, IMMED_SHIFT_0B);

 nfp_prog->prog[nfp_prog->prog_len - 1] |=
  FIELD_PREP(OP_RELO_TYPE, relo);
}

/* ur_load_imm_any() - encode immediate or use tmp register (unrestricted)
 * If the @imm is small enough encode it directly in operand and return
 * otherwise load @imm to a spare register and return its encoding.
 */

static swreg ur_load_imm_any(struct nfp_prog *nfp_prog, u32 imm, swreg tmp_reg)
{
 if (FIELD_FIT(UR_REG_IMM_MAX, imm))
  return reg_imm(imm);

 wrp_immed(nfp_prog, tmp_reg, imm);
 return tmp_reg;
}

/* re_load_imm_any() - encode immediate or use tmp register (restricted)
 * If the @imm is small enough encode it directly in operand and return
 * otherwise load @imm to a spare register and return its encoding.
 */

static swreg re_load_imm_any(struct nfp_prog *nfp_prog, u32 imm, swreg tmp_reg)
{
 if (FIELD_FIT(RE_REG_IMM_MAX, imm))
  return reg_imm(imm);

 wrp_immed(nfp_prog, tmp_reg, imm);
 return tmp_reg;
}

static void wrp_nops(struct nfp_prog *nfp_prog, unsigned int count)
{
 while (count--)
  emit_nop(nfp_prog);
}

static void wrp_mov(struct nfp_prog *nfp_prog, swreg dst, swreg src)
{
 emit_alu(nfp_prog, dst, reg_none(), ALU_OP_NONE, src);
}

static void wrp_reg_mov(struct nfp_prog *nfp_prog, u16 dst, u16 src)
{
 wrp_mov(nfp_prog, reg_both(dst), reg_b(src));
}

/* wrp_reg_subpart() - load @field_len bytes from @offset of @src, write the
 * result to @dst from low end.
 */

static void
wrp_reg_subpart(struct nfp_prog *nfp_prog, swreg dst, swreg src, u8 field_len,
  u8 offset)
{
 enum shf_sc sc = offset ? SHF_SC_R_SHF : SHF_SC_NONE;
 u8 mask = (1 << field_len) - 1;

 emit_ld_field_any(nfp_prog, dst, mask, src, sc, offset * 8, true);
}

/* wrp_reg_or_subpart() - load @field_len bytes from low end of @src, or the
 * result to @dst from offset, there is no change on the other bits of @dst.
 */

static void
wrp_reg_or_subpart(struct nfp_prog *nfp_prog, swreg dst, swreg src,
     u8 field_len, u8 offset)
{
 enum shf_sc sc = offset ? SHF_SC_L_SHF : SHF_SC_NONE;
 u8 mask = ((1 << field_len) - 1) << offset;

 emit_ld_field(nfp_prog, dst, mask, src, sc, 32 - offset * 8);
}

static void
addr40_offset(struct nfp_prog *nfp_prog, u8 src_gpr, swreg offset,
       swreg *rega, swreg *regb)
{
 if (offset == reg_imm(0)) {
  *rega = reg_a(src_gpr);
  *regb = reg_b(src_gpr + 1);
  return;
 }

 emit_alu(nfp_prog, imm_a(nfp_prog), reg_a(src_gpr), ALU_OP_ADD, offset);
 emit_alu(nfp_prog, imm_b(nfp_prog), reg_b(src_gpr + 1), ALU_OP_ADD_C,
   reg_imm(0));
 *rega = imm_a(nfp_prog);
 *regb = imm_b(nfp_prog);
}

/* NFP has Command Push Pull bus which supports bluk memory operations. */
static int nfp_cpp_memcpy(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 bool descending_seq = meta->ldst_gather_len < 0;
 s16 len = abs(meta->ldst_gather_len);
 swreg src_base, off;
 bool src_40bit_addr;
 unsigned int i;
 u8 xfer_num;

 off = re_load_imm_any(nfp_prog, meta->insn.off, imm_b(nfp_prog));
 src_40bit_addr = meta->ptr.type == PTR_TO_MAP_VALUE;
 src_base = reg_a(meta->insn.src_reg * 2);
 xfer_num = round_up(len, 4) / 4;

 if (src_40bit_addr)
  addr40_offset(nfp_prog, meta->insn.src_reg * 2, off, &src_base,
         &off);

 /* Setup PREV_ALU fields to override memory read length. */
 if (len > 32)
  wrp_immed(nfp_prog, reg_none(),
     CMD_OVE_LEN | FIELD_PREP(CMD_OV_LEN, xfer_num - 1));

 /* Memory read from source addr into transfer-in registers. */
 emit_cmd_any(nfp_prog, CMD_TGT_READ32_SWAP,
       src_40bit_addr ? CMD_MODE_40b_BA : CMD_MODE_32b, 0,
       src_base, off, xfer_num - 1, CMD_CTX_SWAP, len > 32);

 /* Move from transfer-in to transfer-out. */
 for (i = 0; i < xfer_num; i++)
  wrp_mov(nfp_prog, reg_xfer(i), reg_xfer(i));

 off = re_load_imm_any(nfp_prog, meta->paired_st->off, imm_b(nfp_prog));

 if (len <= 8) {
  /* Use single direct_ref write8. */
  emit_cmd(nfp_prog, CMD_TGT_WRITE8_SWAP, CMD_MODE_32b, 0,
    reg_a(meta->paired_st->dst_reg * 2), off, len - 1,
    CMD_CTX_SWAP);
 } else if (len <= 32 && IS_ALIGNED(len, 4)) {
  /* Use single direct_ref write32. */
  emit_cmd(nfp_prog, CMD_TGT_WRITE32_SWAP, CMD_MODE_32b, 0,
    reg_a(meta->paired_st->dst_reg * 2), off, xfer_num - 1,
    CMD_CTX_SWAP);
 } else if (len <= 32) {
  /* Use single indirect_ref write8. */
  wrp_immed(nfp_prog, reg_none(),
     CMD_OVE_LEN | FIELD_PREP(CMD_OV_LEN, len - 1));
  emit_cmd_indir(nfp_prog, CMD_TGT_WRITE8_SWAP, CMD_MODE_32b, 0,
          reg_a(meta->paired_st->dst_reg * 2), off,
          len - 1, CMD_CTX_SWAP);
 } else if (IS_ALIGNED(len, 4)) {
  /* Use single indirect_ref write32. */
  wrp_immed(nfp_prog, reg_none(),
     CMD_OVE_LEN | FIELD_PREP(CMD_OV_LEN, xfer_num - 1));
  emit_cmd_indir(nfp_prog, CMD_TGT_WRITE32_SWAP, CMD_MODE_32b, 0,
          reg_a(meta->paired_st->dst_reg * 2), off,
          xfer_num - 1, CMD_CTX_SWAP);
 } else if (len <= 40) {
  /* Use one direct_ref write32 to write the first 32-bytes, then
 * another direct_ref write8 to write the remaining bytes.
 */

  emit_cmd(nfp_prog, CMD_TGT_WRITE32_SWAP, CMD_MODE_32b, 0,
    reg_a(meta->paired_st->dst_reg * 2), off, 7,
    CMD_CTX_SWAP);

  off = re_load_imm_any(nfp_prog, meta->paired_st->off + 32,
          imm_b(nfp_prog));
  emit_cmd(nfp_prog, CMD_TGT_WRITE8_SWAP, CMD_MODE_32b, 8,
    reg_a(meta->paired_st->dst_reg * 2), off, len - 33,
    CMD_CTX_SWAP);
 } else {
  /* Use one indirect_ref write32 to write 4-bytes aligned length,
 * then another direct_ref write8 to write the remaining bytes.
 */

  u8 new_off;

  wrp_immed(nfp_prog, reg_none(),
     CMD_OVE_LEN | FIELD_PREP(CMD_OV_LEN, xfer_num - 2));
  emit_cmd_indir(nfp_prog, CMD_TGT_WRITE32_SWAP, CMD_MODE_32b, 0,
          reg_a(meta->paired_st->dst_reg * 2), off,
          xfer_num - 2, CMD_CTX_SWAP);
  new_off = meta->paired_st->off + (xfer_num - 1) * 4;
  off = re_load_imm_any(nfp_prog, new_off, imm_b(nfp_prog));
  emit_cmd(nfp_prog, CMD_TGT_WRITE8_SWAP, CMD_MODE_32b,
    xfer_num - 1, reg_a(meta->paired_st->dst_reg * 2), off,
    (len & 0x3) - 1, CMD_CTX_SWAP);
 }

 /* TODO: The following extra load is to make sure data flow be identical
 *  before and after we do memory copy optimization.
 *
 *  The load destination register is not guaranteed to be dead, so we
 *  need to make sure it is loaded with the value the same as before
 *  this transformation.
 *
 *  These extra loads could be removed once we have accurate register
 *  usage information.
 */

 if (descending_seq)
  xfer_num = 0;
 else if (BPF_SIZE(meta->insn.code) != BPF_DW)
  xfer_num = xfer_num - 1;
 else
  xfer_num = xfer_num - 2;

 switch (BPF_SIZE(meta->insn.code)) {
 case BPF_B:
  wrp_reg_subpart(nfp_prog, reg_both(meta->insn.dst_reg * 2),
    reg_xfer(xfer_num), 1,
    IS_ALIGNED(len, 4) ? 3 : (len & 3) - 1);
  break;
 case BPF_H:
  wrp_reg_subpart(nfp_prog, reg_both(meta->insn.dst_reg * 2),
    reg_xfer(xfer_num), 2, (len & 3) ^ 2);
  break;
 case BPF_W:
  wrp_mov(nfp_prog, reg_both(meta->insn.dst_reg * 2),
   reg_xfer(0));
  break;
 case BPF_DW:
  wrp_mov(nfp_prog, reg_both(meta->insn.dst_reg * 2),
   reg_xfer(xfer_num));
  wrp_mov(nfp_prog, reg_both(meta->insn.dst_reg * 2 + 1),
   reg_xfer(xfer_num + 1));
  break;
 }

 if (BPF_SIZE(meta->insn.code) != BPF_DW)
  wrp_immed(nfp_prog, reg_both(meta->insn.dst_reg * 2 + 1), 0);

 return 0;
}

static int
data_ld(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta, swreg offset,
 u8 dst_gpr, int size)
{
 unsigned int i;
 u16 shift, sz;

 /* We load the value from the address indicated in @offset and then
 * shift out the data we don't need.  Note: this is big endian!
 */

 sz = max(size, 4);
 shift = size < 4 ? 4 - size : 0;

 emit_cmd(nfp_prog, CMD_TGT_READ8, CMD_MODE_32b, 0,
   pptr_reg(nfp_prog), offset, sz - 1, CMD_CTX_SWAP);

 i = 0;
 if (shift)
  emit_shf(nfp_prog, reg_both(dst_gpr), reg_none(), SHF_OP_NONE,
    reg_xfer(0), SHF_SC_R_SHF, shift * 8);
 else
  for (; i * 4 < size; i++)
   wrp_mov(nfp_prog, reg_both(dst_gpr + i), reg_xfer(i));

 if (i < 2)
  wrp_zext(nfp_prog, meta, dst_gpr);

 return 0;
}

static int
data_ld_host_order(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta,
     u8 dst_gpr, swreg lreg, swreg rreg, int size,
     enum cmd_mode mode)
{
 unsigned int i;
 u8 mask, sz;

 /* We load the value from the address indicated in rreg + lreg and then
 * mask out the data we don't need.  Note: this is little endian!
 */

 sz = max(size, 4);
 mask = size < 4 ? GENMASK(size - 1, 0) : 0;

 emit_cmd(nfp_prog, CMD_TGT_READ32_SWAP, mode, 0,
   lreg, rreg, sz / 4 - 1, CMD_CTX_SWAP);

 i = 0;
 if (mask)
  emit_ld_field_any(nfp_prog, reg_both(dst_gpr), mask,
      reg_xfer(0), SHF_SC_NONE, 0, true);
 else
  for (; i * 4 < size; i++)
   wrp_mov(nfp_prog, reg_both(dst_gpr + i), reg_xfer(i));

 if (i < 2)
  wrp_zext(nfp_prog, meta, dst_gpr);

 return 0;
}

static int
data_ld_host_order_addr32(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta,
     u8 src_gpr, swreg offset, u8 dst_gpr, u8 size)
{
 return data_ld_host_order(nfp_prog, meta, dst_gpr, reg_a(src_gpr),
      offset, size, CMD_MODE_32b);
}

static int
data_ld_host_order_addr40(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta,
     u8 src_gpr, swreg offset, u8 dst_gpr, u8 size)
{
 swreg rega, regb;

 addr40_offset(nfp_prog, src_gpr, offset, ®a, ®b);

 return data_ld_host_order(nfp_prog, meta, dst_gpr, rega, regb,
      size, CMD_MODE_40b_BA);
}

static int
construct_data_ind_ld(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta,
        u16 offset, u16 src, u8 size)
{
 swreg tmp_reg;

 /* Calculate the true offset (src_reg + imm) */
 tmp_reg = ur_load_imm_any(nfp_prog, offset, imm_b(nfp_prog));
 emit_alu(nfp_prog, imm_both(nfp_prog), reg_a(src), ALU_OP_ADD, tmp_reg);

 /* Check packet length (size guaranteed to fit b/c it's u8) */
 emit_alu(nfp_prog, imm_a(nfp_prog),
   imm_a(nfp_prog), ALU_OP_ADD, reg_imm(size));
 emit_alu(nfp_prog, reg_none(),
   plen_reg(nfp_prog), ALU_OP_SUB, imm_a(nfp_prog));
 emit_br_relo(nfp_prog, BR_BLO, BR_OFF_RELO, 0, RELO_BR_GO_ABORT);

 /* Load data */
 return data_ld(nfp_prog, meta, imm_b(nfp_prog), 0, size);
}

static int
construct_data_ld(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta,
    u16 offset, u8 size)
{
 swreg tmp_reg;

 /* Check packet length */
 tmp_reg = ur_load_imm_any(nfp_prog, offset + size, imm_a(nfp_prog));
 emit_alu(nfp_prog, reg_none(), plen_reg(nfp_prog), ALU_OP_SUB, tmp_reg);
 emit_br_relo(nfp_prog, BR_BLO, BR_OFF_RELO, 0, RELO_BR_GO_ABORT);

 /* Load data */
 tmp_reg = re_load_imm_any(nfp_prog, offset, imm_b(nfp_prog));
 return data_ld(nfp_prog, meta, tmp_reg, 0, size);
}

static int
data_stx_host_order(struct nfp_prog *nfp_prog, u8 dst_gpr, swreg offset,
      u8 src_gpr, u8 size)
{
 unsigned int i;

 for (i = 0; i * 4 < size; i++)
  wrp_mov(nfp_prog, reg_xfer(i), reg_a(src_gpr + i));

 emit_cmd(nfp_prog, CMD_TGT_WRITE8_SWAP, CMD_MODE_32b, 0,
   reg_a(dst_gpr), offset, size - 1, CMD_CTX_SWAP);

 return 0;
}

static int
data_st_host_order(struct nfp_prog *nfp_prog, u8 dst_gpr, swreg offset,
     u64 imm, u8 size)
{
 wrp_immed(nfp_prog, reg_xfer(0), imm);
 if (size == 8)
  wrp_immed(nfp_prog, reg_xfer(1), imm >> 32);

 emit_cmd(nfp_prog, CMD_TGT_WRITE8_SWAP, CMD_MODE_32b, 0,
   reg_a(dst_gpr), offset, size - 1, CMD_CTX_SWAP);

 return 0;
}

typedef int
(*lmem_step)(struct nfp_prog *nfp_prog, u8 gpr, u8 gpr_byte, s32 off,
      unsigned int size, bool first, bool new_gpr, bool last, bool lm3,
      bool needs_inc);

static int
wrp_lmem_load(struct nfp_prog *nfp_prog, u8 dst, u8 dst_byte, s32 off,
       unsigned int size, bool first, bool new_gpr, bool last, bool lm3,
       bool needs_inc)
{
 bool should_inc = needs_inc && new_gpr && !last;
 u32 idx, src_byte;
 enum shf_sc sc;
 swreg reg;
 int shf;
 u8 mask;

 if (WARN_ON_ONCE(dst_byte + size > 4 || off % 4 + size > 4))
  return -EOPNOTSUPP;

 idx = off / 4;

 /* Move the entire word */
 if (size == 4) {
  wrp_mov(nfp_prog, reg_both(dst),
   should_inc ? reg_lm_inc(3) : reg_lm(lm3 ? 3 : 0, idx));
  return 0;
 }

 if (WARN_ON_ONCE(lm3 && idx > RE_REG_LM_IDX_MAX))
  return -EOPNOTSUPP;

 src_byte = off % 4;

 mask = (1 << size) - 1;
 mask <<= dst_byte;

 if (WARN_ON_ONCE(mask > 0xf))
  return -EOPNOTSUPP;

 shf = abs(src_byte - dst_byte) * 8;
 if (src_byte == dst_byte) {
  sc = SHF_SC_NONE;
 } else if (src_byte < dst_byte) {
  shf = 32 - shf;
  sc = SHF_SC_L_SHF;
 } else {
  sc = SHF_SC_R_SHF;
 }

 /* ld_field can address fewer indexes, if offset too large do RMW.
 * Because we RMV twice we waste 2 cycles on unaligned 8 byte writes.
 */

 if (idx <= RE_REG_LM_IDX_MAX) {
  reg = reg_lm(lm3 ? 3 : 0, idx);
 } else {
  reg = imm_a(nfp_prog);
  /* If it's not the first part of the load and we start a new GPR
 * that means we are loading a second part of the LMEM word into
 * a new GPR.  IOW we've already looked that LMEM word and
 * therefore it has been loaded into imm_a().
 */

  if (first || !new_gpr)
   wrp_mov(nfp_prog, reg, reg_lm(0, idx));
 }

 emit_ld_field_any(nfp_prog, reg_both(dst), mask, reg, sc, shf, new_gpr);

 if (should_inc)
  wrp_mov(nfp_prog, reg_none(), reg_lm_inc(3));

 return 0;
}

static int
wrp_lmem_store(struct nfp_prog *nfp_prog, u8 src, u8 src_byte, s32 off,
        unsigned int size, bool first, bool new_gpr, bool last, bool lm3,
        bool needs_inc)
{
 bool should_inc = needs_inc && new_gpr && !last;
 u32 idx, dst_byte;
 enum shf_sc sc;
 swreg reg;
 int shf;
 u8 mask;

 if (WARN_ON_ONCE(src_byte + size > 4 || off % 4 + size > 4))
  return -EOPNOTSUPP;

 idx = off / 4;

 /* Move the entire word */
 if (size == 4) {
  wrp_mov(nfp_prog,
   should_inc ? reg_lm_inc(3) : reg_lm(lm3 ? 3 : 0, idx),
   reg_b(src));
  return 0;
 }

 if (WARN_ON_ONCE(lm3 && idx > RE_REG_LM_IDX_MAX))
  return -EOPNOTSUPP;

 dst_byte = off % 4;

 mask = (1 << size) - 1;
 mask <<= dst_byte;

 if (WARN_ON_ONCE(mask > 0xf))
  return -EOPNOTSUPP;

 shf = abs(src_byte - dst_byte) * 8;
 if (src_byte == dst_byte) {
  sc = SHF_SC_NONE;
 } else if (src_byte < dst_byte) {
  shf = 32 - shf;
  sc = SHF_SC_L_SHF;
 } else {
  sc = SHF_SC_R_SHF;
 }

 /* ld_field can address fewer indexes, if offset too large do RMW.
 * Because we RMV twice we waste 2 cycles on unaligned 8 byte writes.
 */

 if (idx <= RE_REG_LM_IDX_MAX) {
  reg = reg_lm(lm3 ? 3 : 0, idx);
 } else {
  reg = imm_a(nfp_prog);
  /* Only first and last LMEM locations are going to need RMW,
 * the middle location will be overwritten fully.
 */

  if (first || last)
   wrp_mov(nfp_prog, reg, reg_lm(0, idx));
 }

 emit_ld_field(nfp_prog, reg, mask, reg_b(src), sc, shf);

 if (new_gpr || last) {
  if (idx > RE_REG_LM_IDX_MAX)
   wrp_mov(nfp_prog, reg_lm(0, idx), reg);
  if (should_inc)
   wrp_mov(nfp_prog, reg_none(), reg_lm_inc(3));
 }

 return 0;
}

static int
mem_op_stack(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta,
      unsigned int size, unsigned int ptr_off, u8 gpr, u8 ptr_gpr,
      bool clr_gpr, lmem_step step)
{
 s32 off = nfp_prog->stack_frame_depth + meta->insn.off + ptr_off;
 bool first = true, narrow_ld, last;
 bool needs_inc = false;
 swreg stack_off_reg;
 u8 prev_gpr = 255;
 u32 gpr_byte = 0;
 bool lm3 = true;
 int ret;

 if (meta->ptr_not_const ||
     meta->flags & FLAG_INSN_PTR_CALLER_STACK_FRAME) {
  /* Use of the last encountered ptr_off is OK, they all have
 * the same alignment.  Depend on low bits of value being
 * discarded when written to LMaddr register.
 */

  stack_off_reg = ur_load_imm_any(nfp_prog, meta->insn.off,
      stack_imm(nfp_prog));

  emit_alu(nfp_prog, imm_b(nfp_prog),
    reg_a(ptr_gpr), ALU_OP_ADD, stack_off_reg);

  needs_inc = true;
 } else if (off + size <= 64) {
  /* We can reach bottom 64B with LMaddr0 */
  lm3 = false;
 } else if (round_down(off, 32) == round_down(off + size - 1, 32)) {
  /* We have to set up a new pointer.  If we know the offset
 * and the entire access falls into a single 32 byte aligned
 * window we won't have to increment the LM pointer.
 * The 32 byte alignment is imporant because offset is ORed in
 * not added when doing *l$indexN[off].
 */

  stack_off_reg = ur_load_imm_any(nfp_prog, round_down(off, 32),
      stack_imm(nfp_prog));
  emit_alu(nfp_prog, imm_b(nfp_prog),
    stack_reg(nfp_prog), ALU_OP_ADD, stack_off_reg);

  off %= 32;
 } else {
  stack_off_reg = ur_load_imm_any(nfp_prog, round_down(off, 4),
      stack_imm(nfp_prog));

  emit_alu(nfp_prog, imm_b(nfp_prog),
    stack_reg(nfp_prog), ALU_OP_ADD, stack_off_reg);

  needs_inc = true;
 }

 narrow_ld = clr_gpr && size < 8;

 if (lm3) {
  unsigned int nop_cnt;

  emit_csr_wr(nfp_prog, imm_b(nfp_prog), NFP_CSR_ACT_LM_ADDR3);
  /* For size < 4 one slot will be filled by zeroing of upper,
 * but be careful, that zeroing could be eliminated by zext
 * optimization.
 */

  nop_cnt = narrow_ld && meta->flags & FLAG_INSN_DO_ZEXT ? 2 : 3;
  wrp_nops(nfp_prog, nop_cnt);
 }

 if (narrow_ld)
  wrp_zext(nfp_prog, meta, gpr);

 while (size) {
  u32 slice_end;
  u8 slice_size;

  slice_size = min(size, 4 - gpr_byte);
  slice_end = min(off + slice_size, round_up(off + 1, 4));
  slice_size = slice_end - off;

  last = slice_size == size;

  if (needs_inc)
   off %= 4;

  ret = step(nfp_prog, gpr, gpr_byte, off, slice_size,
      first, gpr != prev_gpr, last, lm3, needs_inc);
  if (ret)
   return ret;

  prev_gpr = gpr;
  first = false;

  gpr_byte += slice_size;
  if (gpr_byte >= 4) {
   gpr_byte -= 4;
   gpr++;
  }

  size -= slice_size;
  off += slice_size;
 }

 return 0;
}

static void
wrp_alu_imm(struct nfp_prog *nfp_prog, u8 dst, enum alu_op alu_op, u32 imm)
{
 swreg tmp_reg;

 if (alu_op == ALU_OP_AND) {
  if (!imm)
   wrp_immed(nfp_prog, reg_both(dst), 0);
  if (!imm || !~imm)
   return;
 }
 if (alu_op == ALU_OP_OR) {
  if (!~imm)
   wrp_immed(nfp_prog, reg_both(dst), ~0U);
  if (!imm || !~imm)
   return;
 }
 if (alu_op == ALU_OP_XOR) {
  if (!~imm)
   emit_alu(nfp_prog, reg_both(dst), reg_none(),
     ALU_OP_NOT, reg_b(dst));
  if (!imm || !~imm)
   return;
 }

 tmp_reg = ur_load_imm_any(nfp_prog, imm, imm_b(nfp_prog));
 emit_alu(nfp_prog, reg_both(dst), reg_a(dst), alu_op, tmp_reg);
}

static int
wrp_alu64_imm(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta,
       enum alu_op alu_op, bool skip)
{
 const struct bpf_insn *insn = &meta->insn;
 u64 imm = insn->imm; /* sign extend */

 if (skip) {
  meta->flags |= FLAG_INSN_SKIP_NOOP;
  return 0;
 }

 wrp_alu_imm(nfp_prog, insn->dst_reg * 2, alu_op, imm & ~0U);
 wrp_alu_imm(nfp_prog, insn->dst_reg * 2 + 1, alu_op, imm >> 32);

 return 0;
}

static int
wrp_alu64_reg(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta,
       enum alu_op alu_op)
{
 u8 dst = meta->insn.dst_reg * 2, src = meta->insn.src_reg * 2;

 emit_alu(nfp_prog, reg_both(dst), reg_a(dst), alu_op, reg_b(src));
 emit_alu(nfp_prog, reg_both(dst + 1),
   reg_a(dst + 1), alu_op, reg_b(src + 1));

 return 0;
}

static int
wrp_alu32_imm(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta,
       enum alu_op alu_op)
{
 const struct bpf_insn *insn = &meta->insn;
 u8 dst = insn->dst_reg * 2;

 wrp_alu_imm(nfp_prog, dst, alu_op, insn->imm);
 wrp_zext(nfp_prog, meta, dst);

 return 0;
}

static int
wrp_alu32_reg(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta,
       enum alu_op alu_op)
{
 u8 dst = meta->insn.dst_reg * 2, src = meta->insn.src_reg * 2;

 emit_alu(nfp_prog, reg_both(dst), reg_a(dst), alu_op, reg_b(src));
 wrp_zext(nfp_prog, meta, dst);

 return 0;
}

static void
wrp_test_reg_one(struct nfp_prog *nfp_prog, u8 dst, enum alu_op alu_op, u8 src,
   enum br_mask br_mask, u16 off)
{
 emit_alu(nfp_prog, reg_none(), reg_a(dst), alu_op, reg_b(src));
 emit_br(nfp_prog, br_mask, off, 0);
}

static int
wrp_test_reg(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta,
      enum alu_op alu_op, enum br_mask br_mask)
{
 const struct bpf_insn *insn = &meta->insn;

 wrp_test_reg_one(nfp_prog, insn->dst_reg * 2, alu_op,
    insn->src_reg * 2, br_mask, insn->off);
 if (is_mbpf_jmp64(meta))
  wrp_test_reg_one(nfp_prog, insn->dst_reg * 2 + 1, alu_op,
     insn->src_reg * 2 + 1, br_mask, insn->off);

 return 0;
}

static const struct jmp_code_map {
 enum br_mask br_mask;
 bool swap;
} jmp_code_map[] = {
 [BPF_JGT >> 4] = { BR_BLO, true },
 [BPF_JGE >> 4] = { BR_BHS, false },
 [BPF_JLT >> 4] = { BR_BLO, false },
 [BPF_JLE >> 4] = { BR_BHS, true },
 [BPF_JSGT >> 4] = { BR_BLT, true },
 [BPF_JSGE >> 4] = { BR_BGE, false },
 [BPF_JSLT >> 4] = { BR_BLT, false },
 [BPF_JSLE >> 4] = { BR_BGE, true },
};

static const struct jmp_code_map *nfp_jmp_code_get(struct nfp_insn_meta *meta)
{
 unsigned int op;

 op = BPF_OP(meta->insn.code) >> 4;
 /* br_mask of 0 is BR_BEQ which we don't use in jump code table */
 if (WARN_ONCE(op >= ARRAY_SIZE(jmp_code_map) ||
        !jmp_code_map[op].br_mask,
        "no code found for jump instruction"))
  return NULL;

 return &jmp_code_map[op];
}

static int cmp_imm(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 const struct bpf_insn *insn = &meta->insn;
 u64 imm = insn->imm; /* sign extend */
 const struct jmp_code_map *code;
 enum alu_op alu_op, carry_op;
 u8 reg = insn->dst_reg * 2;
 swreg tmp_reg;

 code = nfp_jmp_code_get(meta);
 if (!code)
  return -EINVAL;

 alu_op = meta->jump_neg_op ? ALU_OP_ADD : ALU_OP_SUB;
 carry_op = meta->jump_neg_op ? ALU_OP_ADD_C : ALU_OP_SUB_C;

 tmp_reg = ur_load_imm_any(nfp_prog, imm & ~0U, imm_b(nfp_prog));
 if (!code->swap)
  emit_alu(nfp_prog, reg_none(), reg_a(reg), alu_op, tmp_reg);
 else
  emit_alu(nfp_prog, reg_none(), tmp_reg, alu_op, reg_a(reg));

 if (is_mbpf_jmp64(meta)) {
  tmp_reg = ur_load_imm_any(nfp_prog, imm >> 32, imm_b(nfp_prog));
  if (!code->swap)
   emit_alu(nfp_prog, reg_none(),
     reg_a(reg + 1), carry_op, tmp_reg);
  else
   emit_alu(nfp_prog, reg_none(),
     tmp_reg, carry_op, reg_a(reg + 1));
 }

 emit_br(nfp_prog, code->br_mask, insn->off, 0);

 return 0;
}

static int cmp_reg(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 const struct bpf_insn *insn = &meta->insn;
 const struct jmp_code_map *code;
 u8 areg, breg;

 code = nfp_jmp_code_get(meta);
 if (!code)
  return -EINVAL;

 areg = insn->dst_reg * 2;
 breg = insn->src_reg * 2;

 if (code->swap) {
  areg ^= breg;
  breg ^= areg;
  areg ^= breg;
 }

 emit_alu(nfp_prog, reg_none(), reg_a(areg), ALU_OP_SUB, reg_b(breg));
 if (is_mbpf_jmp64(meta))
  emit_alu(nfp_prog, reg_none(),
    reg_a(areg + 1), ALU_OP_SUB_C, reg_b(breg + 1));
 emit_br(nfp_prog, code->br_mask, insn->off, 0);

 return 0;
}

static void wrp_end32(struct nfp_prog *nfp_prog, swreg reg_in, u8 gpr_out)
{
 emit_ld_field(nfp_prog, reg_both(gpr_out), 0xf, reg_in,
        SHF_SC_R_ROT, 8);
 emit_ld_field(nfp_prog, reg_both(gpr_out), 0x5, reg_a(gpr_out),
        SHF_SC_R_ROT, 16);
}

static void
wrp_mul_u32(struct nfp_prog *nfp_prog, swreg dst_hi, swreg dst_lo, swreg lreg,
     swreg rreg, bool gen_high_half)
{
 emit_mul(nfp_prog, lreg, MUL_TYPE_START, MUL_STEP_NONE, rreg);
 emit_mul(nfp_prog, lreg, MUL_TYPE_STEP_32x32, MUL_STEP_1, rreg);
 emit_mul(nfp_prog, lreg, MUL_TYPE_STEP_32x32, MUL_STEP_2, rreg);
 emit_mul(nfp_prog, lreg, MUL_TYPE_STEP_32x32, MUL_STEP_3, rreg);
 emit_mul(nfp_prog, lreg, MUL_TYPE_STEP_32x32, MUL_STEP_4, rreg);
 emit_mul(nfp_prog, dst_lo, MUL_TYPE_STEP_32x32, MUL_LAST, reg_none());
 if (gen_high_half)
  emit_mul(nfp_prog, dst_hi, MUL_TYPE_STEP_32x32, MUL_LAST_2,
    reg_none());
 else
  wrp_immed(nfp_prog, dst_hi, 0);
}

static void
wrp_mul_u16(struct nfp_prog *nfp_prog, swreg dst_hi, swreg dst_lo, swreg lreg,
     swreg rreg)
{
 emit_mul(nfp_prog, lreg, MUL_TYPE_START, MUL_STEP_NONE, rreg);
 emit_mul(nfp_prog, lreg, MUL_TYPE_STEP_16x16, MUL_STEP_1, rreg);
 emit_mul(nfp_prog, lreg, MUL_TYPE_STEP_16x16, MUL_STEP_2, rreg);
 emit_mul(nfp_prog, dst_lo, MUL_TYPE_STEP_16x16, MUL_LAST, reg_none());
}

static int
wrp_mul(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta,
 bool gen_high_half, bool ropnd_from_reg)
{
 swreg multiplier, multiplicand, dst_hi, dst_lo;
 const struct bpf_insn *insn = &meta->insn;
 u32 lopnd_max, ropnd_max;
 u8 dst_reg;

 dst_reg = insn->dst_reg;
 multiplicand = reg_a(dst_reg * 2);
 dst_hi = reg_both(dst_reg * 2 + 1);
 dst_lo = reg_both(dst_reg * 2);
 lopnd_max = meta->umax_dst;
 if (ropnd_from_reg) {
  multiplier = reg_b(insn->src_reg * 2);
  ropnd_max = meta->umax_src;
 } else {
  u32 imm = insn->imm;

  multiplier = ur_load_imm_any(nfp_prog, imm, imm_b(nfp_prog));
  ropnd_max = imm;
 }
 if (lopnd_max > U16_MAX || ropnd_max > U16_MAX)
  wrp_mul_u32(nfp_prog, dst_hi, dst_lo, multiplicand, multiplier,
       gen_high_half);
 else
  wrp_mul_u16(nfp_prog, dst_hi, dst_lo, multiplicand, multiplier);

 return 0;
}

static int wrp_div_imm(struct nfp_prog *nfp_prog, u8 dst, u64 imm)
{
 swreg dst_both = reg_both(dst), dst_a = reg_a(dst), dst_b = reg_a(dst);
 struct reciprocal_value_adv rvalue;
 u8 pre_shift, exp;
 swreg magic;

 if (imm > U32_MAX) {
  wrp_immed(nfp_prog, dst_both, 0);
  return 0;
 }

 /* NOTE: because we are using "reciprocal_value_adv" which doesn't
 * support "divisor > (1u << 31)", we need to JIT separate NFP sequence
 * to handle such case which actually equals to the result of unsigned
 * comparison "dst >= imm" which could be calculated using the following
 * NFP sequence:
 *
 *  alu[--, dst, -, imm]
 *  immed[imm, 0]
 *  alu[dst, imm, +carry, 0]
 *
 */

 if (imm > 1U << 31) {
  swreg tmp_b = ur_load_imm_any(nfp_prog, imm, imm_b(nfp_prog));

  emit_alu(nfp_prog, reg_none(), dst_a, ALU_OP_SUB, tmp_b);
  wrp_immed(nfp_prog, imm_a(nfp_prog), 0);
  emit_alu(nfp_prog, dst_both, imm_a(nfp_prog), ALU_OP_ADD_C,
    reg_imm(0));
  return 0;
 }

 rvalue = reciprocal_value_adv(imm, 32);
 exp = rvalue.exp;
 if (rvalue.is_wide_m && !(imm & 1)) {
  pre_shift = fls(imm & -imm) - 1;
  rvalue = reciprocal_value_adv(imm >> pre_shift, 32 - pre_shift);
 } else {
  pre_shift = 0;
 }
 magic = ur_load_imm_any(nfp_prog, rvalue.m, imm_b(nfp_prog));
 if (imm == 1U << exp) {
  emit_shf(nfp_prog, dst_both, reg_none(), SHF_OP_NONE, dst_b,
    SHF_SC_R_SHF, exp);
 } else if (rvalue.is_wide_m) {
  wrp_mul_u32(nfp_prog, imm_both(nfp_prog), reg_none(), dst_a,
       magic, true);
  emit_alu(nfp_prog, dst_both, dst_a, ALU_OP_SUB,
    imm_b(nfp_prog));
  emit_shf(nfp_prog, dst_both, reg_none(), SHF_OP_NONE, dst_b,
    SHF_SC_R_SHF, 1);
  emit_alu(nfp_prog, dst_both, dst_a, ALU_OP_ADD,
    imm_b(nfp_prog));
  emit_shf(nfp_prog, dst_both, reg_none(), SHF_OP_NONE, dst_b,
    SHF_SC_R_SHF, rvalue.sh - 1);
 } else {
  if (pre_shift)
   emit_shf(nfp_prog, dst_both, reg_none(), SHF_OP_NONE,
     dst_b, SHF_SC_R_SHF, pre_shift);
  wrp_mul_u32(nfp_prog, dst_both, reg_none(), dst_a, magic, true);
  emit_shf(nfp_prog, dst_both, reg_none(), SHF_OP_NONE,
    dst_b, SHF_SC_R_SHF, rvalue.sh);
 }

 return 0;
}

static int adjust_head(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 swreg tmp = imm_a(nfp_prog), tmp_len = imm_b(nfp_prog);
 struct nfp_bpf_cap_adjust_head *adjust_head;
 u32 ret_einval, end;

 adjust_head = &nfp_prog->bpf->adjust_head;

 /* Optimized version - 5 vs 14 cycles */
 if (nfp_prog->adjust_head_location != UINT_MAX) {
  if (WARN_ON_ONCE(nfp_prog->adjust_head_location != meta->n))
   return -EINVAL;

  emit_alu(nfp_prog, pptr_reg(nfp_prog),
    reg_a(2 * 2), ALU_OP_ADD, pptr_reg(nfp_prog));
  emit_alu(nfp_prog, plen_reg(nfp_prog),
    plen_reg(nfp_prog), ALU_OP_SUB, reg_a(2 * 2));
  emit_alu(nfp_prog, pv_len(nfp_prog),
    pv_len(nfp_prog), ALU_OP_SUB, reg_a(2 * 2));

  wrp_immed(nfp_prog, reg_both(0), 0);
  wrp_immed(nfp_prog, reg_both(1), 0);

  /* TODO: when adjust head is guaranteed to succeed we can
 * also eliminate the following if (r0 == 0) branch.
 */


  return 0;
 }

 ret_einval = nfp_prog_current_offset(nfp_prog) + 14;
 end = ret_einval + 2;

 /* We need to use a temp because offset is just a part of the pkt ptr */
 emit_alu(nfp_prog, tmp,
   reg_a(2 * 2), ALU_OP_ADD_2B, pptr_reg(nfp_prog));

 /* Validate result will fit within FW datapath constraints */
 emit_alu(nfp_prog, reg_none(),
   tmp, ALU_OP_SUB, reg_imm(adjust_head->off_min));
 emit_br(nfp_prog, BR_BLO, ret_einval, 0);
 emit_alu(nfp_prog, reg_none(),
   reg_imm(adjust_head->off_max), ALU_OP_SUB, tmp);
 emit_br(nfp_prog, BR_BLO, ret_einval, 0);

 /* Validate the length is at least ETH_HLEN */
 emit_alu(nfp_prog, tmp_len,
   plen_reg(nfp_prog), ALU_OP_SUB, reg_a(2 * 2));
 emit_alu(nfp_prog, reg_none(),
   tmp_len, ALU_OP_SUB, reg_imm(ETH_HLEN));
 emit_br(nfp_prog, BR_BMI, ret_einval, 0);

 /* Load the ret code */
 wrp_immed(nfp_prog, reg_both(0), 0);
 wrp_immed(nfp_prog, reg_both(1), 0);

 /* Modify the packet metadata */
 emit_ld_field(nfp_prog, pptr_reg(nfp_prog), 0x3, tmp, SHF_SC_NONE, 0);

 /* Skip over the -EINVAL ret code (defer 2) */
 emit_br(nfp_prog, BR_UNC, end, 2);

 emit_alu(nfp_prog, plen_reg(nfp_prog),
   plen_reg(nfp_prog), ALU_OP_SUB, reg_a(2 * 2));
 emit_alu(nfp_prog, pv_len(nfp_prog),
   pv_len(nfp_prog), ALU_OP_SUB, reg_a(2 * 2));

 /* return -EINVAL target */
 if (!nfp_prog_confirm_current_offset(nfp_prog, ret_einval))
  return -EINVAL;

 wrp_immed(nfp_prog, reg_both(0), -22);
 wrp_immed(nfp_prog, reg_both(1), ~0);

 if (!nfp_prog_confirm_current_offset(nfp_prog, end))
  return -EINVAL;

 return 0;
}

static int adjust_tail(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 u32 ret_einval, end;
 swreg plen, delta;

 BUILD_BUG_ON(plen_reg(nfp_prog) != reg_b(STATIC_REG_PKT_LEN));

 plen = imm_a(nfp_prog);
 delta = reg_a(2 * 2);

 ret_einval = nfp_prog_current_offset(nfp_prog) + 9;
 end = nfp_prog_current_offset(nfp_prog) + 11;

 /* Calculate resulting length */
 emit_alu(nfp_prog, plen, plen_reg(nfp_prog), ALU_OP_ADD, delta);
 /* delta == 0 is not allowed by the kernel, add must overflow to make
 * length smaller.
 */

 emit_br(nfp_prog, BR_BCC, ret_einval, 0);

 /* if (new_len < 14) then -EINVAL */
 emit_alu(nfp_prog, reg_none(), plen, ALU_OP_SUB, reg_imm(ETH_HLEN));
 emit_br(nfp_prog, BR_BMI, ret_einval, 0);

 emit_alu(nfp_prog, plen_reg(nfp_prog),
   plen_reg(nfp_prog), ALU_OP_ADD, delta);
 emit_alu(nfp_prog, pv_len(nfp_prog),
   pv_len(nfp_prog), ALU_OP_ADD, delta);

 emit_br(nfp_prog, BR_UNC, end, 2);
 wrp_immed(nfp_prog, reg_both(0), 0);
 wrp_immed(nfp_prog, reg_both(1), 0);

 if (!nfp_prog_confirm_current_offset(nfp_prog, ret_einval))
  return -EINVAL;

 wrp_immed(nfp_prog, reg_both(0), -22);
 wrp_immed(nfp_prog, reg_both(1), ~0);

 if (!nfp_prog_confirm_current_offset(nfp_prog, end))
  return -EINVAL;

 return 0;
}

static int
map_call_stack_common(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 bool load_lm_ptr;
 u32 ret_tgt;
 s64 lm_off;

 /* We only have to reload LM0 if the key is not at start of stack */
 lm_off = nfp_prog->stack_frame_depth;
 lm_off += meta->arg2.reg.var_off.value + meta->arg2.reg.off;
 load_lm_ptr = meta->arg2.var_off || lm_off;

 /* Set LM0 to start of key */
 if (load_lm_ptr)
  emit_csr_wr(nfp_prog, reg_b(2 * 2), NFP_CSR_ACT_LM_ADDR0);
 if (meta->func_id == BPF_FUNC_map_update_elem)
  emit_csr_wr(nfp_prog, reg_b(3 * 2), NFP_CSR_ACT_LM_ADDR2);

 emit_br_relo(nfp_prog, BR_UNC, BR_OFF_RELO + meta->func_id,
       2, RELO_BR_HELPER);
 ret_tgt = nfp_prog_current_offset(nfp_prog) + 2;

 /* Load map ID into A0 */
 wrp_mov(nfp_prog, reg_a(0), reg_a(2));

 /* Load the return address into B0 */
 wrp_immed_relo(nfp_prog, reg_b(0), ret_tgt, RELO_IMMED_REL);

 if (!nfp_prog_confirm_current_offset(nfp_prog, ret_tgt))
  return -EINVAL;

 /* Reset the LM0 pointer */
 if (!load_lm_ptr)
  return 0;

 emit_csr_wr(nfp_prog, stack_reg(nfp_prog), NFP_CSR_ACT_LM_ADDR0);
 wrp_nops(nfp_prog, 3);

 return 0;
}

static int
nfp_get_prandom_u32(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 __emit_csr_rd(nfp_prog, NFP_CSR_PSEUDO_RND_NUM);
 /* CSR value is read in following immed[gpr, 0] */
 emit_immed(nfp_prog, reg_both(0), 0,
     IMMED_WIDTH_ALL, false, IMMED_SHIFT_0B);
 emit_immed(nfp_prog, reg_both(1), 0,
     IMMED_WIDTH_ALL, false, IMMED_SHIFT_0B);
 return 0;
}

static int
nfp_perf_event_output(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 swreg ptr_type;
 u32 ret_tgt;

 ptr_type = ur_load_imm_any(nfp_prog, meta->arg1.type, imm_a(nfp_prog));

 ret_tgt = nfp_prog_current_offset(nfp_prog) + 3;

 emit_br_relo(nfp_prog, BR_UNC, BR_OFF_RELO + meta->func_id,
       2, RELO_BR_HELPER);

 /* Load ptr type into A1 */
 wrp_mov(nfp_prog, reg_a(1), ptr_type);

 /* Load the return address into B0 */
 wrp_immed_relo(nfp_prog, reg_b(0), ret_tgt, RELO_IMMED_REL);

 if (!nfp_prog_confirm_current_offset(nfp_prog, ret_tgt))
  return -EINVAL;

 return 0;
}

static int
nfp_queue_select(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 u32 jmp_tgt;

 jmp_tgt = nfp_prog_current_offset(nfp_prog) + 5;

 /* Make sure the queue id fits into FW field */
 emit_alu(nfp_prog, reg_none(), reg_a(meta->insn.src_reg * 2),
   ALU_OP_AND_NOT_B, reg_imm(0xff));
 emit_br(nfp_prog, BR_BEQ, jmp_tgt, 2);

 /* Set the 'queue selected' bit and the queue value */
 emit_shf(nfp_prog, pv_qsel_set(nfp_prog),
   pv_qsel_set(nfp_prog), SHF_OP_OR, reg_imm(1),
   SHF_SC_L_SHF, PKT_VEL_QSEL_SET_BIT);
 emit_ld_field(nfp_prog,
        pv_qsel_val(nfp_prog), 0x1, reg_b(meta->insn.src_reg * 2),
        SHF_SC_NONE, 0);
 /* Delay slots end here, we will jump over next instruction if queue
 * value fits into the field.
 */

 emit_ld_field(nfp_prog,
        pv_qsel_val(nfp_prog), 0x1, reg_imm(NFP_NET_RXR_MAX),
        SHF_SC_NONE, 0);

 if (!nfp_prog_confirm_current_offset(nfp_prog, jmp_tgt))
  return -EINVAL;

 return 0;
}

/* --- Callbacks --- */
static int mov_reg64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 const struct bpf_insn *insn = &meta->insn;
 u8 dst = insn->dst_reg * 2;
 u8 src = insn->src_reg * 2;

 if (insn->src_reg == BPF_REG_10) {
  swreg stack_depth_reg;

  stack_depth_reg = ur_load_imm_any(nfp_prog,
        nfp_prog->stack_frame_depth,
        stack_imm(nfp_prog));
  emit_alu(nfp_prog, reg_both(dst), stack_reg(nfp_prog),
    ALU_OP_ADD, stack_depth_reg);
  wrp_immed(nfp_prog, reg_both(dst + 1), 0);
 } else {
  wrp_reg_mov(nfp_prog, dst, src);
  wrp_reg_mov(nfp_prog, dst + 1, src + 1);
 }

 return 0;
}

static int mov_imm64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 u64 imm = meta->insn.imm; /* sign extend */

 wrp_immed(nfp_prog, reg_both(meta->insn.dst_reg * 2), imm & ~0U);
 wrp_immed(nfp_prog, reg_both(meta->insn.dst_reg * 2 + 1), imm >> 32);

 return 0;
}

static int xor_reg64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 return wrp_alu64_reg(nfp_prog, meta, ALU_OP_XOR);
}

static int xor_imm64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 return wrp_alu64_imm(nfp_prog, meta, ALU_OP_XOR, !meta->insn.imm);
}

static int and_reg64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 return wrp_alu64_reg(nfp_prog, meta, ALU_OP_AND);
}

static int and_imm64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 return wrp_alu64_imm(nfp_prog, meta, ALU_OP_AND, !~meta->insn.imm);
}

static int or_reg64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 return wrp_alu64_reg(nfp_prog, meta, ALU_OP_OR);
}

static int or_imm64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 return wrp_alu64_imm(nfp_prog, meta, ALU_OP_OR, !meta->insn.imm);
}

static int add_reg64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 const struct bpf_insn *insn = &meta->insn;

 emit_alu(nfp_prog, reg_both(insn->dst_reg * 2),
   reg_a(insn->dst_reg * 2), ALU_OP_ADD,
   reg_b(insn->src_reg * 2));
 emit_alu(nfp_prog, reg_both(insn->dst_reg * 2 + 1),
   reg_a(insn->dst_reg * 2 + 1), ALU_OP_ADD_C,
   reg_b(insn->src_reg * 2 + 1));

 return 0;
}

static int add_imm64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 const struct bpf_insn *insn = &meta->insn;
 u64 imm = insn->imm; /* sign extend */

 wrp_alu_imm(nfp_prog, insn->dst_reg * 2, ALU_OP_ADD, imm & ~0U);
 wrp_alu_imm(nfp_prog, insn->dst_reg * 2 + 1, ALU_OP_ADD_C, imm >> 32);

 return 0;
}

static int sub_reg64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 const struct bpf_insn *insn = &meta->insn;

 emit_alu(nfp_prog, reg_both(insn->dst_reg * 2),
   reg_a(insn->dst_reg * 2), ALU_OP_SUB,
   reg_b(insn->src_reg * 2));
 emit_alu(nfp_prog, reg_both(insn->dst_reg * 2 + 1),
   reg_a(insn->dst_reg * 2 + 1), ALU_OP_SUB_C,
   reg_b(insn->src_reg * 2 + 1));

 return 0;
}

static int sub_imm64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 const struct bpf_insn *insn = &meta->insn;
 u64 imm = insn->imm; /* sign extend */

 wrp_alu_imm(nfp_prog, insn->dst_reg * 2, ALU_OP_SUB, imm & ~0U);
 wrp_alu_imm(nfp_prog, insn->dst_reg * 2 + 1, ALU_OP_SUB_C, imm >> 32);

 return 0;
}

static int mul_reg64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 return wrp_mul(nfp_prog, meta, truetrue);
}

static int mul_imm64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 return wrp_mul(nfp_prog, meta, truefalse);
}

static int div_imm64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 const struct bpf_insn *insn = &meta->insn;

 return wrp_div_imm(nfp_prog, insn->dst_reg * 2, insn->imm);
}

static int div_reg64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 /* NOTE: verifier hook has rejected cases for which verifier doesn't
 * know whether the source operand is constant or not.
 */

 return wrp_div_imm(nfp_prog, meta->insn.dst_reg * 2, meta->umin_src);
}

static int neg_reg64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 const struct bpf_insn *insn = &meta->insn;

 emit_alu(nfp_prog, reg_both(insn->dst_reg * 2), reg_imm(0),
   ALU_OP_SUB, reg_b(insn->dst_reg * 2));
 emit_alu(nfp_prog, reg_both(insn->dst_reg * 2 + 1), reg_imm(0),
   ALU_OP_SUB_C, reg_b(insn->dst_reg * 2 + 1));

 return 0;
}

/* Pseudo code:
 *   if shift_amt >= 32
 *     dst_high = dst_low << shift_amt[4:0]
 *     dst_low = 0;
 *   else
 *     dst_high = (dst_high, dst_low) >> (32 - shift_amt)
 *     dst_low = dst_low << shift_amt
 *
 * The indirect shift will use the same logic at runtime.
 */

static int __shl_imm64(struct nfp_prog *nfp_prog, u8 dst, u8 shift_amt)
{
 if (!shift_amt)
  return 0;

 if (shift_amt < 32) {
  emit_shf(nfp_prog, reg_both(dst + 1), reg_a(dst + 1),
    SHF_OP_NONE, reg_b(dst), SHF_SC_R_DSHF,
    32 - shift_amt);
  emit_shf(nfp_prog, reg_both(dst), reg_none(), SHF_OP_NONE,
    reg_b(dst), SHF_SC_L_SHF, shift_amt);
 } else if (shift_amt == 32) {
  wrp_reg_mov(nfp_prog, dst + 1, dst);
  wrp_immed(nfp_prog, reg_both(dst), 0);
 } else if (shift_amt > 32) {
  emit_shf(nfp_prog, reg_both(dst + 1), reg_none(), SHF_OP_NONE,
    reg_b(dst), SHF_SC_L_SHF, shift_amt - 32);
  wrp_immed(nfp_prog, reg_both(dst), 0);
 }

 return 0;
}

static int shl_imm64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 const struct bpf_insn *insn = &meta->insn;
 u8 dst = insn->dst_reg * 2;

 return __shl_imm64(nfp_prog, dst, insn->imm);
}

static void shl_reg64_lt32_high(struct nfp_prog *nfp_prog, u8 dst, u8 src)
{
 emit_alu(nfp_prog, imm_both(nfp_prog), reg_imm(32), ALU_OP_SUB,
   reg_b(src));
 emit_alu(nfp_prog, reg_none(), imm_a(nfp_prog), ALU_OP_OR, reg_imm(0));
 emit_shf_indir(nfp_prog, reg_both(dst + 1), reg_a(dst + 1), SHF_OP_NONE,
         reg_b(dst), SHF_SC_R_DSHF);
}

/* NOTE: for indirect left shift, HIGH part should be calculated first. */
static void shl_reg64_lt32_low(struct nfp_prog *nfp_prog, u8 dst, u8 src)
{
 emit_alu(nfp_prog, reg_none(), reg_a(src), ALU_OP_OR, reg_imm(0));
 emit_shf_indir(nfp_prog, reg_both(dst), reg_none(), SHF_OP_NONE,
         reg_b(dst), SHF_SC_L_SHF);
}

static void shl_reg64_lt32(struct nfp_prog *nfp_prog, u8 dst, u8 src)
{
 shl_reg64_lt32_high(nfp_prog, dst, src);
 shl_reg64_lt32_low(nfp_prog, dst, src);
}

static void shl_reg64_ge32(struct nfp_prog *nfp_prog, u8 dst, u8 src)
{
 emit_alu(nfp_prog, reg_none(), reg_a(src), ALU_OP_OR, reg_imm(0));
 emit_shf_indir(nfp_prog, reg_both(dst + 1), reg_none(), SHF_OP_NONE,
         reg_b(dst), SHF_SC_L_SHF);
 wrp_immed(nfp_prog, reg_both(dst), 0);
}

static int shl_reg64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 const struct bpf_insn *insn = &meta->insn;
 u64 umin, umax;
 u8 dst, src;

 dst = insn->dst_reg * 2;
 umin = meta->umin_src;
 umax = meta->umax_src;
 if (umin == umax)
  return __shl_imm64(nfp_prog, dst, umin);

 src = insn->src_reg * 2;
 if (umax < 32) {
  shl_reg64_lt32(nfp_prog, dst, src);
 } else if (umin >= 32) {
  shl_reg64_ge32(nfp_prog, dst, src);
 } else {
  /* Generate different instruction sequences depending on runtime
 * value of shift amount.
 */

  u16 label_ge32, label_end;

  label_ge32 = nfp_prog_current_offset(nfp_prog) + 7;
  emit_br_bset(nfp_prog, reg_a(src), 5, label_ge32, 0);

  shl_reg64_lt32_high(nfp_prog, dst, src);
  label_end = nfp_prog_current_offset(nfp_prog) + 6;
  emit_br(nfp_prog, BR_UNC, label_end, 2);
  /* shl_reg64_lt32_low packed in delay slot. */
  shl_reg64_lt32_low(nfp_prog, dst, src);

  if (!nfp_prog_confirm_current_offset(nfp_prog, label_ge32))
   return -EINVAL;
  shl_reg64_ge32(nfp_prog, dst, src);

  if (!nfp_prog_confirm_current_offset(nfp_prog, label_end))
   return -EINVAL;
 }

 return 0;
}

/* Pseudo code:
 *   if shift_amt >= 32
 *     dst_high = 0;
 *     dst_low = dst_high >> shift_amt[4:0]
 *   else
 *     dst_high = dst_high >> shift_amt
 *     dst_low = (dst_high, dst_low) >> shift_amt
 *
 * The indirect shift will use the same logic at runtime.
 */

static int __shr_imm64(struct nfp_prog *nfp_prog, u8 dst, u8 shift_amt)
{
 if (!shift_amt)
  return 0;

 if (shift_amt < 32) {
  emit_shf(nfp_prog, reg_both(dst), reg_a(dst + 1), SHF_OP_NONE,
    reg_b(dst), SHF_SC_R_DSHF, shift_amt);
  emit_shf(nfp_prog, reg_both(dst + 1), reg_none(), SHF_OP_NONE,
    reg_b(dst + 1), SHF_SC_R_SHF, shift_amt);
 } else if (shift_amt == 32) {
  wrp_reg_mov(nfp_prog, dst, dst + 1);
  wrp_immed(nfp_prog, reg_both(dst + 1), 0);
 } else if (shift_amt > 32) {
  emit_shf(nfp_prog, reg_both(dst), reg_none(), SHF_OP_NONE,
    reg_b(dst + 1), SHF_SC_R_SHF, shift_amt - 32);
  wrp_immed(nfp_prog, reg_both(dst + 1), 0);
 }

 return 0;
}

static int shr_imm64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 const struct bpf_insn *insn = &meta->insn;
 u8 dst = insn->dst_reg * 2;

 return __shr_imm64(nfp_prog, dst, insn->imm);
}

/* NOTE: for indirect right shift, LOW part should be calculated first. */
static void shr_reg64_lt32_high(struct nfp_prog *nfp_prog, u8 dst, u8 src)
{
 emit_alu(nfp_prog, reg_none(), reg_a(src), ALU_OP_OR, reg_imm(0));
 emit_shf_indir(nfp_prog, reg_both(dst + 1), reg_none(), SHF_OP_NONE,
         reg_b(dst + 1), SHF_SC_R_SHF);
}

static void shr_reg64_lt32_low(struct nfp_prog *nfp_prog, u8 dst, u8 src)
{
 emit_alu(nfp_prog, reg_none(), reg_a(src), ALU_OP_OR, reg_imm(0));
 emit_shf_indir(nfp_prog, reg_both(dst), reg_a(dst + 1), SHF_OP_NONE,
         reg_b(dst), SHF_SC_R_DSHF);
}

static void shr_reg64_lt32(struct nfp_prog *nfp_prog, u8 dst, u8 src)
{
 shr_reg64_lt32_low(nfp_prog, dst, src);
 shr_reg64_lt32_high(nfp_prog, dst, src);
}

static void shr_reg64_ge32(struct nfp_prog *nfp_prog, u8 dst, u8 src)
{
 emit_alu(nfp_prog, reg_none(), reg_a(src), ALU_OP_OR, reg_imm(0));
 emit_shf_indir(nfp_prog, reg_both(dst), reg_none(), SHF_OP_NONE,
         reg_b(dst + 1), SHF_SC_R_SHF);
 wrp_immed(nfp_prog, reg_both(dst + 1), 0);
}

static int shr_reg64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 const struct bpf_insn *insn = &meta->insn;
 u64 umin, umax;
 u8 dst, src;

 dst = insn->dst_reg * 2;
 umin = meta->umin_src;
 umax = meta->umax_src;
 if (umin == umax)
  return __shr_imm64(nfp_prog, dst, umin);

 src = insn->src_reg * 2;
 if (umax < 32) {
  shr_reg64_lt32(nfp_prog, dst, src);
 } else if (umin >= 32) {
  shr_reg64_ge32(nfp_prog, dst, src);
 } else {
  /* Generate different instruction sequences depending on runtime
 * value of shift amount.
 */

  u16 label_ge32, label_end;

  label_ge32 = nfp_prog_current_offset(nfp_prog) + 6;
  emit_br_bset(nfp_prog, reg_a(src), 5, label_ge32, 0);
  shr_reg64_lt32_low(nfp_prog, dst, src);
  label_end = nfp_prog_current_offset(nfp_prog) + 6;
  emit_br(nfp_prog, BR_UNC, label_end, 2);
  /* shr_reg64_lt32_high packed in delay slot. */
  shr_reg64_lt32_high(nfp_prog, dst, src);

  if (!nfp_prog_confirm_current_offset(nfp_prog, label_ge32))
   return -EINVAL;
  shr_reg64_ge32(nfp_prog, dst, src);

  if (!nfp_prog_confirm_current_offset(nfp_prog, label_end))
   return -EINVAL;
 }

 return 0;
}

/* Code logic is the same as __shr_imm64 except ashr requires signedness bit
 * told through PREV_ALU result.
 */

static int __ashr_imm64(struct nfp_prog *nfp_prog, u8 dst, u8 shift_amt)
{
 if (!shift_amt)
  return 0;

 if (shift_amt < 32) {
  emit_shf(nfp_prog, reg_both(dst), reg_a(dst + 1), SHF_OP_NONE,
    reg_b(dst), SHF_SC_R_DSHF, shift_amt);
  /* Set signedness bit. */
  emit_alu(nfp_prog, reg_none(), reg_a(dst + 1), ALU_OP_OR,
    reg_imm(0));
  emit_shf(nfp_prog, reg_both(dst + 1), reg_none(), SHF_OP_ASHR,
    reg_b(dst + 1), SHF_SC_R_SHF, shift_amt);
 } else if (shift_amt == 32) {
  /* NOTE: this also helps setting signedness bit. */
  wrp_reg_mov(nfp_prog, dst, dst + 1);
  emit_shf(nfp_prog, reg_both(dst + 1), reg_none(), SHF_OP_ASHR,
    reg_b(dst + 1), SHF_SC_R_SHF, 31);
 } else if (shift_amt > 32) {
  emit_alu(nfp_prog, reg_none(), reg_a(dst + 1), ALU_OP_OR,
    reg_imm(0));
  emit_shf(nfp_prog, reg_both(dst), reg_none(), SHF_OP_ASHR,
    reg_b(dst + 1), SHF_SC_R_SHF, shift_amt - 32);
  emit_shf(nfp_prog, reg_both(dst + 1), reg_none(), SHF_OP_ASHR,
    reg_b(dst + 1), SHF_SC_R_SHF, 31);
 }

 return 0;
}

static int ashr_imm64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 const struct bpf_insn *insn = &meta->insn;
 u8 dst = insn->dst_reg * 2;

 return __ashr_imm64(nfp_prog, dst, insn->imm);
}

static void ashr_reg64_lt32_high(struct nfp_prog *nfp_prog, u8 dst, u8 src)
{
 /* NOTE: the first insn will set both indirect shift amount (source A)
 * and signedness bit (MSB of result).
 */

 emit_alu(nfp_prog, reg_none(), reg_a(src), ALU_OP_OR, reg_b(dst + 1));
 emit_shf_indir(nfp_prog, reg_both(dst + 1), reg_none(), SHF_OP_ASHR,
         reg_b(dst + 1), SHF_SC_R_SHF);
}

static void ashr_reg64_lt32_low(struct nfp_prog *nfp_prog, u8 dst, u8 src)
{
 /* NOTE: it is the same as logic shift because we don't need to shift in
 * signedness bit when the shift amount is less than 32.
 */

 return shr_reg64_lt32_low(nfp_prog, dst, src);
}

static void ashr_reg64_lt32(struct nfp_prog *nfp_prog, u8 dst, u8 src)
{
 ashr_reg64_lt32_low(nfp_prog, dst, src);
 ashr_reg64_lt32_high(nfp_prog, dst, src);
}

static void ashr_reg64_ge32(struct nfp_prog *nfp_prog, u8 dst, u8 src)
{
 emit_alu(nfp_prog, reg_none(), reg_a(src), ALU_OP_OR, reg_b(dst + 1));
 emit_shf_indir(nfp_prog, reg_both(dst), reg_none(), SHF_OP_ASHR,
         reg_b(dst + 1), SHF_SC_R_SHF);
 emit_shf(nfp_prog, reg_both(dst + 1), reg_none(), SHF_OP_ASHR,
   reg_b(dst + 1), SHF_SC_R_SHF, 31);
}

/* Like ashr_imm64, but need to use indirect shift. */
static int ashr_reg64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 const struct bpf_insn *insn = &meta->insn;
 u64 umin, umax;
 u8 dst, src;

 dst = insn->dst_reg * 2;
 umin = meta->umin_src;
 umax = meta->umax_src;
 if (umin == umax)
  return __ashr_imm64(nfp_prog, dst, umin);

 src = insn->src_reg * 2;
 if (umax < 32) {
  ashr_reg64_lt32(nfp_prog, dst, src);
 } else if (umin >= 32) {
  ashr_reg64_ge32(nfp_prog, dst, src);
 } else {
  u16 label_ge32, label_end;

  label_ge32 = nfp_prog_current_offset(nfp_prog) + 6;
  emit_br_bset(nfp_prog, reg_a(src), 5, label_ge32, 0);
  ashr_reg64_lt32_low(nfp_prog, dst, src);
  label_end = nfp_prog_current_offset(nfp_prog) + 6;
  emit_br(nfp_prog, BR_UNC, label_end, 2);
  /* ashr_reg64_lt32_high packed in delay slot. */
  ashr_reg64_lt32_high(nfp_prog, dst, src);

  if (!nfp_prog_confirm_current_offset(nfp_prog, label_ge32))
   return -EINVAL;
  ashr_reg64_ge32(nfp_prog, dst, src);

  if (!nfp_prog_confirm_current_offset(nfp_prog, label_end))
   return -EINVAL;
 }

 return 0;
}

static int mov_reg(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
 const struct bpf_insn *insn = &meta->insn;

 wrp_reg_mov(nfp_prog, insn->dst_reg * 2,  insn->src_reg * 2);
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=97 H=99 G=97

¤ Dauer der Verarbeitung: 0.48 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.