/* bpf.pk - eBPF implementation for GNU poke.  */

/* Copyright (C) 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026 Jose E.
 * Marchesi.  */

/* This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/* This file contains a description of the Linux eBPF instruction
   set.  */

/* Instruction classes.  */

type BPF_Insn_Class = uint<3>;

var BPF_INSN_CLASS_LD    = 0b000 as BPF_Insn_Class,
    BPF_INSN_CLASS_LDX   = 0b001 as BPF_Insn_Class,
    BPF_INSN_CLASS_ST    = 0b010 as BPF_Insn_Class,
    BPF_INSN_CLASS_STX   = 0b011 as BPF_Insn_Class,
    BPF_INSN_CLASS_ALU32 = 0b100 as BPF_Insn_Class,
    BPF_INSN_CLASS_JMP64 = 0b101 as BPF_Insn_Class,
    BPF_INSN_CLASS_JMP32 = 0b110 as BPF_Insn_Class,
    BPF_INSN_CLASS_ALU64 = 0b111 as BPF_Insn_Class;

/* ALU instructions operation codes.  */

type BPF_Insn_Op_Code = uint<4>;

var BPF_ALU_OP_ADD  = 0x0 as BPF_Insn_Op_Code,
    BPF_ALU_OP_SUB  = 0x1 as BPF_Insn_Op_Code,
    BPF_ALU_OP_MUL  = 0x2 as BPF_Insn_Op_Code,
    BPF_ALU_OP_DIV  = 0x3 as BPF_Insn_Op_Code,
    BPF_ALU_OP_OR   = 0x4 as BPF_Insn_Op_Code,
    BPF_ALU_OP_AND  = 0x5 as BPF_Insn_Op_Code,
    BPF_ALU_OP_LSH  = 0x6 as BPF_Insn_Op_Code,
    BPF_ALU_OP_RSH  = 0x7 as BPF_Insn_Op_Code,
    BPF_ALU_OP_NEG  = 0x8 as BPF_Insn_Op_Code,
    BPF_ALU_OP_MOD  = 0x9 as BPF_Insn_Op_Code,
    BPF_ALU_OP_XOR  = 0xa as BPF_Insn_Op_Code,
    BPF_ALU_OP_MOV  = 0xb as BPF_Insn_Op_Code,
    BPF_ALU_OP_ARSH = 0xc as BPF_Insn_Op_Code,
    BPF_ALU_OP_END  = 0xd as BPF_Insn_Op_Code;

/* JMP instructions operation codes.  */

var BPF_JMP_CODE_JA   = 0x0 as BPF_Insn_Op_Code,
    BPF_JMP_CODE_JEQ  = 0x1 as BPF_Insn_Op_Code,
    BPF_JMP_CODE_JGT  = 0x2 as BPF_Insn_Op_Code,
    BPF_JMP_CODE_JGE  = 0x3 as BPF_Insn_Op_Code,
    BPF_JMP_CODE_JSET = 0x4 as BPF_Insn_Op_Code,
    BPF_JMP_CODE_JNE  = 0x5 as BPF_Insn_Op_Code,
    BPF_JMP_CODE_JSGT = 0x6 as BPF_Insn_Op_Code,
    BPF_JMP_CODE_JSGE = 0x7 as BPF_Insn_Op_Code,
    BPF_JMP_CODE_CALL = 0x8 as BPF_Insn_Op_Code,
    BPF_JMP_CODE_EXIT = 0x9 as BPF_Insn_Op_Code,
    BPF_JMP_CODE_JLT  = 0xa as BPF_Insn_Op_Code,
    BPF_JMP_CODE_JLE  = 0xb as BPF_Insn_Op_Code,
    BPF_JMP_CODE_JSLT = 0xc as BPF_Insn_Op_Code,
    BPF_JMP_CODE_JSLE = 0xd as BPF_Insn_Op_Code,
    BPF_JMP_CODE_JCND = 0xe as BPF_Insn_Op_Code;

/* Certain instructions abuse other instruction fields as opcodes.
   For example, sdiv is encoded as a regular div with an additional
   bit set in the otherwise-unused offset16 portion of the instruction.  */
var BPF_OFFSET16_SDIVMOD =  0x1 as uint<16>,
    BPF_OFFSET16_MOVS8   =  0x8 as uint<16>,
    BPF_OFFSET16_MOVS16  = 0x10 as uint<16>,
    BPF_OFFSET16_MOVS32  = 0x20 as uint<16>;

var BPF_IMM32_END16      = 0x10U as int<32>,
    BPF_IMM32_END32      = 0x20U as int<32>,
    BPF_IMM32_END64      = 0x40U as int<32>;

var BPF_IMM32_BSWAP16    = 0x10U as int<32>,
    BPF_IMM32_BSWAP32    = 0x20U as int<32>,
    BPF_IMM32_BSWAP64    = 0x40U as int<32>;

/* Instruction source for ALU and JMP instructions.  */
type BPF_Insn_Source = uint<1>;

var BPF_SRC_K = 0T,  /* SRC is immediate value.  */
    BPF_SRC_X = 1T;  /* SRC is %src register.  */

/* LD and ST instruction sizes.  */
type BPF_Insn_Data_Size = uint<2>;

var BPF_LDST_SIZE_W  = 0b00 as BPF_Insn_Data_Size,
    BPF_LDST_SIZE_H  = 0b01 as BPF_Insn_Data_Size,
    BPF_LDST_SIZE_B  = 0b10 as BPF_Insn_Data_Size,
    BPF_LDST_SIZE_DW = 0b11 as BPF_Insn_Data_Size;

/* LD and ST instruction modes.  */

type BPF_Insn_Ldst_Mode = uint<3>;

var BPF_LDST_MODE_IMM    = 0b000 as BPF_Insn_Ldst_Mode,
    BPF_LDST_MODE_ABS    = 0b001 as BPF_Insn_Ldst_Mode,
    BPF_LDST_MODE_IND    = 0b010 as BPF_Insn_Ldst_Mode,
    BPF_LDST_MODE_MEM    = 0b011 as BPF_Insn_Ldst_Mode,
    BPF_LDST_MODE_SMEM   = 0b100 as BPF_Insn_Ldst_Mode,
    BPF_LDST_MODE_ATOMIC = 0b110 as BPF_Insn_Ldst_Mode;

/* Atomic instruction fetch flag: if set, the original value
   that was in memory is returned in destination register.  */
var BPF_ATOMIC_FETCH = 1T;

/* Operation codes unique to atomic instructions.  */
var BPF_ATOMIC_OP_XCHG = 0xe as BPF_Insn_Op_Code,
    BPF_ATOMIC_OP_CMP  = 0xf as BPF_Insn_Op_Code;

/* Registers.  */

var BPF_R0 = 0x0 as uint<4>,
    BPF_R1 = 0x1 as uint<4>,
    BPF_R2 = 0x2 as uint<4>,
    BPF_R3 = 0x3 as uint<4>,
    BPF_R4 = 0x4 as uint<4>,
    BPF_R5 = 0x5 as uint<4>,
    BPF_R6 = 0x6 as uint<4>,
    BPF_R7 = 0x7 as uint<4>,
    BPF_R8 = 0x8 as uint<4>,
    BPF_R9 = 0x9 as uint<4>,
    BPF_FP = 0xa as uint<4>;

type BPF_Reg =
  struct
  {
   uint<4> code;

   method as_asm = string:
     {
       if (code <= BPF_R9)
         return format ("%<insn-register:%%r%i32d%>", code);
       else
         return format ("%<insn-register:fp%>");
     }

   method _print = void:
     { print "#<" + as_asm + ">"; }
  };

/* Source and destination registers for instructions.  4-bits each.
   Note that the order of the source and destination registers is
   endian dependent!  */

type BPF_Insn_Regs =
  struct
  {
    var little_p = (get_endian == ENDIAN_LITTLE);

    BPF_Reg src @ !little_p * 4#b;
    BPF_Reg dst @ little_p * 4#b;
  };


/* ALU instructions.
   BPF ALU and Jump instructions layout:
      +----+---+-----+--------+---..---+---....---+
      |code|src|class|  regs  | offs16 |  imm32   |
      +----+---+-----+--------+---..---+---....---+
   bits  4   1    3       8       16        32       */

type BPF_Insn_ALU = struct
  {
    BPF_Insn_Op_Code code;
    BPF_Insn_Source  src;
    BPF_Insn_Class   class
      : class in [BPF_INSN_CLASS_ALU32, BPF_INSN_CLASS_ALU64];

    BPF_Insn_Regs regs;

    /* Some insns steal the otherwise-unused-by-alu-insns offset16 field
       for additional opcode bits...  */
    uint<16> code_ext = 0
                      : (code in [BPF_ALU_OP_DIV, BPF_ALU_OP_MOD]
                          => code_ext in [0UH, BPF_OFFSET16_SDIVMOD]) &&
                        (code == BPF_ALU_OP_MOV
                          => code_ext in [0UH, BPF_OFFSET16_MOVS8,
                           BPF_OFFSET16_MOVS16, BPF_OFFSET16_MOVS32]);
                        // N.B. otherwise code_ext is ignored.

    /* ... but the endianness swapping insns steal imm32 instead. Why not?  */
    int<32>  imm32 : ((class == BPF_INSN_CLASS_ALU32 && code == BPF_ALU_OP_END)
                        => imm32 in [BPF_IMM32_END16, BPF_IMM32_END32,
                                     BPF_IMM32_END64]) &&
                     ((class == BPF_INSN_CLASS_ALU64 && code == BPF_ALU_OP_END)
                        => imm32 in [BPF_IMM32_BSWAP16, BPF_IMM32_BSWAP32,
                                     BPF_IMM32_BSWAP64]);

    var names = [ "add", "sub", "mul", "div", "or", "and", "lsh",
                  "rsh", "neg", "mod", "xor", "mov", "arsh", "end" ];

    method mnemonic = string:
      {
        var prefix = "", postfix = "";

        if (code in [BPF_ALU_OP_DIV, BPF_ALU_OP_MOD]
              && code_ext == BPF_OFFSET16_SDIVMOD)
          prefix = "s";

        else if (code == BPF_ALU_OP_MOV)
          {
            if (code_ext == BPF_OFFSET16_MOVS8)
              postfix = "s8";
            else if (code_ext == BPF_OFFSET16_MOVS16)
              postfix = "s16";
            else if (code_ext == BPF_OFFSET16_MOVS32)
              postfix = "s32";
          }
        else if (class == BPF_INSN_CLASS_ALU32 && code == BPF_ALU_OP_END)
          {
            if (src == BPF_SRC_K)
              postfix = "le";
            else
              postfix = "be";
          }
        else if (class == BPF_INSN_CLASS_ALU64 && code == BPF_ALU_OP_END)
          return "bswap";

        return prefix + names[code] + postfix;
      }

    method as_asm = string:
      {
        if (code == BPF_ALU_OP_NEG)
          return format ("%s\t%s", mnemonic, regs.dst.as_asm);
        else if (src == BPF_SRC_K)
          return format ("%s\t%s, %i32d", mnemonic, regs.dst.as_asm, imm32);
        else if (code == BPF_ALU_OP_MOV && code_ext)
          return format ("%s\t%s, %s, %i16d", mnemonic, regs.dst.as_asm,
                         regs.src.as_asm, code_ext);
        else
          return format ("%s\t%s, %s", mnemonic, regs.dst.as_asm, regs.src.as_asm);
      }

    method _print = void:
      { print "#<" + as_asm + ">"; }

  };

/* Jump instructions.
   Format is the same as ALU instructions, but off16 and imm32 are always
   just immediates and never encode operations.  So far.  */

type BPF_Insn_Jmp = struct
  {
    BPF_Insn_Op_Code code;
    BPF_Insn_Source  src;
    BPF_Insn_Class   class
      : class in [BPF_INSN_CLASS_JMP32, BPF_INSN_CLASS_JMP64];

    BPF_Insn_Regs regs;
    int<16> off16;
    int<32> imm32;

    var names = ["ja", "jeq", "jgt", "jge", "jset", "jne", "jsgt",
                 "jsge", "call", "exit", "jlt", "jle", "jslt", "jsle",
                 "jcnd"];

    method mnemonic = string:
      {
        if (code in [BPF_JMP_CODE_CALL, BPF_JMP_CODE_EXIT])
          return names[code];
        else if (class == BPF_INSN_CLASS_JMP32 && code == BPF_JMP_CODE_JA)
          return names[code] + "l";

        return format ("%s%s", names[code],
                       class == BPF_INSN_CLASS_JMP32 ? "32" : "");
      }

    method as_asm = string:
      {
        if (code == BPF_JMP_CODE_CALL && src == BPF_SRC_K)
          return format ("%s\t%i32d", mnemonic, imm32);
        else if (code == BPF_JMP_CODE_CALL)
          return format ("%s\t%s", mnemonic, regs.src.as_asm);
        else if (code == BPF_JMP_CODE_EXIT)
          return mnemonic;
        else if (code == BPF_JMP_CODE_JA)
          {
            if (src == BPF_SRC_K)
              {
                if (class == BPF_INSN_CLASS_JMP32)
                  return format ("%s\t%i32d", mnemonic, imm32);
                else
                  return format ("%s\t%i16d", mnemonic, off16);
              }
            else
              return format ("%s\t%s", mnemonic, regs.dst.as_asm);
          }
        else /* Conditional jumps.  */
          {
            if (src == BPF_SRC_K)
              return format ("%s\t%s, %i32d, %i16d", mnemonic,
                             regs.dst.as_asm, imm32, off16);

            else
              return format ("%s\t%s, %s, %i16d", mnemonic,
                             regs.dst.as_asm, regs.src.as_asm,
                             off16);
          }
      }

    method _print = void:
      { print "#<" + as_asm + ">"; }
  };


/* Common portion of all load/store (LD/LDX/ST/STX class) instructions.
   All load/store instructions have the same layout, but the imm32 field
   is used differently (or not used at all) according to class and mode.

      +----+--+-----+--------+---..---+---....---+
      |mode|sz|class|  regs  | offs16 |  imm32   |
      +----+--+-----+--------+---..---+---....---+
   bits   3   2   3      8       16        32

   This struct encapsulates everything except imm32.  */

type BPF_Insn_Ldst_Header = struct
  {
    BPF_Insn_Ldst_Mode mode;
    BPF_Insn_Data_Size size;
    BPF_Insn_Class     class
      : (class in [BPF_INSN_CLASS_LD, BPF_INSN_CLASS_LDX,
                   BPF_INSN_CLASS_ST, BPF_INSN_CLASS_STX]);

    BPF_Insn_Regs regs;
    int<16> off16;

    method imm64_p = int<32>:
      {
        return (class == BPF_INSN_CLASS_LD
                      && mode == BPF_LDST_MODE_IMM
                      && size == BPF_LDST_SIZE_DW);
      }

    var sizes = ["w", "h", "b", "dw"];

    method size_asm = string:
      { return sizes[size]; }

    method off16_asm = string:
      { return format ("%s%<integer:%i16d%>", off16 >= 0 ? "+" : "", off16); }
  };

/* Atomic instructions: class STX with mode LDST_MODE_ATOMIC.
   Only 32- or 64-bit (SIZE_W, SIZE_DW) operations exist.
   The 32-bit immediate field encodes the operation to perform via an ALU
   operation code.  */

type BPF_Insn_Atomic = struct
  {
    BPF_Insn_Ldst_Header hdr
      : hdr.class == BPF_INSN_CLASS_STX &&
        hdr.mode == BPF_LDST_MODE_ATOMIC &&
        hdr.size in [BPF_LDST_SIZE_W, BPF_LDST_SIZE_DW];

    int<24>;
    uint<4> op : op in [BPF_ALU_OP_ADD, BPF_ALU_OP_OR, BPF_ALU_OP_AND,
                        BPF_ALU_OP_XOR, BPF_ATOMIC_OP_XCHG, BPF_ATOMIC_OP_CMP];
    int<3>;
    uint<1> fetch = (op in [BPF_ATOMIC_OP_XCHG, BPF_ATOMIC_OP_CMP])
                  : (op in [BPF_ATOMIC_OP_XCHG, BPF_ATOMIC_OP_CMP]) => fetch == 1;

    var names = [
      .[0x0] = "add",
      .[0x4] = "or",
      .[0x5] = "and",
      .[0xa] = "xor",
      .[0xe] = "xchg",
      .[0xf] = "cmp",
    ];

    method mnemonic = string:
      {
        var name = names[op];
        var sz = hdr.size == BPF_LDST_SIZE_DW ? "" : "32";
        if (op in [BPF_ATOMIC_OP_XCHG, BPF_ATOMIC_OP_CMP])
          return format ("a%s%s", name, sz);
        else
          return format ("a%s%s%s", fetch ? "f" : "", name, sz);
      }

    method as_asm = string:
      {
        return format ("%s\t[%s%s], %s", mnemonic,
                       hdr.regs.dst.as_asm,
                       hdr.off16_asm, hdr.regs.src.as_asm);
      }

    method _print = void:
      { print "#<" + as_asm + ">"; }
  };

/* LD class instructions.  */

type BPF_Insn_Ld = struct
  {
    BPF_Insn_Ldst_Header hdr
      : hdr.class == BPF_INSN_CLASS_LD &&
        (hdr.mode in [BPF_LDST_MODE_IMM, BPF_LDST_MODE_IND, BPF_LDST_MODE_ABS]) &&
        (hdr.mode == BPF_LDST_MODE_IMM => hdr.size == BPF_LDST_SIZE_DW);

    union
      {
        struct
          {
            uint<32> lo;
            uint<32>;
            uint<32> hi;
          } imm64 : hdr.imm64_p;
        int<32> imm32;
      } imm;

    method set_imm64 = (int<64> val) void:
      {
        imm.imm64.lo = val as uint<32>;
        imm.imm64.hi = val .>> 32;
      }

    method set_imm32 = (int<32> val) void:
      { imm.imm32 = val; }

    method imm_value = int<64>:
      {
        return hdr.imm64_p ? (imm.imm64.hi:::imm.imm64.lo) as int<64>
                           : imm.imm32 as int<64>;
      }

    method as_asm = string:
      {
        if (hdr.mode == BPF_LDST_MODE_ABS)
          return format ("ldabs%s\t%i64d", hdr.size_asm, imm_value);
        else if (hdr.mode == BPF_LDST_MODE_IND)
          return format ("ldind%s\t%s, %i64d", hdr.size_asm,
                         hdr.regs.src.as_asm, imm_value);
        else
          return format ("ld%s\t%s,%i64d", hdr.size_asm,
                         hdr.regs.dst.as_asm, imm_value);
      }

    method _print = void:
      { print "#<" + as_asm + ">"; }
  };

/* LDX class instructions.  */

type BPF_Insn_Ldx = struct
  {
    BPF_Insn_Ldst_Header hdr
      : hdr.class == BPF_INSN_CLASS_LDX &&
        (hdr.mode in [BPF_LDST_MODE_MEM, BPF_LDST_MODE_SMEM]);

    int<32>; /* LDX insns do not use imm32 at all.  */

    method as_asm = string:
      {
        return format ("ldx%s%s\t%s, [%s%s]",
                       hdr.mode == BPF_LDST_MODE_SMEM ? "s" : "",
                       hdr.size_asm, hdr.regs.dst.as_asm,
                       hdr.regs.src.as_asm, hdr.off16_asm);
      }

    method _print = void:
      { print "#<" + as_asm + ">"; }
  };

/* ST class instructions.  */

type BPF_Insn_St = struct
  {
    BPF_Insn_Ldst_Header hdr
      : hdr.class == BPF_INSN_CLASS_ST &&
        /* Currently all CLASS_ST insns are MODE_MEM.  */
        hdr.mode == BPF_LDST_MODE_MEM;

    int<32> imm32;

    method as_asm = string:
      {
        return format ("st%s\t[%s%s], %i32d", hdr.size_asm,
                       hdr.regs.dst.as_asm, hdr.off16_asm, imm32);
      }

    method _print = void:
      { print "#<" + as_asm + ">"; }
  };

/* STX class instructions.  */

type BPF_Insn_Stx = struct
  {
    BPF_Insn_Ldst_Header hdr
      : hdr.class == BPF_INSN_CLASS_STX &&
        hdr.mode == BPF_LDST_MODE_MEM;

    int<32>; /* STX insns (apart from atomics) do not use imm32 at all.  */

    method as_asm = string:
      {
        return format ("stx%s\t[%s%s], %s", hdr.size_asm,
                       hdr.regs.dst.as_asm, hdr.off16_asm, hdr.regs.src.as_asm);
      }

    method _print = void:
      { print "#<" + as_asm + ">"; }
  };

/* LD/LDX/ST/STX class instructions.  */

type BPF_Insn_Ldst = union
  {
    BPF_Insn_Atomic atomic;
    BPF_Insn_Ld     ld;
    BPF_Insn_Ldx    ldx;
    BPF_Insn_St     st;
    BPF_Insn_Stx    stx;

    method imm_value = int<64>:
      {
        return !(ld ?! E_elem) ? ld.imm_value
            :  !(st ?! E_elem) ? st.imm32 as int<64>
            : 0 as int<64> /* atomic, ldx, stx do not have immediates.  */;
      }

    method as_asm = string:
      {
        return !(atomic ?! E_elem) ? atomic.as_asm
            :  !(ld     ?! E_elem) ? ld.as_asm
            :  !(ldx    ?! E_elem) ? ldx.as_asm
            :  !(st     ?! E_elem) ? st.as_asm
            :  !(stx    ?! E_elem) ? stx.as_asm
            : "" /* impossible */;
      }

    method _print = void:
      { print "#<" + as_asm + ">"; }
  };

/* LD/LDX/ST/STX/ALU32/ALU64/JMP32/JMP64 (i.e. all) classes.  */

type BPF_Insn = union
  {
    BPF_Insn_ALU  alu;
    BPF_Insn_Jmp  jmp;
    BPF_Insn_Ldst ldst;

    method imm_value = int<64>:
      {
        return !(alu ?! E_elem) ? alu.imm32 as int<64>
            :  !(jmp ?! E_elem) ? jmp.imm32 as int<64>
            :  ldst.imm_value;
      }

    method as_asm = string:
      {
        return !(alu ?! E_elem) ? alu.as_asm
            :  !(jmp ?! E_elem) ? jmp.as_asm
            :  !(ldst ?! E_elem) ? ldst.as_asm
            :  "unknown";
      }

    method _print = void:
      { print "#<" + as_asm + ">"; }
  };

/* == Utility constructors for instructions ==  */

fun _bpf_regs = (uint<4> dr, uint<4> sr) BPF_Insn_Regs:
  {
    return BPF_Insn_Regs {
      src = BPF_Reg { code = sr },
      dst = BPF_Reg { code = dr }
    };
  }

fun _bpf_alu = (uint<3> class,
                uint<4> code,
                uint<1> src = BPF_SRC_K,
                uint<4> dr = BPF_R0,
                uint<4> sr = BPF_R0,
                uint<16> code_ext = 0,
                int<32> imm32 = 0) BPF_Insn:
  {
    return BPF_Insn {
      alu = BPF_Insn_ALU {
        code = code,
        class = class,
        src = src,
        regs = _bpf_regs (dr, sr),
        code_ext = code_ext,
        imm32 = imm32,
      }
    };
  }

// <op> %dr, imm32
fun bpf_alu64i = (uint<4> code,
                  uint<4> dr = BPF_R0,
                  int<32> imm32 = 0,
                  uint<16> code_ext = 0) BPF_Insn:
  {
    return _bpf_alu (BPF_INSN_CLASS_ALU64, code, BPF_SRC_K,
                     dr, BPF_R0, code_ext, imm32);
  }

// <op>32 %dr, imm32
fun bpf_alu32i = (uint<4> code,
                  uint<4> dr = BPF_R0,
                  int<32> imm32 = 0,
                  uint<16> code_ext = 0) BPF_Insn:
  {
    return _bpf_alu (BPF_INSN_CLASS_ALU32, code, BPF_SRC_K,
                     dr, BPF_R0 /* unused */, code_ext, imm32);
  }

// <op> %dr, %sr
fun bpf_alu64r = (uint<4> code,
                  uint<4> dr = BPF_R0,
                  uint<4> sr = BPF_R0,
                  uint<16> code_ext = 0) BPF_Insn:
  {
    return _bpf_alu (BPF_INSN_CLASS_ALU64, code, BPF_SRC_X,
                     dr, sr, code_ext, 0 /* unused */);
  }

// <op>32 %dr, %sr
fun bpf_alu32r = (uint<4> code,
                  uint<4> dr = BPF_R0,
                  uint<4> sr = BPF_R0,
                  uint<16> code_ext = 0) BPF_Insn:
  {
    return _bpf_alu (BPF_INSN_CLASS_ALU32, code, BPF_SRC_X,
                     dr, sr, code_ext, 0 /* unused */);
  }

fun _bpf_jmp = (uint<3> class,
                uint<4> code,
                uint<1> src = BPF_SRC_K,
                uint<4> dr = BPF_R0,
                uint<4> sr = BPF_R0,
                int<16> off16 = 0,
                int<32> imm32 = 0) BPF_Insn:
  {
    return BPF_Insn {
      jmp = BPF_Insn_Jmp {
        code = code,
        class = class,
        src = src,
        regs = _bpf_regs (dr, sr),
        off16 = off16,
        imm32 = imm32,
      }
    };
  }

// j<cmp> %dr, imm32, off16
fun bpf_jmp64i = (uint<4> code,
                  uint<4> dr,
                  int<32> imm32,
                  int<16> off16) BPF_Insn:
  {
    return _bpf_jmp (BPF_INSN_CLASS_JMP64, code, BPF_SRC_K,
                     dr, BPF_R0 /* unused */, off16, imm32);
  }

// ja off16
fun bpf_ja = (int<16> off16) BPF_Insn:
  { return bpf_jmp64i (BPF_JMP_CODE_JA, 0, 0, off16); }

// call val
fun bpf_call = (int<32> val) BPF_Insn:
  { return bpf_jmp64i (BPF_JMP_CODE_CALL, 0, val, 0); }

// exit
fun bpf_exit = BPF_Insn:
  { return bpf_jmp64i (BPF_JMP_CODE_EXIT, 0, 0, 0); }

// j<cmp> %dr, %sr, off16
fun bpf_jmp64r = (uint<4> code,
                  uint<4> dr,
                  uint<4> sr,
                  int<16> off16) BPF_Insn:
  {
    return _bpf_jmp (BPF_INSN_CLASS_JMP64, code, BPF_SRC_X,
                     dr, sr, off16);
  }

// callr %dr
fun bpf_callr = (uint<4> dr) BPF_Insn:
  { return bpf_jmp64r (BPF_JMP_CODE_CALL, dr, 0, 0); }

// j<cmp>32 %dr, imm32, off16
fun bpf_jmp32i = (uint<4> code,
                  uint<4> dr,
                  int<32> imm32,
                  int<16> off16) BPF_Insn:
  {
    return _bpf_jmp (BPF_INSN_CLASS_JMP32, code, BPF_SRC_K,
                     dr, BPF_R0 /* unused */, off16, imm32);
  }

// jal off32
fun bpf_jal = (int<32> off32) BPF_Insn:
  { return bpf_jmp32i (BPF_JMP_CODE_JA, 0, off32, 0); }

// j<cmp>32 %dr, %sr, off16
fun bpf_jmp32r = (uint<4> code,
                  uint<4> dr,
                  uint<4> sr,
                  int<16> off16) BPF_Insn:
  {
    return _bpf_jmp (BPF_INSN_CLASS_JMP64, code, BPF_SRC_X,
                     dr, sr, off16);
  }

fun _bpf_ldst_hdr = (uint<3> class, uint<3> mode, uint<2> size,
                     uint<4> dr, uint<4> sr, int<16> off16) BPF_Insn_Ldst_Header:
  {
    return BPF_Insn_Ldst_Header {
      mode = mode,
      size = size,
      class = class,
      regs = _bpf_regs (dr, sr),
      off16 = off16
    };
  }

/* a<f?><op><32?> [%dr + off16], %sr
   Atomically do: memory value at (%sr + off16) op= value in %sr.  */
fun bpf_atomic = (uint<4> op,
                  uint<2> size,
                  uint<4> dr,
                  int<16> off16,
                  uint<4> sr,
                  uint<1> fetch = 0) BPF_Insn:
  {
    return BPF_Insn {
      ldst = BPF_Insn_Ldst {
        atomic = BPF_Insn_Atomic {
          hdr = _bpf_ldst_hdr (BPF_INSN_CLASS_STX, BPF_LDST_MODE_ATOMIC, size,
                               dr, sr, off16),
          op = op,
          fetch = fetch,
        }
      }
    };
  }

/* The only non-deprecated CLASS_LD insn is lddw:
     lddw %dr, imm64
   64-bit immediate load register.
   Note that this instruction is 128 bits!  */
fun bpf_lddw = (uint<4> dr, int<64> imm64) BPF_Insn:
  {
    var i = BPF_Insn {
      ldst = BPF_Insn_Ldst {
        ld = BPF_Insn_Ld {
          hdr = _bpf_ldst_hdr (BPF_INSN_CLASS_LD, BPF_LDST_MODE_IMM,
                               BPF_LDST_SIZE_DW, dr,
                               /* both unused */ BPF_R0, 0),
        },
      }
    };

    i.ldst.ld.set_imm64 (imm64);
    return i;
  }


/* ldx<s?><size> %dr, [%sr + off16]
   Load value in memory at %sr + off16 into %dr.  */
fun bpf_ldx = (uint<2> size, int<32> signed, uint<4> dr, uint<4> sr,
               int<16> off16) BPF_Insn:
  {
    return BPF_Insn {
      ldst = BPF_Insn_Ldst {
        ldx = BPF_Insn_Ldx {
          hdr = _bpf_ldst_hdr (
            BPF_INSN_CLASS_LDX,
            signed ? BPF_LDST_MODE_SMEM : BPF_LDST_MODE_MEM,
            size, dr, sr, off16)
        }
      }
    };
  }

/* st<size> [%dr + off16], imm32
   Store immediate into memory at %dr + off16.  */
fun bpf_st = (uint<2> size, uint<4> dr, int<16> off16, int<32> imm32) BPF_Insn:
  {
    return BPF_Insn {
      ldst = BPF_Insn_Ldst {
        st = BPF_Insn_St {
          hdr = _bpf_ldst_hdr (BPF_INSN_CLASS_ST, BPF_LDST_MODE_MEM, size, dr,
                               BPF_R0 /* unused */, off16),
          imm32 = imm32,
        }
      }
    };
  }

/* stx<size> [%dr + off16], %sr
   Store value in register %sr into memory at %dr + off16.  */
fun bpf_stx = (uint<2> size, uint<4> dr, int<16> off16, uint<4> sr) BPF_Insn:
  {
    return BPF_Insn {
      ldst = BPF_Insn_Ldst {
        stx = BPF_Insn_Stx {
          hdr = _bpf_ldst_hdr (BPF_INSN_CLASS_STX, BPF_LDST_MODE_MEM, size, dr,
                               sr, off16),
        }
      }
    };
  }
