;; Machine description for eBPF.
;; Copyright (C) 2019-2024 Free Software Foundation, Inc.

;; This file is part of GCC.

;; GCC 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, or (at your option)
;; any later version.

;; GCC 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 GCC; see the file COPYING3.  If not see
;; <http://www.gnu.org/licenses/>.

(include "predicates.md")
(include "constraints.md")

;;;; Instruction Scheduler FSM

;; This is just to get INSN_SCHEDULING defined, so that combine does
;; not make paradoxical subregs of memory.  These subregs seems to
;; confuse LRA that ends generating wrong instructions.

(define_automaton "frob")
(define_cpu_unit "frob_unit" "frob")
(define_insn_reservation "frobnicator" 814
  (const_int 0) "frob_unit")

;;;; Unspecs

(define_c_enum "unspec" [
  UNSPEC_LDINDABS
  UNSPEC_AADD
  UNSPEC_AAND
  UNSPEC_AOR
  UNSPEC_AXOR
  UNSPEC_AFADD
  UNSPEC_AFAND
  UNSPEC_AFOR
  UNSPEC_AFXOR
  UNSPEC_AXCHG
  UNSPEC_ACMP
  UNSPEC_CORE_RELOC
])

;;;; Constants

(define_constants
  [(R0_REGNUM		0)
   (R1_REGNUM		1)
   (R2_REGNUM		2)
   (R3_REGNUM		3)
   (R4_REGNUM		4)
   (R5_REGNUM		5)
   (R6_REGNUM		6)
   (R7_REGNUM		7)
   (R8_REGNUM		8)
   (R9_REGNUM		9)
   (R10_REGNUM		10)
   (R11_REGNUM		11)
])

;;;; Attributes

;; Instruction classes.
;; alu		64-bit arithmetic.
;; alu32	32-bit arithmetic.
;; end		endianness conversion or byte swap instructions.
;; ld		load instructions.
;; lddx		load 64-bit immediate instruction.
;; ldx		generic load instructions.
;; st		generic store instructions for immediates.
;; stx		generic store instructions.
;; jmp		jump instructions.
;; multi	multiword sequence (or user asm statements).

(define_attr "type"
  "unknown,alu,alu32,end,ld,lddw,ldx,st,stx,jmp,multi,atomic"
  (const_string "unknown"))

;; Length of instruction in bytes.
(define_attr "length" ""
  (cond [
         (eq_attr "type" "lddw") (const_int 16)
         ] (const_int 8)))

;; Describe a user's asm statement.
(define_asm_attributes
  [(set_attr "type" "multi")])

;;;; Mode attributes and iterators

(define_mode_attr mop [(QI "b") (HI "h") (SI "w") (DI "dw")
                       (SF "w") (DF "dw")])
(define_mode_attr smop [(QI "u8") (HI "u16") (SI "u32") (DI "u64")
                       (SF "u32") (DF "u64")])
(define_mode_attr mtype [(SI "alu32") (DI "alu")])
(define_mode_attr msuffix [(SI "32") (DI "")])

;;;; NOPs

;; The Linux kernel verifier performs some optimizations that rely on
;; nop instructions to be encoded as `ja 0', i.e. a jump to offset 0,
;; which actually means to jump to the next instruction, since in BPF
;; offsets are expressed in 64-bit words _minus one_.

(define_insn "nop"
  [(const_int 0)]
  ""
  "{ja\t0|goto 0}"
  [(set_attr "type" "alu")])

;;;; Stack usage

(define_expand "allocate_stack"
  [(match_operand:DI 0 "general_operand" "")
   (match_operand:DI 1 "general_operand" "")]
  ""
  "
{
  error (\"BPF does not support dynamic stack allocation\");
  emit_insn (gen_nop ());
  DONE;
}")

;;;; Arithmetic/Logical

;; The arithmetic and logic operations below are defined for SI and DI
;; modes.  The mode iterator AM is used in order to expand to two
;; insns, with the proper modes.
;;
;; 32-bit arithmetic (for SI modes) is implemented using the alu32
;; instructions, if available.

(define_mode_iterator AM [(SI "bpf_has_alu32") DI])

;;; Addition
(define_insn "add<AM:mode>3"
  [(set (match_operand:AM          0 "register_operand"   "=r,r")
        (plus:AM (match_operand:AM 1 "register_operand"   " 0,0")
                 (match_operand:AM 2 "reg_or_imm_operand" " r,I")))]
  "1"
  "{add<msuffix>\t%0,%2|%w0 += %w2}"
  [(set_attr "type" "<mtype>")])

;;; Subtraction

;; Note that subtractions of constants become additions, so there is
;; no need to handle immediate operands in the subMODE3 insns.

(define_insn "sub<AM:mode>3"
  [(set (match_operand:AM          0 "register_operand" "=r")
        (minus:AM (match_operand:AM 1 "register_operand" " 0")
                  (match_operand:AM 2 "register_operand" " r")))]
  ""
  "{sub<msuffix>\t%0,%2|%w0 -= %w2}"
  [(set_attr "type" "<mtype>")])

;;; Negation
(define_insn "neg<AM:mode>2"
  [(set (match_operand:AM         0 "register_operand" "=r")
        (neg:AM (match_operand:AM 1 "register_operand" " 0")))]
  ""
  "{neg<msuffix>\t%0|%w0 = -%w1}"
  [(set_attr "type" "<mtype>")])

;;; Multiplication
(define_insn "mul<AM:mode>3"
  [(set (match_operand:AM          0 "register_operand"   "=r,r")
        (mult:AM (match_operand:AM 1 "register_operand"   " 0,0")
                 (match_operand:AM 2 "reg_or_imm_operand" " r,I")))]
  ""
  "{mul<msuffix>\t%0,%2|%w0 *= %w2}"
  [(set_attr "type" "<mtype>")])

(define_insn "*mulsidi3_zeroextend"
  [(set (match_operand:DI	   0 "register_operand" "=r,r")
        (zero_extend:DI
         (mult:SI (match_operand:SI 1 "register_operand" "0,0")
                  (match_operand:SI 2 "reg_or_imm_operand" "r,I"))))]
  ""
  "{mul32\t%0,%2|%W0 *= %W2}"
  [(set_attr "type" "alu32")])

;;; Division

;; Note that eBPF <= V3 doesn't provide instructions for signed
;; integer division.

(define_insn "udiv<AM:mode>3"
  [(set (match_operand:AM 0 "register_operand" "=r,r")
        (udiv:AM (match_operand:AM 1 "register_operand" " 0,0")
                 (match_operand:AM 2 "reg_or_imm_operand" "r,I")))]
  ""
  "{div<msuffix>\t%0,%2|%w0 /= %w2}"
  [(set_attr "type" "<mtype>")])

;; However, BPF V4 does provide a signed division operator, sdiv.

(define_insn "div<AM:mode>3"
  [(set (match_operand:AM 0 "register_operand" "=r,r")
        (div:AM (match_operand:AM 1 "register_operand" " 0,0")
                (match_operand:AM 2 "reg_or_imm_operand" "r,I")))]
  "bpf_has_sdiv"
  "{sdiv<msuffix>\t%0,%2|%w0 s/= %w2}"
  [(set_attr "type" "<mtype>")])

;;; Modulus

;; Note that eBPF <= V3 doesn't provide instructions for signed
;; integer remainder.

(define_insn "umod<AM:mode>3"
  [(set (match_operand:AM 0 "register_operand" "=r,r")
        (umod:AM (match_operand:AM 1 "register_operand" " 0,0")
                 (match_operand:AM 2 "reg_or_imm_operand" "r,I")))]
  ""
  "{mod<msuffix>\t%0,%2|%w0 %%= %w2}"
  [(set_attr "type" "<mtype>")])

;; However, BPF V4 does provide a signed modulus operator, smod.

(define_insn "mod<AM:mode>3"
  [(set (match_operand:AM 0 "register_operand" "=r,r")
        (mod:AM (match_operand:AM 1 "register_operand" " 0,0")
                (match_operand:AM 2 "reg_or_imm_operand" "r,I")))]
  "bpf_has_sdiv"
  "{smod<msuffix>\t%0,%2|%w0 s%%= %w2}"
  [(set_attr "type" "<mtype>")])

;;; Logical AND
(define_insn "and<AM:mode>3"
  [(set (match_operand:AM 0 "register_operand" "=r,r")
        (and:AM (match_operand:AM 1 "register_operand" " 0,0")
                (match_operand:AM 2 "reg_or_imm_operand" "r,I")))]
  ""
  "{and<msuffix>\t%0,%2|%w0 &= %w2}"
  [(set_attr "type" "<mtype>")])

;;; Logical inclusive-OR
(define_insn "ior<AM:mode>3"
  [(set (match_operand:AM 0 "register_operand" "=r,r")
        (ior:AM (match_operand:AM 1 "register_operand" " 0,0")
                (match_operand:AM 2 "reg_or_imm_operand" "r,I")))]
  ""
  "{or<msuffix>\t%0,%2|%w0 %|= %w2}"
  [(set_attr "type" "<mtype>")])

;;; Logical exclusive-OR
(define_insn "xor<AM:mode>3"
  [(set (match_operand:AM 0 "register_operand" "=r,r")
        (xor:AM (match_operand:AM 1 "register_operand" " 0,0")
                (match_operand:AM 2 "reg_or_imm_operand" "r,I")))]
  ""
  "{xor<msuffix>\t%0,%2|%w0 ^= %w2}"
  [(set_attr "type" "<mtype>")])

;;;; Conversions

;;; Zero-extensions

;; For register operands smaller than 32-bit zero-extending is
;; achieved ANDing the value in the source register to a suitable
;; mask.
;;
;; For register operands bigger or equal than 32-bit, we generate a
;; mov32 instruction to zero the high 32-bits of the destination
;; register.
;;
;; For memory operands, of any width, zero-extending is achieved using
;; the ldx{bhwdw} instructions to load the values in registers.

(define_insn "zero_extendhidi2"
  [(set (match_operand:DI 0 "register_operand" "=r,r,r")
	(zero_extend:DI (match_operand:HI 1 "nonimmediate_operand" "0,r,q")))]
  ""
  "@
   {and\t%0,0xffff|%0 &= 0xffff}
   *return bpf_output_move (operands, \"{mov\t%0,%1\;and\t%0,0xffff|%0 = %1;%0 &= 0xffff}\");
   *return bpf_output_move (operands, \"{ldxh\t%0,%1|%0 = *(u16 *) (%1)}\");"
  [(set_attr "type" "alu,alu,ldx")])

(define_insn "zero_extendqidi2"
  [(set (match_operand:DI 0 "register_operand" "=r,r,r")
	(zero_extend:DI (match_operand:QI 1 "nonimmediate_operand" "0,r,q")))]
  ""
  "@
   {and\t%0,0xff|%0 &= 0xff}
   *return bpf_output_move (operands, \"{mov\t%0,%1\;and\t%0,0xff|%0 = %1;%0 &= 0xff}\");
   *return bpf_output_move (operands, \"{ldxb\t%0,%1|%0 = *(u8 *) (%1)}\");"
  [(set_attr "type" "alu,alu,ldx")])

(define_insn "zero_extendsidi2"
  [(set (match_operand:DI 0 "register_operand" "=r,r")
	(zero_extend:DI
	  (match_operand:SI 1 "nonimmediate_operand" "r,q")))]
  ""
  "@
   *return bpf_output_move (operands, bpf_has_alu32 ? \"{mov32\t%0,%1|%0 = %1}\" : \"{mov\t%0,%1\;and\t%0,0xffffffff|%0 = %1;%0 &= 0xffffffff}\");
   *return bpf_output_move (operands, \"{ldxw\t%0,%1|%0 = *(u32 *) (%1)}\");"
  [(set_attr "type" "alu,ldx")])

;;; Sign-extension

;; Sign-extending a 32-bit value into a 64-bit value is achieved using
;; shifting, with instructions generated by the expand below.

(define_expand "extendsidi2"
  [(set (match_operand:DI 0 "register_operand")
	(sign_extend:DI (match_operand:SI 1 "register_operand")))]
  ""
{
  operands[1] = gen_lowpart (DImode, operands[1]);
  emit_insn (gen_ashldi3 (operands[0], operands[1], GEN_INT (32)));
  emit_insn (gen_ashrdi3 (operands[0], operands[0], GEN_INT (32)));
  DONE;
})

;; ISA V4 introduces sign-extending move and load operations.

(define_insn "*extendsidi2"
  [(set (match_operand:DI 0 "register_operand" "=r,r")
        (sign_extend:DI (match_operand:SI 1 "nonimmediate_operand" "r,q")))]
  "bpf_has_smov"
  "@
   *return bpf_output_move (operands, \"{movs\t%0,%1,32|%0 = (s32) %1}\");
   *return bpf_output_move (operands, \"{ldxsw\t%0,%1|%0 = *(s32 *) (%1)}\");"
  [(set_attr "type" "alu,ldx")])

(define_insn "extendhidi2"
  [(set (match_operand:DI 0 "register_operand" "=r,r")
        (sign_extend:DI (match_operand:HI 1 "nonimmediate_operand" "r,q")))]
  "bpf_has_smov"
  "@
   *return bpf_output_move (operands, \"{movs\t%0,%1,16|%0 = (s16) %1}\");
   *return bpf_output_move (operands, \"{ldxsh\t%0,%1|%0 = *(s16 *) (%1)}\");"
  [(set_attr "type" "alu,ldx")])

(define_insn "extendqidi2"
  [(set (match_operand:DI 0 "register_operand" "=r,r")
        (sign_extend:DI (match_operand:QI 1 "nonimmediate_operand" "r,q")))]
  "bpf_has_smov"
  "@
   *return bpf_output_move (operands, \"{movs\t%0,%1,8|%0 = (s8) %1}\");
   *return bpf_output_move (operands, \"{ldxsb\t%0,%1|%0 = *(s8 *) (%1)}\");"
  [(set_attr "type" "alu,ldx")])

(define_insn "extendhisi2"
  [(set (match_operand:SI 0 "register_operand" "=r")
        (sign_extend:SI (match_operand:HI 1 "register_operand" "r")))]
  "bpf_has_smov"
  "*return bpf_output_move (operands, \"{movs32\t%0,%1,16|%w0 = (s16) %w1}\");"
  [(set_attr "type" "alu")])

(define_insn "extendqisi2"
  [(set (match_operand:SI 0 "register_operand" "=r")
        (sign_extend:SI (match_operand:QI 1 "register_operand" "r")))]
  "bpf_has_smov"
  "*return bpf_output_move (operands, \"{movs32\t%0,%1,8|%w0 = (s8) %w1}\");"
  [(set_attr "type" "alu")])

;;;; Data movement

(define_mode_iterator MM [QI HI SI DI SF DF])

(define_expand "mov<MM:mode>"
  [(set (match_operand:MM 0 "general_operand")
        (match_operand:MM 1 "general_operand"))]
        ""
        "
{
  if (!register_operand(operands[0], <MM:MODE>mode)
      && !register_operand(operands[1], <MM:MODE>mode))
    operands[1] = force_reg (<MM:MODE>mode, operands[1]);
}")

(define_insn "*mov<MM:mode>"
  [(set (match_operand:MM 0 "nonimmediate_operand" "=r,  r, r,q,q")
        (match_operand:MM 1 "mov_src_operand"      " q,rIc,BC,r,I"))]
  ""
  "@
   *return bpf_output_move (operands, \"{ldx<mop>\t%0,%1|%0 = *(<smop> *) (%1)}\");
   *return bpf_output_move (operands, \"{mov\t%0,%1|%0 = %1}\");
   *return bpf_output_move (operands, \"{lddw\t%0,%1|%0 = %1 ll}\");
   *return bpf_output_move (operands, \"{stx<mop>\t%0,%1|*(<smop> *) (%0) = %1}\");
   *return bpf_output_move (operands, \"{st<mop>\t%0,%1|*(<smop> *) (%0) = %1}\");"
[(set_attr "type" "ldx,alu,alu,stx,st")])

;;;; Shifts

(define_mode_iterator SIM [(SI "bpf_has_alu32") DI])

(define_insn "ashr<SIM:mode>3"
  [(set (match_operand:SIM 0 "register_operand"                 "=r,r")
        (ashiftrt:SIM (match_operand:SIM 1 "register_operand"   " 0,0")
                      (match_operand:SIM 2 "reg_or_imm_operand" " r,I")))]
  ""
  "{arsh<msuffix>\t%0,%2|%w0 s>>= %w2}"
  [(set_attr "type" "<mtype>")])

(define_insn "ashl<SIM:mode>3"
  [(set (match_operand:SIM 0 "register_operand"               "=r,r")
        (ashift:SIM (match_operand:SIM 1 "register_operand"   " 0,0")
                    (match_operand:SIM 2 "reg_or_imm_operand" " r,I")))]
  ""
  "{lsh<msuffix>\t%0,%2|%w0 <<= %w2}"
  [(set_attr "type" "<mtype>")])

(define_insn "lshr<SIM:mode>3"
  [(set (match_operand:SIM 0 "register_operand"                 "=r,r")
        (lshiftrt:SIM (match_operand:SIM 1 "register_operand"   " 0,0")
                      (match_operand:SIM 2 "reg_or_imm_operand" " r,I")))]
  ""
  "{rsh<msuffix>\t%0,%2|%w0 >>= %w2}"
  [(set_attr "type" "<mtype>")])

;;;; Byte swapping

(define_mode_iterator BSM [HI SI DI])
(define_mode_attr endmode [(HI "16") (SI "32") (DI "64")])

(define_insn "bswap<BSM:mode>2"
  [(set (match_operand:BSM 0 "register_operand"            "=r")
        (bswap:BSM (match_operand:BSM 1 "register_operand" " 0")))]
  ""
{
  if (bpf_has_bswap)
    return "{bswap\t%0, <endmode>|%0 = bswap<endmode> %1}";
  else
    {
      if (TARGET_BIG_ENDIAN)
        return "{endle\t%0, <endmode>|%0 = le<endmode> %1}";
      else
        return "{endbe\t%0, <endmode>|%0 = be<endmode> %1}";
    }
}
  [(set_attr "type" "end")])

;;;; Conditional branches

;; The eBPF jump instructions use 64-bit arithmetic when evaluating
;; the jump conditions.  Therefore we use DI modes below.

(define_mode_iterator JM [(SI "bpf_has_jmp32") DI])

(define_expand "cbranch<JM:mode>4"
  [(set (pc)
	(if_then_else (match_operator 0 "comparison_operator"
			[(match_operand:JM 1 "register_operand")
			 (match_operand:JM 2 "reg_or_imm_operand")])
		      (label_ref (match_operand 3 "" ""))
		      (pc)))]
  ""
{
  if (!ordered_comparison_operator (operands[0], VOIDmode))
    FAIL;

  bpf_expand_cbranch (<JM:MODE>mode, operands);
})

(define_insn "*branch_on_<JM:mode>"
  [(set (pc)
	(if_then_else (match_operator 3 "ordered_comparison_operator"
			 [(match_operand:JM 0 "register_operand" "r")
			  (match_operand:JM 1 "reg_or_imm_operand" "rI")])
		      (label_ref (match_operand 2 "" ""))
		      (pc)))]
  ""
{
  int code = GET_CODE (operands[3]);

  switch (code)
  {
  case EQ: return  "{jeq<msuffix>\t%0,%1,%2|if %w0 == %w1 goto %2}"; break;
  case NE: return  "{jne<msuffix>\t%0,%1,%2|if %w0 != %w1 goto %2}"; break;
  case LT: return  "{jslt<msuffix>\t%0,%1,%2|if %w0 s< %w1 goto %2}"; break;
  case LE: return  "{jsle<msuffix>\t%0,%1,%2|if %w0 s<= %w1 goto %2}"; break;
  case GT: return  "{jsgt<msuffix>\t%0,%1,%2|if %w0 s> %w1 goto %2}"; break;
  case GE: return  "{jsge<msuffix>\t%0,%1,%2|if %w0 s>= %w1 goto %2}"; break;
  case LTU: return "{jlt<msuffix>\t%0,%1,%2|if %w0 < %w1 goto %2}"; break;
  case LEU: return "{jle<msuffix>\t%0,%1,%2|if %w0 <= %w1 goto %2}"; break;
  case GTU: return "{jgt<msuffix>\t%0,%1,%2|if %w0 > %w1 goto %2}"; break;
  case GEU: return "{jge<msuffix>\t%0,%1,%2|if %w0 >= %w1 goto %2}"; break;
  default:
    gcc_unreachable ();
    return "";
  }
}
  [(set_attr "type" "jmp")])

;;;; Unconditional branches

(define_insn "jump"
  [(set (pc)
        (label_ref (match_operand 0 "" "")))]
  ""
  "{ja\t%0|goto %0}"
[(set_attr "type" "jmp")])

;;;; Function prologue/epilogue

(define_insn "exit"
  [(simple_return)]
  ""
  "exit"
  [(set_attr "type" "jmp")])

(define_expand "prologue"
  [(const_int 0)]
  ""
{
  bpf_expand_prologue ();
  DONE;
})

(define_expand "epilogue"
  [(const_int 0)]
  ""
{
  bpf_expand_epilogue ();
  DONE;
})

;;;; Function calls

(define_expand "call"
  [(parallel [(call (match_operand 0 "")
		    (match_operand 1 ""))
	      (use (match_operand 2 ""))	;; next_arg_reg
	      (use (match_operand 3 ""))])]	;; struct_value_size_rtx
  ""
{
  rtx target = XEXP (operands[0], 0);
  emit_call_insn (gen_call_internal (target, operands[1]));
  DONE;
})

(define_insn "call_internal"
  [(call (mem:DI (match_operand:DI 0 "call_operand" "Sr"))
         (match_operand:SI 1 "general_operand" ""))]
  ;; operands[2] is next_arg_register
  ;; operands[3] is struct_value_size_rtx.
  ""
  { return bpf_output_call (operands[0]); }
  [(set_attr "type" "jmp")])

(define_expand "call_value"
  [(parallel [(set (match_operand 0 "")
		   (call (match_operand 1 "")
			 (match_operand 2 "")))
	      (use (match_operand 3 ""))])]		;; next_arg_reg
  ""
{
  rtx target = XEXP (operands[1], 0);
  emit_call_insn (gen_call_value_internal (operands[0], target,
                                           operands[2]));
  DONE;
})

(define_insn "call_value_internal"
  [(set (match_operand 0 "register_operand" "")
	(call (mem:DI (match_operand:DI 1 "call_operand" "Sr"))
	      (match_operand:SI 2 "general_operand" "")))]
  ;; operands[3] is next_arg_register
  ;; operands[4] is struct_value_size_rtx.
  ""
  { return bpf_output_call (operands[1]); }
  [(set_attr "type" "jmp")])

(define_insn "sibcall"
  [(call (label_ref (match_operand 0 "" ""))
	 (match_operand:SI 1 "general_operand" ""))]
  ;; operands[2] is next_arg_register
  ;; operands[3] is struct_value_size_rtx.
  ""
  "{ja\t%0|goto %0}"
  [(set_attr "type" "jmp")])

;;;; Non-generic load instructions

(define_mode_iterator LDM [QI HI SI DI])
(define_mode_attr ldop [(QI "b") (HI "h") (SI "w") (DI "dw")])
(define_mode_attr pldop [(QI "u8") (HI "u16") (SI "u32") (DI "u64")])

(define_insn "ldind<ldop>"
  [(set (reg:LDM R0_REGNUM)
        (unspec:LDM [(match_operand:DI 0 "register_operand" "r")
                    (match_operand:SI 1 "imm32_operand" "I")]
                    UNSPEC_LDINDABS))
   (clobber (reg:DI R1_REGNUM))
   (clobber (reg:DI R2_REGNUM))
   (clobber (reg:DI R3_REGNUM))
   (clobber (reg:DI R4_REGNUM))]
  ""
  "{ldind<ldop>\t%0,%1|r0 = *(<pldop> *) skb[%0 + %1]}"
  [(set_attr "type" "ld")])

(define_insn "ldabs<ldop>"
  [(set (reg:LDM R0_REGNUM)
        (unspec:LDM [(match_operand:SI 0 "imm32_operand" "I")
                    (match_operand:SI 1 "imm32_operand" "I")]
                    UNSPEC_LDINDABS))
   (clobber (reg:DI R1_REGNUM))
   (clobber (reg:DI R2_REGNUM))
   (clobber (reg:DI R3_REGNUM))
   (clobber (reg:DI R4_REGNUM))]
  ""
  "{ldabs<ldop>\t%0|r0 = *(<pldop> *) skb[%0]}"
  [(set_attr "type" "ld")])

;;; memmove and memcopy

;; 0 is dst
;; 1 is src
;; 2 is size of copy in bytes
;; 3 is alignment

(define_expand "cpymemdi"
  [(match_operand:BLK 0 "memory_operand")
   (match_operand:BLK 1 "memory_operand")
   (match_operand:DI 2 "general_operand")
   (match_operand:DI 3 "immediate_operand")]
   ""
{
  if (bpf_expand_cpymem (operands, false))
    DONE;
  FAIL;
})

;; 0 is dst
;; 1 is src
;; 2 is size of copy in bytes
;; 3 is alignment

(define_expand "movmemdi"
  [(match_operand:BLK 0 "memory_operand")
   (match_operand:BLK 1 "memory_operand")
   (match_operand:DI 2 "general_operand")
   (match_operand:DI 3 "immediate_operand")]
   ""
{
  if (bpf_expand_cpymem (operands, true))
    DONE;
  FAIL;
})

;; memset
;; 0 is dst
;; 1 is length
;; 2 is value
;; 3 is alignment
(define_expand "setmemdi"
  [(set (match_operand:BLK 0 "memory_operand")
	(match_operand:QI  2 "nonmemory_operand"))
   (use (match_operand:DI  1 "general_operand"))
   (match_operand 3 "immediate_operand")]
 ""
 {
  if (bpf_expand_setmem (operands))
    DONE;
  FAIL;
})

(include "atomic.md")
