;; Machine description for eBPF.
;; Copyright (C) 2019-2021 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")

;;;; Unspecs

(define_c_enum "unspec" [
  UNSPEC_LDINDABS
  UNSPEC_XADD
])

;;;; 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 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.
;; xadd		atomic exchange-and-add instructions.
;; multi	multiword sequence (or user asm statements).

(define_attr "type"
  "unknown,alu,alu32,end,ld,lddw,ldx,st,stx,jmp,xadd,multi"
  (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 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"
  [(set_attr "type" "alu")])

;;;; 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.

(define_mode_iterator AM [SI 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"
  [(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"
  [(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"
  [(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"
  [(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"
  [(set_attr "type" "alu32")])

;;; Division

;; Note that eBPF 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"
  [(set_attr "type" "<mtype>")])

;; However, xBPF 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")))]
  "TARGET_XBPF"
  "sdiv<msuffix>\t%0,%2"
  [(set_attr "type" "<mtype>")])

;;; Modulus

;; Note that eBPF 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"
  [(set_attr "type" "<mtype>")])

;; Again, xBPF provides a signed version, 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")))]
  "TARGET_XBPF"
  "smod<msuffix>\t%0,%2"
  [(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"
  [(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"
  [(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"
  [(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")
	(zero_extend:DI (match_operand:HI 1 "nonimmediate_operand" "r,m")))]
  ""
  "@
   and\t%0,0xffff
   ldxh\t%0,%1"
  [(set_attr "type" "alu,ldx")])

(define_insn "zero_extendqidi2"
  [(set (match_operand:DI 0 "register_operand" "=r,r")
	(zero_extend:DI (match_operand:QI 1 "nonimmediate_operand" "r,m")))]
  ""
  "@
   and\t%0,0xff
   ldxb\t%0,%1"
  [(set_attr "type" "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,m")))]
  ""
  "@
   mov32\t%0,%1
   ldxw\t%0,%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;
})

;;;; 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,m,m")
        (match_operand:MM 1 "mov_src_operand"      " m,rI,B,r,I"))]
  ""
  "@
   ldx<mop>\t%0,%1
   mov\t%0,%1
   lddw\t%0,%1
   stx<mop>\t%0,%1
   st<mop>\t%0,%1"
[(set_attr "type" "ldx,alu,alu,stx,st")])

;;;; Shifts

(define_mode_iterator SIM [SI 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"
  [(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"
  [(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"
  [(set_attr "type" "<mtype>")])

;;;; Conditional branches

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

(define_expand "cbranchdi4"
  [(set (pc)
	(if_then_else (match_operator 0 "comparison_operator"
			[(match_operand:DI 1 "register_operand")
			 (match_operand:DI 2 "reg_or_imm_operand")])
		      (label_ref (match_operand 3 "" ""))
		      (pc)))]
  ""
{
  if (!ordered_comparison_operator (operands[0], VOIDmode))
    FAIL;
})

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

  switch (code)
  {
  case EQ: return "jeq\t%0,%1,%2"; break;
  case NE: return "jne\t%0,%1,%2"; break;
  case LT: return "jslt\t%0,%1,%2"; break;
  case LE: return "jsle\t%0,%1,%2"; break;
  case GT: return "jsgt\t%0,%1,%2"; break;
  case GE: return "jsge\t%0,%1,%2"; break;
  case LTU: return "jlt\t%0,%1,%2"; break;
  case LEU: return "jle\t%0,%1,%2"; break;
  case GTU: return "jgt\t%0,%1,%2"; break;
  case GEU: return "jge\t%0,%1,%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"
[(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"
  [(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_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"
  [(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"
  [(set_attr "type" "ld")])

;;;; Atomic increments

(define_mode_iterator AMO [SI DI])

(define_insn "atomic_add<AMO:mode>"
  [(set (match_operand:AMO 0 "memory_operand" "+m")
        (unspec_volatile:AMO
         [(plus:AMO (match_dup 0)
                    (match_operand:AMO 1 "register_operand" "r"))
          (match_operand:SI 2 "const_int_operand")] ;; Memory model.
         UNSPEC_XADD))]
  ""
  "xadd<mop>\t%0,%1"
  [(set_attr "type" "xadd")])
