;; 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, if available.

(define_mode_iterator AM [(SI “bpf_has_alu32”) DI])

;;; Addition (define_insn “addAM:mode3” [(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\t%0,%2” [(set_attr “type” “”)])

;;; Subtraction

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

(define_insn “subAM:mode3” [(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\t%0,%2” [(set_attr “type” “”)])

;;; Negation (define_insn “negAM:mode2” [(set (match_operand:AM 0 “register_operand” “=r”) (neg:AM (match_operand:AM 1 “register_operand” " 0")))] "" “neg\t%0” [(set_attr “type” “”)])

;;; Multiplication (define_insn “mulAM:mode3” [(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\t%0,%2” [(set_attr “type” “”)])

(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 “udivAM:mode3” [(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\t%0,%2” [(set_attr “type” “”)])

;; However, xBPF does provide a signed division operator, sdiv.

(define_insn “divAM:mode3” [(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\t%0,%2” [(set_attr “type” “”)])

;;; Modulus

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

(define_insn “umodAM:mode3” [(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\t%0,%2” [(set_attr “type” “”)])

;; Again, xBPF provides a signed version, smod.

(define_insn “modAM:mode3” [(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\t%0,%2” [(set_attr “type” “”)])

;;; Logical AND (define_insn “andAM:mode3” [(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\t%0,%2” [(set_attr “type” “”)])

;;; Logical inclusive-OR (define_insn “iorAM:mode3” [(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\t%0,%2” [(set_attr “type” “”)])

;;; Logical exclusive-OR (define_insn “xorAM:mode3” [(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\t%0,%2” [(set_attr “type” “”)])

;;;; 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,m”)))] "" “@ and\t%0,0xffff mov\t%0,%1;and\t%0,0xffff ldxh\t%0,%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,m”)))] "" “@ and\t%0,0xff mov\t%0,%1;and\t%0,0xff ldxb\t%0,%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,m”)))] "" "@

  • return bpf_has_alu32 ? "mov32\t%0,%1" : "mov\t%0,%1;and\t%0,0xffffffff"; 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 “movMM:mode” [(set (match_operand:MM 0 “general_operand”) (match_operand:MM 1 “general_operand”))] "" " { if (!register_operand(operands[0], MM:MODEmode) && !register_operand(operands[1], MM:MODEmode)) operands[1] = force_reg (MM:MODEmode, operands[1]); }")

(define_insn “*movMM: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\t%0,%1 mov\t%0,%1 lddw\t%0,%1 stx\t%0,%1 st\t%0,%1” [(set_attr “type” “ldx,alu,alu,stx,st”)])

;;;; Shifts

(define_mode_iterator SIM [(SI “bpf_has_alu32”) DI])

(define_insn “ashrSIM:mode3” [(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\t%0,%2” [(set_attr “type” “”)])

(define_insn “ashlSIM:mode3” [(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\t%0,%2” [(set_attr “type” “”)])

(define_insn “lshrSIM:mode3” [(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\t%0,%2” [(set_attr “type” “”)])

;;;; 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 “cbranchJM:mode4” [(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:MODEmode, 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\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” [(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\t%0,%1” [(set_attr “type” “ld”)])

(define_insn “ldabs” [(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\t%0” [(set_attr “type” “ld”)])

;;;; Atomic increments

(define_mode_iterator AMO [SI DI])

(define_insn “atomic_addAMO: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\t%0,%1” [(set_attr “type” “xadd”)])