;; Machine description for eBPF.
;; Copyright (C) 2019-2023 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
;; .
(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 "add3"
[(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|%w0 += %w2}"
[(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 "sub3"
[(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|%w0 -= %w2}"
[(set_attr "type" "")])
;;; Negation
(define_insn "neg2"
[(set (match_operand:AM 0 "register_operand" "=r")
(neg:AM (match_operand:AM 1 "register_operand" " 0")))]
""
"{neg\t%0|%w0 = -%w1}"
[(set_attr "type" "")])
;;; Multiplication
(define_insn "mul3"
[(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|%w0 *= %w2}"
[(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|%w0 *= %w2}"
[(set_attr "type" "alu32")])
;;; Division
;; Note that eBPF <= V3 doesn't provide instructions for signed
;; integer division.
(define_insn "udiv3"
[(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|%w0 /= %w2}"
[(set_attr "type" "")])
;; However, BPF V4 does provide a signed division operator, sdiv.
(define_insn "div3"
[(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\t%0,%2|%w0 s/= %w2}"
[(set_attr "type" "")])
;;; Modulus
;; Note that eBPF <= V3 doesn't provide instructions for signed
;; integer remainder.
(define_insn "umod3"
[(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|%w0 %%= %w2}"
[(set_attr "type" "")])
;; However, BPF V4 does provide a signed modulus operator, smod.
(define_insn "mod3"
[(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\t%0,%2|%w0 s%%= %w2}"
[(set_attr "type" "")])
;;; Logical AND
(define_insn "and3"
[(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|%w0 &= %w2}"
[(set_attr "type" "")])
;;; Logical inclusive-OR
(define_insn "ior3"
[(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|%w0 %|= %w2}"
[(set_attr "type" "")])
;;; Logical exclusive-OR
(define_insn "xor3"
[(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|%w0 ^= %w2}"
[(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,q")))]
""
"@
{and\t%0,0xffff|%0 &= 0xffff}
{mov\t%0,%1\;and\t%0,0xffff|%0 = %1;%0 &= 0xffff}
{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}
{mov\t%0,%1\;and\t%0,0xff|%0 = %1;%0 &= 0xff}
{ldxh\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_has_alu32 ? \"{mov32\t%0,%1|%0 = %1}\" : \"{mov\t%0,%1\;and\t%0,0xffffffff|%0 = %1;%0 &= 0xffffffff}\";
{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"
"@
{movs\t%0,%1,32|%0 = (s32) %1}
{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"
"@
{movs\t%0,%1,16|%0 = (s16) %1}
{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"
"@
{movs\t%0,%1,8|%0 = (s8) %1}
{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"
"{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"
"{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"
[(set (match_operand:MM 0 "general_operand")
(match_operand:MM 1 "general_operand"))]
""
"
{
bpf_replace_core_move_operands (operands);
if (!register_operand(operands[0], mode)
&& !register_operand(operands[1], mode))
operands[1] = force_reg (mode, operands[1]);
}")
(define_insn "*mov"
[(set (match_operand:MM 0 "nonimmediate_operand" "=r, r,r,q,q")
(match_operand:MM 1 "mov_src_operand" " q,rI,B,r,I"))]
""
"@
{ldx\t%0,%1|%0 = *( *) (%1)}
{mov\t%0,%1|%0 = %1}
{lddw\t%0,%1|%0 = %1 ll}
{stx\t%0,%1|*( *) (%0) = %1}
{st\t%0,%1|*( *) (%0) = %1}"
[(set_attr "type" "ldx,alu,alu,stx,st")])
(define_insn "mov_reloc_core"
[(set (match_operand:MM 0 "nonimmediate_operand" "=r,q,r")
(unspec:MM [
(match_operand:MM 1 "immediate_operand" " I,I,B")
(match_operand:SI 2 "immediate_operand" " I,I,I")
] UNSPEC_CORE_RELOC)
)]
""
"@
*return bpf_add_core_reloc (operands, \"{mov\t%0,%1|%0 = %1}\");
*return bpf_add_core_reloc (operands, \"{st\t%0,%1|*( *) (%0) = %1}\");
*return bpf_add_core_reloc (operands, \"{lddw\t%0,%1|%0 = %1 ll}\");"
[(set_attr "type" "alu,st,alu")])
;;;; Shifts
(define_mode_iterator SIM [(SI "bpf_has_alu32") DI])
(define_insn "ashr3"
[(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|%w0 s>>= %w2}"
[(set_attr "type" "")])
(define_insn "ashl3"
[(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|%w0 <<= %w2}"
[(set_attr "type" "")])
(define_insn "lshr3"
[(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|%w0 >>= %w2}"
[(set_attr "type" "")])
;;;; Byte swapping
(define_mode_iterator BSM [HI SI DI])
(define_mode_attr endmode [(HI "16") (SI "32") (DI "64")])
(define_insn "bswap2"
[(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, |%0 = bswap %1}";
else
{
if (TARGET_BIG_ENDIAN)
return "{endle\t%0, |%0 = le %1}";
else
return "{endbe\t%0, |%0 = be %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 "cbranch4"
[(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 (mode, operands);
})
(define_insn "*branch_on_"
[(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|if %w0 == %w1 goto %2}"; break;
case NE: return "{jne\t%0,%1,%2|if %w0 != %w1 goto %2}"; break;
case LT: return "{jslt\t%0,%1,%2|if %w0 s< %w1 goto %2}"; break;
case LE: return "{jsle\t%0,%1,%2|if %w0 s<= %w1 goto %2}"; break;
case GT: return "{jsgt\t%0,%1,%2|if %w0 s> %w1 goto %2}"; break;
case GE: return "{jsge\t%0,%1,%2|if %w0 s>= %w1 goto %2}"; break;
case LTU: return "{jlt\t%0,%1,%2|if %w0 < %w1 goto %2}"; break;
case LEU: return "{jle\t%0,%1,%2|if %w0 <= %w1 goto %2}"; break;
case GTU: return "{jgt\t%0,%1,%2|if %w0 > %w1 goto %2}"; break;
case GEU: return "{jge\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"
[(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|r0 = *( *) skb[%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|r0 = *( *) skb[%0]}"
[(set_attr "type" "ld")])
(include "atomic.md")