aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard-Qin-X <richard.qin.001@gmail.com>2026-08-07 22:06:28 +0800
committerRichard-Qin-X <richard.qin.001@gmail.com>2026-08-07 22:06:28 +0800
commitf99ef4322a7a2c09380fff61f88ab9440f05a92c (patch)
tree69538d9ac3acb4a4a680dbce143f39c7b576035c
parent3e15bfd7b075209ec3b65d30d0611e060f6fbb77 (diff)
downloadrcc-project-f99ef4322a7a2c09380fff61f88ab9440f05a92c.tar.gz
rcc-project-f99ef4322a7a2c09380fff61f88ab9440f05a92c.tar.xz
[CodeGen] Complete machine pass and ABI lowering infrastructure
-rw-r--r--.clang-tidy3
-rw-r--r--backend/.clang-tidy3
-rw-r--r--backend/include/rcc/CodeGen/GenericOpcode.def336
-rw-r--r--backend/include/rcc/CodeGen/GenericOpcodes.h68
-rw-r--r--backend/include/rcc/CodeGen/MachineAnalyses.h244
-rw-r--r--backend/include/rcc/CodeGen/MachineIRBuilder.h10
-rw-r--r--backend/include/rcc/CodeGen/MachineIRTypes.h3
-rw-r--r--backend/include/rcc/CodeGen/MachinePass.h655
-rw-r--r--backend/include/rcc/CodeGen/TargetCallLowering.h404
-rw-r--r--backend/include/rcc/Target/RISCV/RISCVCallLowering.h43
-rw-r--r--backend/lib/CodeGen/CMakeLists.txt16
-rw-r--r--backend/lib/CodeGen/GenericOpcodes.cpp1498
-rw-r--r--backend/lib/CodeGen/MachineAnalyses.cpp850
-rw-r--r--backend/lib/CodeGen/MachineIRBuilder.cpp13
-rw-r--r--backend/lib/CodeGen/MachinePass.cpp1370
-rw-r--r--backend/lib/CodeGen/TargetCallLowering.cpp353
-rw-r--r--backend/lib/Target/RISCV/CMakeLists.txt4
-rw-r--r--backend/lib/Target/RISCV/RISCVCallLowering.cpp1473
-rw-r--r--backend/unittests/CodeGen/CMakeLists.txt48
-rw-r--r--backend/unittests/CodeGen/GenericOpcodesTest.cpp701
-rw-r--r--backend/unittests/CodeGen/MachineAnalysesTest.cpp477
-rw-r--r--backend/unittests/CodeGen/MachinePassTest.cpp918
-rw-r--r--backend/unittests/CodeGen/TargetCallLoweringTest.cpp329
-rw-r--r--backend/unittests/Target/CMakeLists.txt7
-rw-r--r--backend/unittests/Target/RISCVCallLoweringTest.cpp802
25 files changed, 10628 insertions, 0 deletions
diff --git a/.clang-tidy b/.clang-tidy
index dcad2ae..514bd75 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -7,6 +7,7 @@ Checks: >
bugprone-*,
-bugprone-easily-swappable-parameters,
performance-*,
+ -performance-enum-size,
portability-*,
llvm-header-guard,
llvm-namespace-comment,
@@ -47,6 +48,8 @@ CheckOptions:
- { key: readability-identifier-naming.ConstexprVariableCase, value: CamelCase }
- { key: readability-identifier-naming.ClassConstantCase, value: CamelCase }
- { key: readability-identifier-naming.EnumConstantCase, value: CamelCase }
+ # Preserve canonical protocol, opcode, ABI, and ISA spellings.
+ - { key: readability-identifier-naming.EnumConstantIgnoredRegexp, value: '^([A-Z][A-Z0-9_]*|Pseudo[A-Z0-9_]+)$' }
- { key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE }
# Other
- { key: readability-identifier-naming.IgnoreMainLikeFunctions, value: 1 }
diff --git a/backend/.clang-tidy b/backend/.clang-tidy
index a0e18ce..73a225a 100644
--- a/backend/.clang-tidy
+++ b/backend/.clang-tidy
@@ -7,6 +7,7 @@ Checks: >
bugprone-*,
-bugprone-easily-swappable-parameters,
performance-*,
+ -performance-enum-size,
portability-*,
llvm-header-guard,
llvm-namespace-comment,
@@ -43,6 +44,8 @@ CheckOptions:
- { key: readability-identifier-naming.ConstexprVariableCase, value: CamelCase }
- { key: readability-identifier-naming.ClassConstantCase, value: CamelCase }
- { key: readability-identifier-naming.EnumConstantCase, value: CamelCase }
+ # Preserve canonical protocol, opcode, ABI, and ISA spellings.
+ - { key: readability-identifier-naming.EnumConstantIgnoredRegexp, value: '^([A-Z][A-Z0-9_]*|Pseudo[A-Z0-9_]+)$' }
- { key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE }
- { key: readability-identifier-naming.IgnoreMainLikeFunctions, value: 1 }
- { key: readability-redundant-member-init.IgnoreBaseInCopyConstructors, value: 1 }
diff --git a/backend/include/rcc/CodeGen/GenericOpcode.def b/backend/include/rcc/CodeGen/GenericOpcode.def
new file mode 100644
index 0000000..8caaf7b
--- /dev/null
+++ b/backend/include/rcc/CodeGen/GenericOpcode.def
@@ -0,0 +1,336 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// This file is intentionally included multiple times. Define
+// RCC_CORE_OPCODE and RCC_GENERIC_OPCODE before including it.
+
+RCC_CORE_OPCODE(PHI)
+RCC_CORE_OPCODE(INLINEASM)
+RCC_CORE_OPCODE(INLINEASM_BR)
+RCC_CORE_OPCODE(CFI_INSTRUCTION)
+RCC_CORE_OPCODE(EH_LABEL)
+RCC_CORE_OPCODE(GC_LABEL)
+RCC_CORE_OPCODE(ANNOTATION_LABEL)
+RCC_CORE_OPCODE(KILL)
+RCC_CORE_OPCODE(EXTRACT_SUBREG)
+RCC_CORE_OPCODE(INSERT_SUBREG)
+RCC_CORE_OPCODE(IMPLICIT_DEF)
+RCC_CORE_OPCODE(INIT_UNDEF)
+RCC_CORE_OPCODE(SUBREG_TO_REG)
+RCC_CORE_OPCODE(COPY_TO_REGCLASS)
+RCC_CORE_OPCODE(DBG_VALUE)
+RCC_CORE_OPCODE(DBG_VALUE_LIST)
+RCC_CORE_OPCODE(DBG_INSTR_REF)
+RCC_CORE_OPCODE(DBG_PHI)
+RCC_CORE_OPCODE(DBG_LABEL)
+RCC_CORE_OPCODE(REG_SEQUENCE)
+RCC_CORE_OPCODE(COPY)
+RCC_CORE_OPCODE(COPY_LANEMASK)
+RCC_CORE_OPCODE(BUNDLE)
+RCC_CORE_OPCODE(LIFETIME_START)
+RCC_CORE_OPCODE(LIFETIME_END)
+RCC_CORE_OPCODE(PSEUDO_PROBE)
+RCC_CORE_OPCODE(ARITH_FENCE)
+RCC_CORE_OPCODE(STACKMAP)
+RCC_CORE_OPCODE(FENTRY_CALL)
+RCC_CORE_OPCODE(PATCHPOINT)
+RCC_CORE_OPCODE(LOAD_STACK_GUARD)
+RCC_CORE_OPCODE(PREALLOCATED_SETUP)
+RCC_CORE_OPCODE(PREALLOCATED_ARG)
+RCC_CORE_OPCODE(STATEPOINT)
+RCC_CORE_OPCODE(LOCAL_ESCAPE)
+RCC_CORE_OPCODE(FAULTING_OP)
+RCC_CORE_OPCODE(PATCHABLE_OP)
+RCC_CORE_OPCODE(PATCHABLE_FUNCTION_ENTER)
+RCC_CORE_OPCODE(PATCHABLE_RET)
+RCC_CORE_OPCODE(PATCHABLE_FUNCTION_EXIT)
+RCC_CORE_OPCODE(PATCHABLE_TAIL_CALL)
+RCC_CORE_OPCODE(PATCHABLE_EVENT_CALL)
+RCC_CORE_OPCODE(PATCHABLE_TYPED_EVENT_CALL)
+RCC_CORE_OPCODE(ICALL_BRANCH_FUNNEL)
+RCC_CORE_OPCODE(FAKE_USE)
+RCC_CORE_OPCODE(MEMBARRIER)
+RCC_CORE_OPCODE(JUMP_TABLE_DEBUG_INFO)
+RCC_CORE_OPCODE(RELOC_NONE)
+RCC_CORE_OPCODE(CONVERGENCECTRL_ENTRY)
+RCC_CORE_OPCODE(CONVERGENCECTRL_ANCHOR)
+RCC_CORE_OPCODE(CONVERGENCECTRL_LOOP)
+RCC_CORE_OPCODE(CONVERGENCECTRL_GLUE)
+
+RCC_GENERIC_OPCODE(G_ASSERT_SEXT)
+RCC_GENERIC_OPCODE(G_ASSERT_ZEXT)
+RCC_GENERIC_OPCODE(G_ASSERT_ALIGN)
+RCC_GENERIC_OPCODE(G_ADD)
+RCC_GENERIC_OPCODE(G_SUB)
+RCC_GENERIC_OPCODE(G_MUL)
+RCC_GENERIC_OPCODE(G_SDIV)
+RCC_GENERIC_OPCODE(G_UDIV)
+RCC_GENERIC_OPCODE(G_SREM)
+RCC_GENERIC_OPCODE(G_UREM)
+RCC_GENERIC_OPCODE(G_SDIVREM)
+RCC_GENERIC_OPCODE(G_UDIVREM)
+RCC_GENERIC_OPCODE(G_AND)
+RCC_GENERIC_OPCODE(G_OR)
+RCC_GENERIC_OPCODE(G_XOR)
+RCC_GENERIC_OPCODE(G_ABDS)
+RCC_GENERIC_OPCODE(G_ABDU)
+RCC_GENERIC_OPCODE(G_UAVGFLOOR)
+RCC_GENERIC_OPCODE(G_UAVGCEIL)
+RCC_GENERIC_OPCODE(G_SAVGFLOOR)
+RCC_GENERIC_OPCODE(G_SAVGCEIL)
+RCC_GENERIC_OPCODE(G_IMPLICIT_DEF)
+RCC_GENERIC_OPCODE(G_PHI)
+RCC_GENERIC_OPCODE(G_FRAME_INDEX)
+RCC_GENERIC_OPCODE(G_GLOBAL_VALUE)
+RCC_GENERIC_OPCODE(G_PTRAUTH_GLOBAL_VALUE)
+RCC_GENERIC_OPCODE(G_CONSTANT_POOL)
+RCC_GENERIC_OPCODE(G_EXTRACT)
+RCC_GENERIC_OPCODE(G_UNMERGE_VALUES)
+RCC_GENERIC_OPCODE(G_INSERT)
+RCC_GENERIC_OPCODE(G_MERGE_VALUES)
+RCC_GENERIC_OPCODE(G_BUILD_VECTOR)
+RCC_GENERIC_OPCODE(G_BUILD_VECTOR_TRUNC)
+RCC_GENERIC_OPCODE(G_CONCAT_VECTORS)
+RCC_GENERIC_OPCODE(G_PTRTOINT)
+RCC_GENERIC_OPCODE(G_INTTOPTR)
+RCC_GENERIC_OPCODE(G_BITCAST)
+RCC_GENERIC_OPCODE(G_FREEZE)
+RCC_GENERIC_OPCODE(G_CONSTANT_FOLD_BARRIER)
+RCC_GENERIC_OPCODE(G_INTRINSIC_FPTRUNC_ROUND)
+RCC_GENERIC_OPCODE(G_INTRINSIC_TRUNC)
+RCC_GENERIC_OPCODE(G_INTRINSIC_ROUND)
+RCC_GENERIC_OPCODE(G_INTRINSIC_LRINT)
+RCC_GENERIC_OPCODE(G_INTRINSIC_LLRINT)
+RCC_GENERIC_OPCODE(G_INTRINSIC_ROUNDEVEN)
+RCC_GENERIC_OPCODE(G_READCYCLECOUNTER)
+RCC_GENERIC_OPCODE(G_READSTEADYCOUNTER)
+RCC_GENERIC_OPCODE(G_LOAD)
+RCC_GENERIC_OPCODE(G_SEXTLOAD)
+RCC_GENERIC_OPCODE(G_ZEXTLOAD)
+RCC_GENERIC_OPCODE(G_FPEXTLOAD)
+RCC_GENERIC_OPCODE(G_INDEXED_LOAD)
+RCC_GENERIC_OPCODE(G_INDEXED_SEXTLOAD)
+RCC_GENERIC_OPCODE(G_INDEXED_ZEXTLOAD)
+RCC_GENERIC_OPCODE(G_STORE)
+RCC_GENERIC_OPCODE(G_FPTRUNCSTORE)
+RCC_GENERIC_OPCODE(G_INDEXED_STORE)
+RCC_GENERIC_OPCODE(G_ATOMIC_CMPXCHG_WITH_SUCCESS)
+RCC_GENERIC_OPCODE(G_ATOMIC_CMPXCHG)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_XCHG)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_ADD)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_SUB)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_AND)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_NAND)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_OR)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_XOR)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_MAX)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_MIN)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_UMAX)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_UMIN)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_FADD)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_FSUB)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_FMAX)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_FMIN)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_FMAXIMUM)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_FMINIMUM)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_FMAXIMUMNUM)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_FMINIMUMNUM)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_UINC_WRAP)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_UDEC_WRAP)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_USUB_COND)
+RCC_GENERIC_OPCODE(G_ATOMICRMW_USUB_SAT)
+RCC_GENERIC_OPCODE(G_FENCE)
+RCC_GENERIC_OPCODE(G_PREFETCH)
+RCC_GENERIC_OPCODE(G_BRCOND)
+RCC_GENERIC_OPCODE(G_BRINDIRECT)
+RCC_GENERIC_OPCODE(G_INVOKE_REGION_START)
+RCC_GENERIC_OPCODE(G_INTRINSIC)
+RCC_GENERIC_OPCODE(G_INTRINSIC_W_SIDE_EFFECTS)
+RCC_GENERIC_OPCODE(G_INTRINSIC_CONVERGENT)
+RCC_GENERIC_OPCODE(G_INTRINSIC_CONVERGENT_W_SIDE_EFFECTS)
+RCC_GENERIC_OPCODE(G_ANYEXT)
+RCC_GENERIC_OPCODE(G_TRUNC)
+RCC_GENERIC_OPCODE(G_TRUNC_SSAT_S)
+RCC_GENERIC_OPCODE(G_TRUNC_SSAT_U)
+RCC_GENERIC_OPCODE(G_TRUNC_USAT_U)
+RCC_GENERIC_OPCODE(G_CONSTANT)
+RCC_GENERIC_OPCODE(G_FCONSTANT)
+RCC_GENERIC_OPCODE(G_VASTART)
+RCC_GENERIC_OPCODE(G_VAARG)
+RCC_GENERIC_OPCODE(G_SEXT)
+RCC_GENERIC_OPCODE(G_SEXT_INREG)
+RCC_GENERIC_OPCODE(G_ZEXT)
+RCC_GENERIC_OPCODE(G_SHL)
+RCC_GENERIC_OPCODE(G_LSHR)
+RCC_GENERIC_OPCODE(G_ASHR)
+RCC_GENERIC_OPCODE(G_FSHL)
+RCC_GENERIC_OPCODE(G_FSHR)
+RCC_GENERIC_OPCODE(G_ROTR)
+RCC_GENERIC_OPCODE(G_ROTL)
+RCC_GENERIC_OPCODE(G_ICMP)
+RCC_GENERIC_OPCODE(G_FCMP)
+RCC_GENERIC_OPCODE(G_SCMP)
+RCC_GENERIC_OPCODE(G_UCMP)
+RCC_GENERIC_OPCODE(G_SELECT)
+RCC_GENERIC_OPCODE(G_UADDO)
+RCC_GENERIC_OPCODE(G_UADDE)
+RCC_GENERIC_OPCODE(G_USUBO)
+RCC_GENERIC_OPCODE(G_USUBE)
+RCC_GENERIC_OPCODE(G_SADDO)
+RCC_GENERIC_OPCODE(G_SADDE)
+RCC_GENERIC_OPCODE(G_SSUBO)
+RCC_GENERIC_OPCODE(G_SSUBE)
+RCC_GENERIC_OPCODE(G_UMULO)
+RCC_GENERIC_OPCODE(G_SMULO)
+RCC_GENERIC_OPCODE(G_UMULH)
+RCC_GENERIC_OPCODE(G_SMULH)
+RCC_GENERIC_OPCODE(G_UADDSAT)
+RCC_GENERIC_OPCODE(G_SADDSAT)
+RCC_GENERIC_OPCODE(G_USUBSAT)
+RCC_GENERIC_OPCODE(G_SSUBSAT)
+RCC_GENERIC_OPCODE(G_USHLSAT)
+RCC_GENERIC_OPCODE(G_SSHLSAT)
+RCC_GENERIC_OPCODE(G_SMULFIX)
+RCC_GENERIC_OPCODE(G_UMULFIX)
+RCC_GENERIC_OPCODE(G_SMULFIXSAT)
+RCC_GENERIC_OPCODE(G_UMULFIXSAT)
+RCC_GENERIC_OPCODE(G_SDIVFIX)
+RCC_GENERIC_OPCODE(G_UDIVFIX)
+RCC_GENERIC_OPCODE(G_SDIVFIXSAT)
+RCC_GENERIC_OPCODE(G_UDIVFIXSAT)
+RCC_GENERIC_OPCODE(G_FADD)
+RCC_GENERIC_OPCODE(G_FSUB)
+RCC_GENERIC_OPCODE(G_FMUL)
+RCC_GENERIC_OPCODE(G_FMA)
+RCC_GENERIC_OPCODE(G_FMAD)
+RCC_GENERIC_OPCODE(G_FDIV)
+RCC_GENERIC_OPCODE(G_FREM)
+RCC_GENERIC_OPCODE(G_FMODF)
+RCC_GENERIC_OPCODE(G_FPOW)
+RCC_GENERIC_OPCODE(G_FPOWI)
+RCC_GENERIC_OPCODE(G_FEXP)
+RCC_GENERIC_OPCODE(G_FEXP2)
+RCC_GENERIC_OPCODE(G_FEXP10)
+RCC_GENERIC_OPCODE(G_FLOG)
+RCC_GENERIC_OPCODE(G_FLOG2)
+RCC_GENERIC_OPCODE(G_FLOG10)
+RCC_GENERIC_OPCODE(G_FLDEXP)
+RCC_GENERIC_OPCODE(G_FFREXP)
+RCC_GENERIC_OPCODE(G_FNEG)
+RCC_GENERIC_OPCODE(G_FPEXT)
+RCC_GENERIC_OPCODE(G_FPTRUNC)
+RCC_GENERIC_OPCODE(G_FPTOSI)
+RCC_GENERIC_OPCODE(G_FPTOUI)
+RCC_GENERIC_OPCODE(G_SITOFP)
+RCC_GENERIC_OPCODE(G_UITOFP)
+RCC_GENERIC_OPCODE(G_FPTOSI_SAT)
+RCC_GENERIC_OPCODE(G_FPTOUI_SAT)
+RCC_GENERIC_OPCODE(G_FABS)
+RCC_GENERIC_OPCODE(G_FCOPYSIGN)
+RCC_GENERIC_OPCODE(G_IS_FPCLASS)
+RCC_GENERIC_OPCODE(G_FCANONICALIZE)
+RCC_GENERIC_OPCODE(G_FMINNUM)
+RCC_GENERIC_OPCODE(G_FMAXNUM)
+RCC_GENERIC_OPCODE(G_FMINNUM_IEEE)
+RCC_GENERIC_OPCODE(G_FMAXNUM_IEEE)
+RCC_GENERIC_OPCODE(G_FMINIMUM)
+RCC_GENERIC_OPCODE(G_FMAXIMUM)
+RCC_GENERIC_OPCODE(G_FMINIMUMNUM)
+RCC_GENERIC_OPCODE(G_FMAXIMUMNUM)
+RCC_GENERIC_OPCODE(G_GET_FPENV)
+RCC_GENERIC_OPCODE(G_SET_FPENV)
+RCC_GENERIC_OPCODE(G_RESET_FPENV)
+RCC_GENERIC_OPCODE(G_GET_FPMODE)
+RCC_GENERIC_OPCODE(G_SET_FPMODE)
+RCC_GENERIC_OPCODE(G_RESET_FPMODE)
+RCC_GENERIC_OPCODE(G_GET_ROUNDING)
+RCC_GENERIC_OPCODE(G_SET_ROUNDING)
+RCC_GENERIC_OPCODE(G_PTR_ADD)
+RCC_GENERIC_OPCODE(G_PTRMASK)
+RCC_GENERIC_OPCODE(G_SMIN)
+RCC_GENERIC_OPCODE(G_SMAX)
+RCC_GENERIC_OPCODE(G_UMIN)
+RCC_GENERIC_OPCODE(G_UMAX)
+RCC_GENERIC_OPCODE(G_ABS)
+RCC_GENERIC_OPCODE(G_LROUND)
+RCC_GENERIC_OPCODE(G_LLROUND)
+RCC_GENERIC_OPCODE(G_BR)
+RCC_GENERIC_OPCODE(G_BRJT)
+RCC_GENERIC_OPCODE(G_VSCALE)
+RCC_GENERIC_OPCODE(G_INSERT_SUBVECTOR)
+RCC_GENERIC_OPCODE(G_EXTRACT_SUBVECTOR)
+RCC_GENERIC_OPCODE(G_INSERT_VECTOR_ELT)
+RCC_GENERIC_OPCODE(G_EXTRACT_VECTOR_ELT)
+RCC_GENERIC_OPCODE(G_SHUFFLE_VECTOR)
+RCC_GENERIC_OPCODE(G_SPLAT_VECTOR)
+RCC_GENERIC_OPCODE(G_STEP_VECTOR)
+RCC_GENERIC_OPCODE(G_VECTOR_COMPRESS)
+RCC_GENERIC_OPCODE(G_CTTZ)
+RCC_GENERIC_OPCODE(G_CTTZ_ZERO_POISON)
+RCC_GENERIC_OPCODE(G_CTLZ)
+RCC_GENERIC_OPCODE(G_CTLZ_ZERO_POISON)
+RCC_GENERIC_OPCODE(G_CTLS)
+RCC_GENERIC_OPCODE(G_CTPOP)
+RCC_GENERIC_OPCODE(G_BSWAP)
+RCC_GENERIC_OPCODE(G_BITREVERSE)
+RCC_GENERIC_OPCODE(G_CLMUL)
+RCC_GENERIC_OPCODE(G_FCEIL)
+RCC_GENERIC_OPCODE(G_FCOS)
+RCC_GENERIC_OPCODE(G_FSIN)
+RCC_GENERIC_OPCODE(G_FSINCOS)
+RCC_GENERIC_OPCODE(G_FTAN)
+RCC_GENERIC_OPCODE(G_FACOS)
+RCC_GENERIC_OPCODE(G_FASIN)
+RCC_GENERIC_OPCODE(G_FATAN)
+RCC_GENERIC_OPCODE(G_FATAN2)
+RCC_GENERIC_OPCODE(G_FCOSH)
+RCC_GENERIC_OPCODE(G_FSINH)
+RCC_GENERIC_OPCODE(G_FTANH)
+RCC_GENERIC_OPCODE(G_FSQRT)
+RCC_GENERIC_OPCODE(G_FFLOOR)
+RCC_GENERIC_OPCODE(G_FRINT)
+RCC_GENERIC_OPCODE(G_FNEARBYINT)
+RCC_GENERIC_OPCODE(G_ADDRSPACE_CAST)
+RCC_GENERIC_OPCODE(G_BLOCK_ADDR)
+RCC_GENERIC_OPCODE(G_JUMP_TABLE)
+RCC_GENERIC_OPCODE(G_DYN_STACKALLOC)
+RCC_GENERIC_OPCODE(G_STACKSAVE)
+RCC_GENERIC_OPCODE(G_STACKRESTORE)
+RCC_GENERIC_OPCODE(G_STRICT_FADD)
+RCC_GENERIC_OPCODE(G_STRICT_FSUB)
+RCC_GENERIC_OPCODE(G_STRICT_FMUL)
+RCC_GENERIC_OPCODE(G_STRICT_FDIV)
+RCC_GENERIC_OPCODE(G_STRICT_FREM)
+RCC_GENERIC_OPCODE(G_STRICT_FMA)
+RCC_GENERIC_OPCODE(G_STRICT_FSQRT)
+RCC_GENERIC_OPCODE(G_STRICT_FLDEXP)
+RCC_GENERIC_OPCODE(G_STRICT_FCMP)
+RCC_GENERIC_OPCODE(G_STRICT_FCMPS)
+RCC_GENERIC_OPCODE(G_READ_REGISTER)
+RCC_GENERIC_OPCODE(G_WRITE_REGISTER)
+RCC_GENERIC_OPCODE(G_MEMCPY)
+RCC_GENERIC_OPCODE(G_MEMCPY_INLINE)
+RCC_GENERIC_OPCODE(G_MEMMOVE)
+RCC_GENERIC_OPCODE(G_MEMSET)
+RCC_GENERIC_OPCODE(G_BZERO)
+RCC_GENERIC_OPCODE(G_TRAP)
+RCC_GENERIC_OPCODE(G_DEBUGTRAP)
+RCC_GENERIC_OPCODE(G_UBSANTRAP)
+RCC_GENERIC_OPCODE(G_VECREDUCE_SEQ_FADD)
+RCC_GENERIC_OPCODE(G_VECREDUCE_SEQ_FMUL)
+RCC_GENERIC_OPCODE(G_VECREDUCE_FADD)
+RCC_GENERIC_OPCODE(G_VECREDUCE_FMUL)
+RCC_GENERIC_OPCODE(G_VECREDUCE_FMAX)
+RCC_GENERIC_OPCODE(G_VECREDUCE_FMIN)
+RCC_GENERIC_OPCODE(G_VECREDUCE_FMAXIMUM)
+RCC_GENERIC_OPCODE(G_VECREDUCE_FMINIMUM)
+RCC_GENERIC_OPCODE(G_VECREDUCE_ADD)
+RCC_GENERIC_OPCODE(G_VECREDUCE_MUL)
+RCC_GENERIC_OPCODE(G_VECREDUCE_AND)
+RCC_GENERIC_OPCODE(G_VECREDUCE_OR)
+RCC_GENERIC_OPCODE(G_VECREDUCE_XOR)
+RCC_GENERIC_OPCODE(G_VECREDUCE_SMAX)
+RCC_GENERIC_OPCODE(G_VECREDUCE_SMIN)
+RCC_GENERIC_OPCODE(G_VECREDUCE_UMAX)
+RCC_GENERIC_OPCODE(G_VECREDUCE_UMIN)
+RCC_GENERIC_OPCODE(G_SBFX)
+RCC_GENERIC_OPCODE(G_UBFX)
diff --git a/backend/include/rcc/CodeGen/GenericOpcodes.h b/backend/include/rcc/CodeGen/GenericOpcodes.h
new file mode 100644
index 0000000..5a46e21
--- /dev/null
+++ b/backend/include/rcc/CodeGen/GenericOpcodes.h
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_CODEGEN_GENERICOPCODES_H
+#define RCC_CODEGEN_GENERICOPCODES_H
+
+#include "rcc/CodeGen/InstructionDescriptor.h"
+
+#include <cstdint>
+#include <span>
+#include <string_view>
+#include <vector>
+
+namespace rcc {
+
+// Target-independent values are build-local. Target tables start at 1024 and
+// are appended to this set when a target description is constructed.
+enum class GenericOpcode : std::uint32_t {
+ Invalid,
+#define RCC_CORE_OPCODE(Name) Name,
+#define RCC_GENERIC_OPCODE(Name)
+#include "rcc/CodeGen/GenericOpcode.def"
+#undef RCC_GENERIC_OPCODE
+#undef RCC_CORE_OPCODE
+ CORE_PSEUDO_END,
+
+#define RCC_CORE_OPCODE(Name)
+#define RCC_GENERIC_OPCODE(Name) Name,
+#include "rcc/CodeGen/GenericOpcode.def"
+#undef RCC_GENERIC_OPCODE
+#undef RCC_CORE_OPCODE
+ G_GENERIC_END,
+};
+
+[[nodiscard]] constexpr Opcode opcode(GenericOpcode Generic) noexcept {
+ return Opcode::fromValue(static_cast<std::uint32_t>(Generic));
+}
+
+[[nodiscard]] constexpr bool isGenericOpcode(Opcode Value) noexcept {
+ return Value.value() >= static_cast<std::uint32_t>(GenericOpcode::G_ASSERT_SEXT) &&
+ Value.value() < static_cast<std::uint32_t>(GenericOpcode::G_GENERIC_END);
+}
+
+[[nodiscard]] constexpr bool isCorePseudoOpcode(Opcode Value) noexcept {
+ return Value.value() >= static_cast<std::uint32_t>(GenericOpcode::PHI) &&
+ Value.value() < static_cast<std::uint32_t>(GenericOpcode::CORE_PSEUDO_END);
+}
+
+inline constexpr std::size_t CorePseudoOpcodeCount =
+ static_cast<std::size_t>(GenericOpcode::CORE_PSEUDO_END) -
+ static_cast<std::size_t>(GenericOpcode::PHI);
+
+inline constexpr std::size_t GenericOpcodeCount =
+ static_cast<std::size_t>(GenericOpcode::G_GENERIC_END) -
+ static_cast<std::size_t>(GenericOpcode::G_ASSERT_SEXT);
+
+[[nodiscard]] std::string_view getGenericOpcodeName(GenericOpcode OpcodeValue) noexcept;
+
+[[nodiscard]] std::span<const InstructionDescriptor> genericInstructionDescriptors() noexcept;
+
+[[nodiscard]] std::span<const ImmediatePredicate> genericImmediatePredicates() noexcept;
+
+[[nodiscard]] Expected<void>
+appendGenericInstructionInfo(std::vector<InstructionDescriptor> &Descriptors,
+ std::vector<ImmediatePredicate> &Predicates);
+
+} // namespace rcc
+
+#endif // RCC_CODEGEN_GENERICOPCODES_H
diff --git a/backend/include/rcc/CodeGen/MachineAnalyses.h b/backend/include/rcc/CodeGen/MachineAnalyses.h
new file mode 100644
index 0000000..eda550d
--- /dev/null
+++ b/backend/include/rcc/CodeGen/MachineAnalyses.h
@@ -0,0 +1,244 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_CODEGEN_MACHINEANALYSES_H
+#define RCC_CODEGEN_MACHINEANALYSES_H
+
+#include "rcc/CodeGen/MachineBasicBlock.h"
+#include "rcc/CodeGen/MachineIRTypes.h"
+#include "rcc/CodeGen/MachinePass.h"
+#include "rcc/CodeGen/StableID.h"
+#include "rcc/Support/Error.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <span>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+namespace rcc {
+
+class MachineBasicBlock;
+class MachineFunction;
+class MachineInstr;
+class MachineMemOperand;
+class TargetDescription;
+
+class MachineDominatorTreeAnalysis final {
+public:
+ class Result final {
+ public:
+ [[nodiscard]] bool isReachable(const MachineBasicBlock &Block) const noexcept;
+ [[nodiscard]] bool dominates(const MachineBasicBlock &Dominator,
+ const MachineBasicBlock &Block) const noexcept;
+ [[nodiscard]] bool dominates(const MachineInstr &Dominator,
+ const MachineInstr &Instruction) const noexcept;
+ [[nodiscard]] const MachineBasicBlock *
+ immediateDominator(const MachineBasicBlock &Block) const noexcept;
+ [[nodiscard]] const MachineBasicBlock *
+ nearestCommonDominator(const MachineBasicBlock &Left,
+ const MachineBasicBlock &Right) const noexcept;
+ [[nodiscard]] std::span<MachineBasicBlock *const> unreachableBlocks() const noexcept {
+ return UnreachableBlocks;
+ }
+
+ [[nodiscard]] Expected<void> updateAfterCFGChange(MachineFunction &Function);
+
+ private:
+ [[nodiscard]] Expected<void> build(MachineFunction &Function);
+
+ std::vector<MachineBasicBlock *> Blocks;
+ std::unordered_map<const MachineBasicBlock *, std::size_t> BlockToIndex;
+ std::vector<std::vector<bool>> Dominators;
+ std::vector<const MachineBasicBlock *> ImmediateDominators;
+ std::vector<bool> Reachable;
+ std::vector<MachineBasicBlock *> UnreachableBlocks;
+
+ friend class MachineDominatorTreeAnalysis;
+ };
+
+ [[nodiscard]] static constexpr std::string_view name() noexcept {
+ return "MachineDominatorTree";
+ }
+
+ [[nodiscard]] static Expected<Result>
+ run(MachineFunction &Function, const TargetDescription &Target, MachineAnalysisManager &Analyses);
+};
+
+struct MachineLoop final {
+ MachineBasicBlock *Header = nullptr;
+ std::vector<MachineBasicBlock *> Blocks;
+ std::vector<MachineBasicBlock *> Latches;
+ std::size_t Parent = static_cast<std::size_t>(-1);
+ std::vector<std::size_t> Children;
+ std::uint32_t Depth = 1;
+};
+
+struct IrreducibleMachineRegion final {
+ std::vector<MachineBasicBlock *> Blocks;
+ std::vector<MachineBasicBlock *> Entries;
+};
+
+class MachineLoopAnalysis final {
+public:
+ class Result final {
+ public:
+ [[nodiscard]] std::span<const MachineLoop> loops() const noexcept {
+ return Loops;
+ }
+ [[nodiscard]] std::span<const IrreducibleMachineRegion> irreducibleRegions() const noexcept {
+ return IrreducibleRegions;
+ }
+ [[nodiscard]] const MachineLoop *loopFor(const MachineBasicBlock &Block) const noexcept;
+ [[nodiscard]] std::uint32_t loopDepth(const MachineBasicBlock &Block) const noexcept;
+ [[nodiscard]] bool isIrreducible(const MachineBasicBlock &Block) const noexcept;
+
+ private:
+ std::vector<MachineLoop> Loops;
+ std::vector<IrreducibleMachineRegion> IrreducibleRegions;
+ std::unordered_map<const MachineBasicBlock *, std::size_t> InnermostLoop;
+ std::unordered_set<const MachineBasicBlock *> IrreducibleBlocks;
+
+ friend class MachineLoopAnalysis;
+ };
+
+ [[nodiscard]] static constexpr std::string_view name() noexcept {
+ return "MachineLoopInfo";
+ }
+
+ [[nodiscard]] static Expected<Result>
+ run(MachineFunction &Function, const TargetDescription &Target, MachineAnalysisManager &Analyses);
+};
+
+class MachineBranchProbabilityAnalysis final {
+public:
+ class Result final {
+ public:
+ [[nodiscard]] BranchProbability probability(const MachineBasicBlock &From,
+ const MachineBasicBlock &To) const noexcept;
+ [[nodiscard]] std::span<const MachineSuccessor>
+ successors(const MachineBasicBlock &Block) const noexcept;
+
+ private:
+ std::unordered_map<const MachineBasicBlock *, std::vector<MachineSuccessor>> Probabilities;
+
+ friend class MachineBranchProbabilityAnalysis;
+ };
+
+ [[nodiscard]] static constexpr std::string_view name() noexcept {
+ return "MachineBranchProbability";
+ }
+
+ [[nodiscard]] static Expected<Result>
+ run(MachineFunction &Function, const TargetDescription &Target, MachineAnalysisManager &Analyses);
+};
+
+class MachineBlockFrequencyAnalysis final {
+public:
+ static constexpr std::uint64_t EntryFrequency = std::uint64_t{1} << 32U;
+
+ class Result final {
+ public:
+ [[nodiscard]] std::uint64_t frequency(const MachineBasicBlock &Block) const noexcept;
+
+ private:
+ std::unordered_map<const MachineBasicBlock *, std::uint64_t> Frequencies;
+
+ friend class MachineBlockFrequencyAnalysis;
+ };
+
+ [[nodiscard]] static constexpr std::string_view name() noexcept {
+ return "MachineBlockFrequency";
+ }
+
+ [[nodiscard]] static Expected<Result>
+ run(MachineFunction &Function, const TargetDescription &Target, MachineAnalysisManager &Analyses);
+};
+
+enum class MachineAliasResult : std::uint8_t {
+ NoAlias,
+ MayAlias,
+ PartialAlias,
+ MustAlias,
+};
+
+class MachineAliasAnalysis final {
+public:
+ class Result final {
+ public:
+ [[nodiscard]] MachineAliasResult alias(const MachineMemOperand &Left,
+ const MachineMemOperand &Right) const noexcept;
+ };
+
+ [[nodiscard]] static constexpr std::string_view name() noexcept {
+ return "MachineAliasAnalysis";
+ }
+
+ [[nodiscard]] static Expected<Result>
+ run(MachineFunction &Function, const TargetDescription &Target, MachineAnalysisManager &Analyses);
+};
+
+enum class MachineMemoryDependencyKind : std::uint8_t {
+ Flow,
+ Anti,
+ Output,
+ Ordered,
+};
+
+struct MachineMemoryDependency final {
+ MachineInstrStableID Source;
+ MachineInstrStableID Destination;
+ MachineMemoryDependencyKind Kind = MachineMemoryDependencyKind::Ordered;
+};
+
+struct MachineMemoryDependenceRegion final {
+ const MachineBasicBlock *Block = nullptr;
+ std::vector<MachineInstrStableID> Instructions;
+};
+
+class MachineMemoryDependenceAnalysis final {
+public:
+ class Result final {
+ public:
+ [[nodiscard]] std::span<const MachineMemoryDependenceRegion> regions() const noexcept {
+ return Regions;
+ }
+ [[nodiscard]] const MachineMemoryDependenceRegion *
+ regionFor(const MachineBasicBlock &Block) const noexcept;
+ [[nodiscard]] std::span<const MachineMemoryDependency>
+ dependencies(const MachineBasicBlock &Block) const noexcept;
+ [[nodiscard]] std::span<const MachineMemoryDependency>
+ dependencies(const MachineInstr &Instruction) const noexcept;
+ [[nodiscard]] std::span<const MachineMemoryDependency> allDependencies() const noexcept {
+ return Dependencies;
+ }
+
+ private:
+ struct Range final {
+ std::size_t Begin = 0;
+ std::size_t Size = 0;
+ };
+
+ std::vector<MachineMemoryDependency> Dependencies;
+ std::vector<MachineMemoryDependenceRegion> Regions;
+ std::unordered_map<const MachineBasicBlock *, std::size_t> BlockToRegion;
+ std::unordered_map<const MachineBasicBlock *, Range> BlockRanges;
+ std::unordered_map<MachineInstrStableID, Range> DestinationRanges;
+
+ friend class MachineMemoryDependenceAnalysis;
+ };
+
+ [[nodiscard]] static constexpr std::string_view name() noexcept {
+ return "MachineMemoryDependence";
+ }
+
+ [[nodiscard]] static Expected<Result>
+ run(MachineFunction &Function, const TargetDescription &Target, MachineAnalysisManager &Analyses);
+};
+
+[[nodiscard]] Expected<void> registerStage8MachineAnalyses(MachineAnalysisManager &Analyses);
+
+} // namespace rcc
+
+#endif // RCC_CODEGEN_MACHINEANALYSES_H
diff --git a/backend/include/rcc/CodeGen/MachineIRBuilder.h b/backend/include/rcc/CodeGen/MachineIRBuilder.h
index 2a5979b..2524c7a 100644
--- a/backend/include/rcc/CodeGen/MachineIRBuilder.h
+++ b/backend/include/rcc/CodeGen/MachineIRBuilder.h
@@ -161,6 +161,16 @@ public:
return *Function;
}
+ [[nodiscard]] const TargetDescription &target() const noexcept {
+ return *Target;
+ }
+
+ [[nodiscard]] MachineBasicBlock *insertionBlock() const noexcept {
+ return InsertBlock;
+ }
+
+ [[nodiscard]] Expected<void> verifyInsertPoint() const;
+
[[nodiscard]] Expected<void> setInsertPoint(MachineBasicBlock &Block);
[[nodiscard]] Expected<void> setInsertPointBefore(MachineInstr &Instruction);
[[nodiscard]] Expected<void> setInsertPointAfter(MachineInstr &Instruction);
diff --git a/backend/include/rcc/CodeGen/MachineIRTypes.h b/backend/include/rcc/CodeGen/MachineIRTypes.h
index f7095cc..ea65eff 100644
--- a/backend/include/rcc/CodeGen/MachineIRTypes.h
+++ b/backend/include/rcc/CodeGen/MachineIRTypes.h
@@ -61,6 +61,7 @@ struct OriginIDTag;
struct TargetConstantTypeIDTag;
struct TargetFunctionStateTypeIDTag;
struct TargetRelocationKindIDTag;
+struct CallingConventionIDTag;
} // namespace detail
@@ -86,6 +87,7 @@ using OriginID = MachineIndex<detail::OriginIDTag, std::uint64_t>;
using TargetConstantTypeID = MachineIndex<detail::TargetConstantTypeIDTag, std::uint64_t>;
using TargetFunctionStateTypeID = MachineIndex<detail::TargetFunctionStateTypeIDTag, std::uint64_t>;
using TargetRelocationKindID = MachineIndex<detail::TargetRelocationKindIDTag>;
+using CallingConventionID = MachineIndex<detail::CallingConventionIDTag>;
class Alignment final {
public:
@@ -298,6 +300,7 @@ struct SourceLocation final {
static_assert(sizeof(Opcode) == sizeof(std::uint32_t));
static_assert(sizeof(Alignment) == sizeof(std::uint64_t));
static_assert(sizeof(FrameIndex) == sizeof(std::uint32_t));
+static_assert(sizeof(CallingConventionID) == sizeof(std::uint32_t));
static_assert(sizeof(MemoryAliasSourceID) == sizeof(std::uint64_t));
static_assert(std::is_trivially_copyable_v<Opcode>);
static_assert(std::is_trivially_copyable_v<FrameIndex>);
diff --git a/backend/include/rcc/CodeGen/MachinePass.h b/backend/include/rcc/CodeGen/MachinePass.h
new file mode 100644
index 0000000..66e3b6b
--- /dev/null
+++ b/backend/include/rcc/CodeGen/MachinePass.h
@@ -0,0 +1,655 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_CODEGEN_MACHINEPASS_H
+#define RCC_CODEGEN_MACHINEPASS_H
+
+#include "rcc/CodeGen/InstructionDescriptor.h"
+#include "rcc/CodeGen/MachineFunctionProperties.h"
+#include "rcc/Support/Error.h"
+
+#include <concepts>
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <initializer_list>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <span>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+namespace rcc {
+
+class CompilationUnit;
+class MachineFunction;
+class MachineVerifier;
+class TargetDescription;
+
+using AnalysisID = const void *;
+
+enum class MachinePassChange : std::uint32_t {
+ None = 0,
+ Instructions = 1U << 0U,
+ Operands = 1U << 1U,
+ Registers = 1U << 2U,
+ CFG = 1U << 3U,
+ BlockLayout = 1U << 4U,
+ Frame = 1U << 5U,
+ ConstantPool = 1U << 6U,
+ JumpTable = 1U << 7U,
+ TargetState = 1U << 8U,
+};
+
+class MachinePassChanges final {
+public:
+ constexpr MachinePassChanges() noexcept = default;
+ constexpr MachinePassChanges(std::initializer_list<MachinePassChange> Changes) noexcept {
+ for (const MachinePassChange Change : Changes)
+ add(Change);
+ }
+
+ [[nodiscard]] static constexpr MachinePassChanges all() noexcept {
+ return fromRaw((1U << 9U) - 1U);
+ }
+
+ [[nodiscard]] static constexpr MachinePassChanges fromRaw(std::uint32_t Bits) noexcept {
+ MachinePassChanges Result;
+ Result.Bits = Bits & rawMask();
+ return Result;
+ }
+
+ constexpr void add(MachinePassChange Change) noexcept {
+ Bits |= static_cast<std::uint32_t>(Change);
+ }
+
+ [[nodiscard]] constexpr bool has(MachinePassChange Change) const noexcept {
+ return (Bits & static_cast<std::uint32_t>(Change)) != 0;
+ }
+
+ [[nodiscard]] constexpr bool contains(MachinePassChanges Other) const noexcept {
+ return (Bits & Other.Bits) == Other.Bits;
+ }
+
+ [[nodiscard]] constexpr bool intersects(MachinePassChanges Other) const noexcept {
+ return (Bits & Other.Bits) != 0;
+ }
+
+ [[nodiscard]] constexpr bool empty() const noexcept {
+ return Bits == 0;
+ }
+
+ [[nodiscard]] constexpr std::uint32_t rawValue() const noexcept {
+ return Bits;
+ }
+
+ friend constexpr bool operator==(MachinePassChanges Left,
+ MachinePassChanges Right) noexcept = default;
+
+private:
+ [[nodiscard]] static constexpr std::uint32_t rawMask() noexcept {
+ return (1U << 9U) - 1U;
+ }
+
+ std::uint32_t Bits = 0;
+};
+
+class PreservedAnalyses final {
+public:
+ [[nodiscard]] static PreservedAnalyses all() noexcept {
+ PreservedAnalyses Result;
+ Result.PreserveAll = true;
+ return Result;
+ }
+
+ [[nodiscard]] static PreservedAnalyses none() noexcept {
+ return {};
+ }
+
+ void preserve(AnalysisID ID) {
+ if (ID != nullptr)
+ Preserved.insert(ID);
+ }
+
+ template <typename AnalysisT> void preserve() {
+ preserve(analysisID<AnalysisT>());
+ }
+
+ [[nodiscard]] bool preserves(AnalysisID ID) const noexcept {
+ return PreserveAll || (ID != nullptr && Preserved.contains(ID));
+ }
+
+ [[nodiscard]] bool preservesAll() const noexcept {
+ return PreserveAll;
+ }
+
+ template <typename AnalysisT> [[nodiscard]] static AnalysisID analysisID() noexcept {
+ static const std::uint8_t Token = 0;
+ return &Token;
+ }
+
+private:
+ bool PreserveAll = false;
+ std::unordered_set<AnalysisID> Preserved;
+};
+
+enum class PassThreadSafety : std::uint8_t {
+ NotThreadSafe,
+ ThreadSafe,
+};
+
+struct PassParameter final {
+ std::string Name;
+ std::string Description;
+ std::optional<std::string> DefaultValue;
+ bool Required = false;
+};
+
+struct MachinePassRegistrationMetadata {
+ std::vector<PassParameter> Parameters;
+ PassThreadSafety ThreadSafety = PassThreadSafety::NotThreadSafe;
+ MachinePassChanges DeclaredChanges = MachinePassChanges::all();
+};
+
+struct MachinePassContract final : MachinePassRegistrationMetadata {
+ std::string Name;
+ MachineFunctionProperties Requires;
+ MachineFunctionProperties Sets;
+ MachineFunctionProperties Clears;
+ std::vector<AnalysisID> RequiredAnalyses;
+ std::optional<PipelineStage> VerificationStage;
+};
+
+struct MachinePassResult final {
+ bool Changed = false;
+ MachinePassChanges Changes;
+ PreservedAnalyses Preserved = PreservedAnalyses::none();
+};
+
+struct CompilationUnitPassContract final {
+ std::string Name;
+ std::vector<AnalysisID> RequiredAnalyses;
+ std::vector<PassParameter> Parameters;
+ MachinePassChanges DeclaredChanges = MachinePassChanges::all();
+};
+
+struct CompilationUnitPassResult final {
+ bool Changed = false;
+ MachinePassChanges Changes;
+ PreservedAnalyses PreservedUnitAnalyses = PreservedAnalyses::none();
+ PreservedAnalyses PreservedFunctionAnalyses = PreservedAnalyses::none();
+};
+
+struct MachineAnalysisRegistration final {
+ AnalysisID ID = nullptr;
+ std::string Name;
+ std::vector<AnalysisID> Dependencies;
+ MachinePassChanges InvalidatedBy = MachinePassChanges::all();
+ PassThreadSafety ThreadSafety = PassThreadSafety::NotThreadSafe;
+};
+
+struct CompilationUnitAnalysisRegistration final {
+ AnalysisID ID = nullptr;
+ std::string Name;
+ std::vector<AnalysisID> Dependencies;
+ MachinePassChanges InvalidatedBy = MachinePassChanges::all();
+};
+
+class MachinePassInstrumentation;
+
+class MachineAnalysisManager final {
+public:
+ MachineAnalysisManager() = default;
+ MachineAnalysisManager(const MachineAnalysisManager &) = delete;
+ MachineAnalysisManager &operator=(const MachineAnalysisManager &) = delete;
+ MachineAnalysisManager(MachineAnalysisManager &&) = delete;
+ MachineAnalysisManager &operator=(MachineAnalysisManager &&) = delete;
+ ~MachineAnalysisManager() = default;
+
+ template <typename AnalysisT> [[nodiscard]] static AnalysisID id() noexcept {
+ return PreservedAnalyses::analysisID<AnalysisT>();
+ }
+
+ template <typename AnalysisT>
+ [[nodiscard]] Expected<void> registerAnalysis(MachineAnalysisRegistration Registration = {}) {
+ using ResultT = typename AnalysisT::Result;
+ if (Registration.ID == nullptr)
+ Registration.ID = id<AnalysisT>();
+ if (Registration.Name.empty()) {
+ if constexpr (requires {
+ { AnalysisT::name() } -> std::convertible_to<std::string_view>;
+ }) {
+ Registration.Name = std::string(AnalysisT::name());
+ } else {
+ Registration.Name = "unnamed-machine-analysis";
+ }
+ }
+
+ ErasedRunner Runner = [](MachineFunction &Function, const TargetDescription &Target,
+ MachineAnalysisManager &Manager) -> Expected<std::shared_ptr<void>> {
+ auto Computed = AnalysisT::run(Function, Target, Manager);
+ if (!Computed)
+ return std::unexpected(Computed.error());
+ return std::shared_ptr<void>(new ResultT(std::move(*Computed)),
+ [](void *Value) { delete static_cast<ResultT *>(Value); });
+ };
+ ErasedInvalidator Invalidator = [](void *Value, const MachineFunction &Function,
+ const PreservedAnalyses &Preserved) -> bool {
+ if constexpr (requires(ResultT &Result, const MachineFunction &InputFunction,
+ const PreservedAnalyses &Analyses) {
+ { Result.invalidate(InputFunction, Analyses) } -> std::convertible_to<bool>;
+ }) {
+ return static_cast<ResultT *>(Value)->invalidate(Function, Preserved);
+ }
+ return true;
+ };
+ return registerAnalysisImpl(std::move(Registration), std::move(Runner), std::move(Invalidator));
+ }
+
+ template <typename AnalysisT>
+ [[nodiscard]] Expected<typename AnalysisT::Result *> getResult(MachineFunction &Function,
+ const TargetDescription &Target) {
+ const AnalysisID Key = id<AnalysisT>();
+ if (!isRegistered(Key)) {
+ auto Registered = registerAnalysis<AnalysisT>();
+ if (!Registered)
+ return std::unexpected(Registered.error());
+ }
+ auto Result = getResultImpl(Key, Function, Target);
+ if (!Result)
+ return std::unexpected(Result.error());
+ return static_cast<typename AnalysisT::Result *>(*Result);
+ }
+
+ void invalidate(MachineFunction &Function, const PreservedAnalyses &Preserved,
+ MachinePassChanges Changes) noexcept;
+ void invalidate(MachineFunction &Function, const PreservedAnalyses &Preserved) noexcept {
+ invalidate(Function, Preserved, MachinePassChanges::all());
+ }
+ [[nodiscard]] bool hasCached(const MachineFunction &Function, AnalysisID ID) const noexcept;
+ [[nodiscard]] bool isRegistered(AnalysisID ID) const noexcept;
+ [[nodiscard]] std::size_t cachedResultCount(const MachineFunction &Function) const noexcept;
+ void clear(MachineFunction &Function) noexcept;
+ void clearAll() noexcept;
+
+ [[nodiscard]] Expected<std::unique_ptr<MachineAnalysisManager>> forkEmpty() const;
+ [[nodiscard]] Expected<bool>
+ requestAction(std::string_view Action, const MachineFunction &Function, std::uint64_t Cost = 1);
+
+private:
+ using ErasedRunner = std::function<Expected<std::shared_ptr<void>>(
+ MachineFunction &, const TargetDescription &, MachineAnalysisManager &)>;
+ using ErasedInvalidator =
+ std::function<bool(void *, const MachineFunction &, const PreservedAnalyses &)>;
+
+ struct RegisteredAnalysis final {
+ MachineAnalysisRegistration Info;
+ ErasedRunner Runner;
+ ErasedInvalidator Invalidator;
+ };
+
+ struct CacheEntry final {
+ std::shared_ptr<void> Value;
+ AnalysisID ID = nullptr;
+ };
+
+ [[nodiscard]] Expected<void> registerAnalysisImpl(MachineAnalysisRegistration Registration,
+ ErasedRunner Runner,
+ ErasedInvalidator Invalidator);
+ [[nodiscard]] Expected<void *> getResultImpl(AnalysisID ID, MachineFunction &Function,
+ const TargetDescription &Target);
+ [[nodiscard]] Expected<void> materializeDependencies(const RegisteredAnalysis &Registration,
+ MachineFunction &Function,
+ const TargetDescription &Target);
+ [[nodiscard]] Expected<void>
+ validateParallelRequirements(std::span<const AnalysisID> RequiredAnalyses) const;
+ void setInstrumentation(MachinePassInstrumentation *NewInstrumentation) noexcept {
+ Instrumentation = NewInstrumentation;
+ }
+
+ std::unordered_map<AnalysisID, RegisteredAnalysis> Registry;
+ std::unordered_map<MachineFunction *, std::unordered_map<AnalysisID, CacheEntry>> Cache;
+ std::unordered_map<MachineFunction *, std::vector<AnalysisID>> ActiveComputations;
+ MachinePassInstrumentation *Instrumentation = nullptr;
+
+ friend class MachinePassManager;
+ friend class CompilationUnitPassManager;
+ friend Expected<void> registerStage8MachineAnalyses(MachineAnalysisManager &Analyses);
+};
+
+class CompilationUnitAnalysisManager final {
+public:
+ CompilationUnitAnalysisManager() = default;
+ CompilationUnitAnalysisManager(const CompilationUnitAnalysisManager &) = delete;
+ CompilationUnitAnalysisManager &operator=(const CompilationUnitAnalysisManager &) = delete;
+ CompilationUnitAnalysisManager(CompilationUnitAnalysisManager &&) = delete;
+ CompilationUnitAnalysisManager &operator=(CompilationUnitAnalysisManager &&) = delete;
+ ~CompilationUnitAnalysisManager() = default;
+
+ template <typename AnalysisT> [[nodiscard]] static AnalysisID id() noexcept {
+ return PreservedAnalyses::analysisID<AnalysisT>();
+ }
+
+ template <typename AnalysisT>
+ [[nodiscard]] Expected<void>
+ registerAnalysis(CompilationUnitAnalysisRegistration Registration = {}) {
+ using ResultT = typename AnalysisT::Result;
+ if (Registration.ID == nullptr)
+ Registration.ID = id<AnalysisT>();
+ if (Registration.Name.empty()) {
+ if constexpr (requires {
+ { AnalysisT::name() } -> std::convertible_to<std::string_view>;
+ }) {
+ Registration.Name = std::string(AnalysisT::name());
+ } else {
+ Registration.Name = "unnamed-compilation-unit-analysis";
+ }
+ }
+
+ ErasedRunner Runner =
+ [](CompilationUnit &Unit, const TargetDescription &Target,
+ CompilationUnitAnalysisManager &Manager) -> Expected<std::shared_ptr<void>> {
+ auto Computed = AnalysisT::run(Unit, Target, Manager);
+ if (!Computed)
+ return std::unexpected(Computed.error());
+ return std::shared_ptr<void>(new ResultT(std::move(*Computed)),
+ [](void *Value) { delete static_cast<ResultT *>(Value); });
+ };
+ ErasedInvalidator Invalidator = [](void *Value, const CompilationUnit &Unit,
+ const PreservedAnalyses &Preserved) -> bool {
+ if constexpr (requires(ResultT &Result, const CompilationUnit &InputUnit,
+ const PreservedAnalyses &Analyses) {
+ { Result.invalidate(InputUnit, Analyses) } -> std::convertible_to<bool>;
+ }) {
+ return static_cast<ResultT *>(Value)->invalidate(Unit, Preserved);
+ }
+ return true;
+ };
+ return registerAnalysisImpl(std::move(Registration), std::move(Runner), std::move(Invalidator));
+ }
+
+ template <typename AnalysisT>
+ [[nodiscard]] Expected<typename AnalysisT::Result *> getResult(CompilationUnit &Unit,
+ const TargetDescription &Target) {
+ const AnalysisID Key = id<AnalysisT>();
+ if (!isRegistered(Key)) {
+ auto Registered = registerAnalysis<AnalysisT>();
+ if (!Registered)
+ return std::unexpected(Registered.error());
+ }
+ auto Result = getResultImpl(Key, Unit, Target);
+ if (!Result)
+ return std::unexpected(Result.error());
+ return static_cast<typename AnalysisT::Result *>(*Result);
+ }
+
+ void invalidate(CompilationUnit &Unit, const PreservedAnalyses &Preserved,
+ MachinePassChanges Changes) noexcept;
+ [[nodiscard]] bool hasCached(const CompilationUnit &Unit, AnalysisID ID) const noexcept;
+ [[nodiscard]] bool isRegistered(AnalysisID ID) const noexcept;
+ void clear(CompilationUnit &Unit) noexcept;
+ void clearAll() noexcept;
+
+private:
+ using ErasedRunner = std::function<Expected<std::shared_ptr<void>>(
+ CompilationUnit &, const TargetDescription &, CompilationUnitAnalysisManager &)>;
+ using ErasedInvalidator =
+ std::function<bool(void *, const CompilationUnit &, const PreservedAnalyses &)>;
+
+ struct RegisteredAnalysis final {
+ CompilationUnitAnalysisRegistration Info;
+ ErasedRunner Runner;
+ ErasedInvalidator Invalidator;
+ };
+
+ struct CacheEntry final {
+ std::shared_ptr<void> Value;
+ AnalysisID ID = nullptr;
+ };
+
+ [[nodiscard]] Expected<void>
+ registerAnalysisImpl(CompilationUnitAnalysisRegistration Registration, ErasedRunner Runner,
+ ErasedInvalidator Invalidator);
+ [[nodiscard]] Expected<void *> getResultImpl(AnalysisID ID, CompilationUnit &Unit,
+ const TargetDescription &Target);
+ [[nodiscard]] Expected<void> materializeDependencies(const RegisteredAnalysis &Registration,
+ CompilationUnit &Unit,
+ const TargetDescription &Target);
+ void setInstrumentation(MachinePassInstrumentation *NewInstrumentation) noexcept {
+ Instrumentation = NewInstrumentation;
+ }
+
+ std::unordered_map<AnalysisID, RegisteredAnalysis> Registry;
+ std::unordered_map<CompilationUnit *, std::unordered_map<AnalysisID, CacheEntry>> Cache;
+ std::unordered_map<CompilationUnit *, std::vector<AnalysisID>> ActiveComputations;
+ MachinePassInstrumentation *Instrumentation = nullptr;
+
+ friend class CompilationUnitPassManager;
+};
+
+class MachineFunctionPass {
+public:
+ MachineFunctionPass() = default;
+ MachineFunctionPass(const MachineFunctionPass &) = delete;
+ MachineFunctionPass &operator=(const MachineFunctionPass &) = delete;
+ virtual ~MachineFunctionPass() = default;
+
+ [[nodiscard]] virtual const MachinePassContract &contract() const noexcept = 0;
+ [[nodiscard]] virtual Expected<MachinePassResult> run(MachineFunction &Function,
+ const TargetDescription &Target,
+ MachineAnalysisManager &Analyses) = 0;
+};
+
+class CompilationUnitPass {
+public:
+ CompilationUnitPass() = default;
+ CompilationUnitPass(const CompilationUnitPass &) = delete;
+ CompilationUnitPass &operator=(const CompilationUnitPass &) = delete;
+ virtual ~CompilationUnitPass() = default;
+
+ [[nodiscard]] virtual const CompilationUnitPassContract &contract() const noexcept = 0;
+ [[nodiscard]] virtual Expected<CompilationUnitPassResult>
+ run(CompilationUnit &Unit, const TargetDescription &Target,
+ CompilationUnitAnalysisManager &UnitAnalyses, MachineAnalysisManager &FunctionAnalyses) = 0;
+};
+
+enum class InstrumentationPhase : std::uint8_t {
+ Before,
+ After,
+};
+
+struct InstrumentationStatistic final {
+ std::string Name;
+ bool IsAnalysis = false;
+ std::uint64_t Invocations = 0;
+ std::uint64_t CacheHits = 0;
+ std::uint64_t Changes = 0;
+ std::uint64_t Failures = 0;
+ std::uint64_t TotalNanoseconds = 0;
+};
+
+class MachinePassInstrumentation final {
+public:
+ using BeforeCallback = std::function<void(std::string_view, const MachineFunction &)>;
+ using AfterCallback =
+ std::function<void(std::string_view, const MachineFunction &, const MachinePassResult &)>;
+ using FailureCallback =
+ std::function<void(std::string_view, const MachineFunction &, const Error &)>;
+ using BeforeAnalysisCallback = std::function<void(std::string_view, const MachineFunction &)>;
+ using AfterAnalysisCallback =
+ std::function<void(std::string_view, const MachineFunction &, bool, std::uint64_t)>;
+ using AnalysisFailureCallback =
+ std::function<void(std::string_view, const MachineFunction &, const Error &)>;
+ using IRPrintCallback =
+ std::function<void(InstrumentationPhase, std::string_view, const MachineFunction &)>;
+ using ChangeCallback =
+ std::function<void(std::string_view, const MachineFunction &, MachinePassChanges, bool)>;
+ using ActionFilter =
+ std::function<bool(std::string_view, const MachineFunction &, std::uint64_t)>;
+
+ using BeforeUnitCallback = std::function<void(std::string_view, const CompilationUnit &)>;
+ using AfterUnitCallback = std::function<void(std::string_view, const CompilationUnit &,
+ const CompilationUnitPassResult &)>;
+ using UnitFailureCallback =
+ std::function<void(std::string_view, const CompilationUnit &, const Error &)>;
+ using BeforeUnitAnalysisCallback = std::function<void(std::string_view, const CompilationUnit &)>;
+ using AfterUnitAnalysisCallback =
+ std::function<void(std::string_view, const CompilationUnit &, bool, std::uint64_t)>;
+ using UnitAnalysisFailureCallback =
+ std::function<void(std::string_view, const CompilationUnit &, const Error &)>;
+
+ void addBeforeCallback(BeforeCallback Callback);
+ void addAfterCallback(AfterCallback Callback);
+ void addFailureCallback(FailureCallback Callback);
+ void addBeforeAnalysisCallback(BeforeAnalysisCallback Callback);
+ void addAfterAnalysisCallback(AfterAnalysisCallback Callback);
+ void addAnalysisFailureCallback(AnalysisFailureCallback Callback);
+ void addIRPrintCallback(IRPrintCallback Callback);
+ void addChangeCallback(ChangeCallback Callback);
+ void setActionFilter(ActionFilter Filter);
+ void setPerFunctionFuel(std::optional<std::uint64_t> Fuel);
+
+ void addBeforeUnitCallback(BeforeUnitCallback Callback);
+ void addAfterUnitCallback(AfterUnitCallback Callback);
+ void addUnitFailureCallback(UnitFailureCallback Callback);
+ void addBeforeUnitAnalysisCallback(BeforeUnitAnalysisCallback Callback);
+ void addAfterUnitAnalysisCallback(AfterUnitAnalysisCallback Callback);
+ void addUnitAnalysisFailureCallback(UnitAnalysisFailureCallback Callback);
+
+ [[nodiscard]] std::vector<InstrumentationStatistic> statistics() const;
+ void clearStatistics();
+
+private:
+ struct BufferedEvents final {
+ const TargetDescription *Target = nullptr;
+ std::vector<std::function<void()>> Events;
+ };
+
+ [[nodiscard]] bool bufferFunctionEvent(const MachineFunction &Function,
+ std::function<void(const MachineFunction &)> Replay) const;
+ void beginBuffering(BufferedEvents &Buffer, const TargetDescription &Target) const noexcept;
+ void endBuffering() const noexcept;
+ void replayBufferedEvents(BufferedEvents &Buffer) const;
+
+ void before(std::string_view Name, const MachineFunction &Function) const;
+ void after(std::string_view Name, const MachineFunction &Function,
+ const MachinePassResult &Result, std::uint64_t Nanoseconds) const;
+ void failure(std::string_view Name, const MachineFunction &Function, const Error &ErrorValue,
+ std::uint64_t Nanoseconds = 0) const;
+ void beforeAnalysis(std::string_view Name, const MachineFunction &Function) const;
+ void afterAnalysis(std::string_view Name, const MachineFunction &Function, bool CacheHit,
+ std::uint64_t Nanoseconds) const;
+ void analysisFailure(std::string_view Name, const MachineFunction &Function,
+ const Error &ErrorValue, std::uint64_t Nanoseconds) const;
+ void change(std::string_view Name, const MachineFunction &Function, MachinePassChanges Changes,
+ bool FingerprintChanged) const;
+ [[nodiscard]] Expected<bool> requestAction(std::string_view Action,
+ const MachineFunction &Function, std::uint64_t Cost);
+
+ void beforeUnit(std::string_view Name, const CompilationUnit &Unit) const;
+ void afterUnit(std::string_view Name, const CompilationUnit &Unit,
+ const CompilationUnitPassResult &Result, std::uint64_t Nanoseconds) const;
+ void unitFailure(std::string_view Name, const CompilationUnit &Unit, const Error &ErrorValue,
+ std::uint64_t Nanoseconds = 0) const;
+ void beforeUnitAnalysis(std::string_view Name, const CompilationUnit &Unit) const;
+ void afterUnitAnalysis(std::string_view Name, const CompilationUnit &Unit, bool CacheHit,
+ std::uint64_t Nanoseconds) const;
+ void unitAnalysisFailure(std::string_view Name, const CompilationUnit &Unit,
+ const Error &ErrorValue, std::uint64_t Nanoseconds) const;
+
+ [[nodiscard]] InstrumentationStatistic &statistic(std::string_view Name, bool IsAnalysis) const;
+
+ mutable std::mutex Mutex;
+ mutable std::mutex ActionMutex;
+ std::vector<BeforeCallback> Before;
+ std::vector<AfterCallback> After;
+ std::vector<FailureCallback> Failure;
+ std::vector<BeforeAnalysisCallback> BeforeAnalysis;
+ std::vector<AfterAnalysisCallback> AfterAnalysis;
+ std::vector<AnalysisFailureCallback> AnalysisFailure;
+ std::vector<IRPrintCallback> IRPrint;
+ std::vector<ChangeCallback> Change;
+ ActionFilter Filter;
+ std::optional<std::uint64_t> PerFunctionFuel;
+ std::unordered_map<std::uint64_t, std::uint64_t> ConsumedFuel;
+
+ std::vector<BeforeUnitCallback> BeforeUnit;
+ std::vector<AfterUnitCallback> AfterUnit;
+ std::vector<UnitFailureCallback> UnitFailure;
+ std::vector<BeforeUnitAnalysisCallback> BeforeUnitAnalysis;
+ std::vector<AfterUnitAnalysisCallback> AfterUnitAnalysis;
+ std::vector<UnitAnalysisFailureCallback> UnitAnalysisFailure;
+ mutable std::unordered_map<std::string, InstrumentationStatistic> Statistics;
+
+ static thread_local BufferedEvents *ActiveBuffer;
+
+ friend class CompilationUnitAnalysisManager;
+ friend class CompilationUnitPassManager;
+ friend class MachineAnalysisManager;
+ friend class MachinePassManager;
+};
+
+struct MachinePassManagerOptions final {
+ bool VerifyBefore = true;
+ bool VerifyEach = true;
+ bool DetectUnreportedChanges = false;
+};
+
+class MachinePassManager final {
+public:
+ explicit MachinePassManager(MachinePassManagerOptions Options = {}) : Options(Options) {}
+
+ MachinePassManager(const MachinePassManager &) = delete;
+ MachinePassManager &operator=(const MachinePassManager &) = delete;
+
+ [[nodiscard]] Expected<void> addPass(std::unique_ptr<MachineFunctionPass> Pass);
+ [[nodiscard]] Expected<void> run(MachineFunction &Function, const TargetDescription &Target,
+ MachineAnalysisManager &Analyses,
+ MachinePassInstrumentation *Instrumentation = nullptr) const;
+ [[nodiscard]] Expected<void>
+ runFunctions(CompilationUnit &Unit, const TargetDescription &Target,
+ const MachineAnalysisManager &AnalysisPrototype, std::size_t ThreadCount,
+ MachinePassInstrumentation *Instrumentation = nullptr) const;
+
+ [[nodiscard]] std::size_t size() const noexcept {
+ return Passes.size();
+ }
+
+private:
+ [[nodiscard]] Expected<void> validateContract(const MachinePassContract &Contract) const;
+
+ MachinePassManagerOptions Options;
+ std::vector<std::unique_ptr<MachineFunctionPass>> Passes;
+};
+
+struct CompilationUnitPassManagerOptions final {
+ bool VerifyBefore = true;
+ bool VerifyEach = true;
+};
+
+class CompilationUnitPassManager final {
+public:
+ explicit CompilationUnitPassManager(CompilationUnitPassManagerOptions Options = {})
+ : Options(Options) {}
+
+ CompilationUnitPassManager(const CompilationUnitPassManager &) = delete;
+ CompilationUnitPassManager &operator=(const CompilationUnitPassManager &) = delete;
+
+ [[nodiscard]] Expected<void> addPass(std::unique_ptr<CompilationUnitPass> Pass);
+ [[nodiscard]] Expected<void> run(CompilationUnit &Unit, const TargetDescription &Target,
+ CompilationUnitAnalysisManager &UnitAnalyses,
+ MachineAnalysisManager &FunctionAnalyses,
+ MachinePassInstrumentation *Instrumentation = nullptr) const;
+
+private:
+ CompilationUnitPassManagerOptions Options;
+ std::vector<std::unique_ptr<CompilationUnitPass>> Passes;
+};
+
+} // namespace rcc
+
+#endif // RCC_CODEGEN_MACHINEPASS_H
diff --git a/backend/include/rcc/CodeGen/TargetCallLowering.h b/backend/include/rcc/CodeGen/TargetCallLowering.h
new file mode 100644
index 0000000..a6b3535
--- /dev/null
+++ b/backend/include/rcc/CodeGen/TargetCallLowering.h
@@ -0,0 +1,404 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_CODEGEN_TARGETCALLLOWERING_H
+#define RCC_CODEGEN_TARGETCALLLOWERING_H
+
+#include "rcc/CodeGen/LowLevelType.h"
+#include "rcc/CodeGen/MachineIRTypes.h"
+#include "rcc/CodeGen/Register.h"
+#include "rcc/CodeGen/RegisterMask.h"
+#include "rcc/Support/Error.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <initializer_list>
+#include <memory>
+#include <optional>
+#include <span>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <variant>
+#include <vector>
+
+namespace rcc {
+
+class MachineIRBuilder;
+
+enum class ABIExtensionKind : std::uint8_t {
+ None,
+ AnyExtend,
+ SignExtend,
+ ZeroExtend,
+ Count,
+};
+
+enum class ABIValueFlag : std::uint8_t {
+ ByVal,
+ StructReturn,
+ Indirect,
+ InRegister,
+ Count,
+};
+
+class ABIValueFlags final {
+public:
+ constexpr ABIValueFlags() noexcept = default;
+
+ ABIValueFlags(std::initializer_list<ABIValueFlag> Flags) noexcept;
+
+ [[nodiscard]] constexpr bool has(ABIValueFlag Flag) const noexcept {
+ const auto Index = static_cast<std::uint32_t>(Flag);
+ return Index < static_cast<std::uint32_t>(ABIValueFlag::Count) &&
+ (Bits & (std::uint8_t{1} << Index)) != 0;
+ }
+
+ constexpr void set(ABIValueFlag Flag) noexcept {
+ const auto Index = static_cast<std::uint32_t>(Flag);
+ if (Index < static_cast<std::uint32_t>(ABIValueFlag::Count))
+ Bits |= std::uint8_t{1} << Index;
+ }
+
+ [[nodiscard]] constexpr std::uint8_t rawValue() const noexcept {
+ return Bits;
+ }
+
+ friend constexpr bool operator==(ABIValueFlags Left, ABIValueFlags Right) noexcept = default;
+
+private:
+ std::uint8_t Bits = 0;
+};
+
+struct ABIValue final {
+ Register Value;
+ LLT Type;
+ std::uint32_t OriginalValue = 0;
+ std::uint16_t FragmentIndex = 0;
+ std::uint16_t FragmentCount = 1;
+ std::uint32_t FragmentOffsetBytes = 0;
+ Alignment OriginalAlignment;
+ ABIExtensionKind Extension = ABIExtensionKind::None;
+ ABIValueFlags Flags;
+ std::uint64_t ByValSizeBytes = 0;
+ Alignment ByValAlignment;
+
+ friend bool operator==(const ABIValue &Left, const ABIValue &Right) = default;
+};
+
+enum class MachineCallTargetKind : std::uint8_t {
+ Invalid,
+ GlobalSymbol,
+ ExternalSymbol,
+ IndirectRegister,
+};
+
+class MachineCallTarget final {
+public:
+ constexpr MachineCallTarget() noexcept = default;
+
+ [[nodiscard]] static Expected<MachineCallTarget> globalSymbol(std::string Name,
+ std::uint32_t TargetFlags = 0);
+ [[nodiscard]] static Expected<MachineCallTarget> externalSymbol(std::string Name,
+ std::uint32_t TargetFlags = 0);
+ [[nodiscard]] static Expected<MachineCallTarget> indirect(Register Callee,
+ std::uint32_t TargetFlags = 0);
+
+ [[nodiscard]] MachineCallTargetKind kind() const noexcept {
+ return Kind;
+ }
+
+ [[nodiscard]] std::string_view symbolName() const noexcept;
+ [[nodiscard]] Register indirectRegister() const noexcept;
+ [[nodiscard]] std::uint32_t targetFlags() const noexcept {
+ return TargetFlags;
+ }
+
+ [[nodiscard]] bool isValid() const noexcept {
+ return Kind != MachineCallTargetKind::Invalid;
+ }
+
+ friend bool operator==(const MachineCallTarget &Left, const MachineCallTarget &Right) = default;
+
+private:
+ using Storage = std::variant<std::monostate, std::string, Register>;
+
+ MachineCallTarget(MachineCallTargetKind Kind, Storage Value, std::uint32_t TargetFlags) noexcept
+ : Kind(Kind), Value(std::move(Value)), TargetFlags(TargetFlags) {}
+
+ MachineCallTargetKind Kind = MachineCallTargetKind::Invalid;
+ Storage Value;
+ std::uint32_t TargetFlags = 0;
+};
+
+enum class TailCallKind : std::uint8_t {
+ NotTail,
+ Tail,
+ MustTail,
+};
+
+struct TailCallContext final {
+ CallingConventionID CallerConvention;
+ std::vector<ABIValue> CallerReturns;
+ std::uint64_t ReusableIncomingArgumentBytes = 0;
+ std::size_t CallerFixedArgumentCount = 0;
+ bool CallerIsVariadic = false;
+ bool CallerHasDynamicStackAllocation = false;
+ bool CallerUsesStructReturn = false;
+};
+
+struct FormalArgumentRequest final {
+ CallingConventionID Convention;
+ std::vector<ABIValue> Arguments;
+ std::size_t FixedArgumentCount = 0;
+ bool IsVariadic = false;
+};
+
+struct CallLoweringRequest final {
+ CallingConventionID Convention;
+ MachineCallTarget Callee;
+ std::vector<ABIValue> Arguments;
+ std::vector<ABIValue> Results;
+ std::size_t FixedArgumentCount = 0;
+ std::uint64_t CallSiteID = 0;
+ bool IsVariadic = false;
+ TailCallKind TailKind = TailCallKind::NotTail;
+ std::optional<TailCallContext> TailContext;
+};
+
+struct ReturnLoweringRequest final {
+ CallingConventionID Convention;
+ std::vector<ABIValue> Values;
+ bool FunctionIsVariadic = false;
+};
+
+enum class CCLocationKind : std::uint8_t {
+ Register,
+ Stack,
+ Indirect,
+ Custom,
+};
+
+enum class CCAssignmentFlag : std::uint8_t {
+ Variadic,
+ ByVal,
+ StructReturn,
+ Indirect,
+ InRegister,
+ Bitcast,
+ Count,
+};
+
+class CCAssignmentFlags final {
+public:
+ constexpr CCAssignmentFlags() noexcept = default;
+
+ CCAssignmentFlags(std::initializer_list<CCAssignmentFlag> Flags) noexcept;
+
+ [[nodiscard]] constexpr bool has(CCAssignmentFlag Flag) const noexcept {
+ const auto Index = static_cast<std::uint32_t>(Flag);
+ return Index < static_cast<std::uint32_t>(CCAssignmentFlag::Count) &&
+ (Bits & (std::uint8_t{1} << Index)) != 0;
+ }
+
+ constexpr void set(CCAssignmentFlag Flag) noexcept {
+ const auto Index = static_cast<std::uint32_t>(Flag);
+ if (Index < static_cast<std::uint32_t>(CCAssignmentFlag::Count))
+ Bits |= std::uint8_t{1} << Index;
+ }
+
+ [[nodiscard]] constexpr std::uint8_t rawValue() const noexcept {
+ return Bits;
+ }
+
+ friend constexpr bool operator==(CCAssignmentFlags Left,
+ CCAssignmentFlags Right) noexcept = default;
+
+private:
+ std::uint8_t Bits = 0;
+};
+
+struct CCAssignment final {
+ std::size_t ValueIndex = 0;
+ Register OriginalValue;
+ std::uint32_t OriginalValueIndex = 0;
+ std::uint16_t FragmentIndex = 0;
+ std::uint16_t FragmentCount = 1;
+ LLT ValueType;
+ LLT LocationType;
+ CCLocationKind LocationKind = CCLocationKind::Register;
+ std::optional<PhysicalRegister> LocationRegister;
+ std::optional<std::uint64_t> StackOffsetBytes;
+ std::uint64_t LocationSizeBytes = 0;
+ std::uint64_t StackSlotSizeBytes = 0;
+ Alignment LocationAlignment;
+ ABIExtensionKind Extension = ABIExtensionKind::None;
+ CCAssignmentFlags CustomFlags;
+
+ friend bool operator==(const CCAssignment &Left, const CCAssignment &Right) = default;
+};
+
+struct StackArgumentArea final {
+ std::uint64_t SizeBytes = 0;
+ std::uint64_t ShadowSpaceBytes = 0;
+ Alignment Align;
+
+ friend bool operator==(StackArgumentArea Left, StackArgumentArea Right) noexcept = default;
+};
+
+struct CallFrameRequirements final {
+ std::uint64_t SizeBytes = 0;
+ Alignment Align;
+ bool EmitSetup = false;
+ bool EmitDestroy = false;
+ bool ReusesIncomingArea = false;
+
+ friend bool operator==(CallFrameRequirements Left,
+ CallFrameRequirements Right) noexcept = default;
+};
+
+struct VarArgRegisterSave final {
+ PhysicalRegister Reg;
+ std::uint64_t OffsetBytes = 0;
+
+ friend bool operator==(VarArgRegisterSave Left, VarArgRegisterSave Right) noexcept = default;
+};
+
+struct VarArgLoweringState final {
+ bool IsVariadic = false;
+ std::size_t FixedArgumentCount = 0;
+ std::size_t FirstUnallocatedIntegerRegister = 0;
+ std::uint64_t OverflowAreaOffsetBytes = 0;
+ std::int64_t RegisterSaveAreaOffsetBytes = 0;
+ std::uint64_t RegisterSaveAreaSizeBytes = 0;
+ std::uint64_t RegisterSavePaddingBytes = 0;
+ std::vector<VarArgRegisterSave> RegistersToSave;
+
+ friend bool operator==(const VarArgLoweringState &Left,
+ const VarArgLoweringState &Right) = default;
+};
+
+struct ByValCopyAssignment final {
+ std::size_t ArgumentAssignmentIndex = 0;
+ Register Source;
+ LLT PointerType;
+ std::uint64_t StackOffsetBytes = 0;
+ std::uint64_t SizeBytes = 0;
+ Alignment Align;
+
+ friend bool operator==(ByValCopyAssignment Left, ByValCopyAssignment Right) noexcept = default;
+};
+
+enum class CallLoweringPlanKind : std::uint8_t {
+ FormalArguments,
+ Call,
+ Return,
+};
+
+struct CallLoweringPlanDesc final {
+ CallLoweringPlanKind Kind = CallLoweringPlanKind::FormalArguments;
+ std::string Target;
+ CallingConventionID Convention;
+ std::vector<CCAssignment> ArgumentAssignments;
+ std::vector<CCAssignment> ReturnAssignments;
+ StackArgumentArea StackArguments;
+ CallFrameRequirements CallFrame;
+ VarArgLoweringState VarArgs;
+ std::vector<ByValCopyAssignment> ByValCopies;
+ std::shared_ptr<const RegisterMask> PreservedMask;
+ std::optional<MachineCallTarget> Callee;
+ std::uint64_t CallSiteID = 0;
+ TailCallKind TailDecision = TailCallKind::NotTail;
+ std::string TailRejectionReason;
+};
+
+class CallLoweringPlan final {
+public:
+ [[nodiscard]] static Expected<CallLoweringPlan> create(CallLoweringPlanDesc Description);
+
+ [[nodiscard]] CallLoweringPlanKind kind() const noexcept {
+ return Description.Kind;
+ }
+
+ [[nodiscard]] std::string_view target() const noexcept {
+ return Description.Target;
+ }
+
+ [[nodiscard]] CallingConventionID convention() const noexcept {
+ return Description.Convention;
+ }
+
+ [[nodiscard]] std::span<const CCAssignment> argumentAssignments() const noexcept {
+ return Description.ArgumentAssignments;
+ }
+
+ [[nodiscard]] std::span<const CCAssignment> returnAssignments() const noexcept {
+ return Description.ReturnAssignments;
+ }
+
+ [[nodiscard]] const StackArgumentArea &stackArguments() const noexcept {
+ return Description.StackArguments;
+ }
+
+ [[nodiscard]] const CallFrameRequirements &callFrame() const noexcept {
+ return Description.CallFrame;
+ }
+
+ [[nodiscard]] const VarArgLoweringState &varArgs() const noexcept {
+ return Description.VarArgs;
+ }
+
+ [[nodiscard]] std::span<const ByValCopyAssignment> byValCopies() const noexcept {
+ return Description.ByValCopies;
+ }
+
+ [[nodiscard]] const std::shared_ptr<const RegisterMask> &preservedMask() const noexcept {
+ return Description.PreservedMask;
+ }
+
+ [[nodiscard]] const MachineCallTarget *callee() const noexcept {
+ return Description.Callee ? &*Description.Callee : nullptr;
+ }
+
+ [[nodiscard]] std::uint64_t callSiteID() const noexcept {
+ return Description.CallSiteID;
+ }
+
+ [[nodiscard]] TailCallKind tailDecision() const noexcept {
+ return Description.TailDecision;
+ }
+
+ [[nodiscard]] std::string_view tailRejectionReason() const noexcept {
+ return Description.TailRejectionReason;
+ }
+
+private:
+ explicit CallLoweringPlan(CallLoweringPlanDesc Description)
+ : Description(std::move(Description)) {}
+
+ CallLoweringPlanDesc Description;
+};
+
+class TargetCallLowering {
+public:
+ virtual ~TargetCallLowering() = default;
+
+ [[nodiscard]] Expected<void> lowerFormalArguments(MachineIRBuilder &Builder,
+ const FormalArgumentRequest &Request) const;
+ [[nodiscard]] Expected<void> lowerCall(MachineIRBuilder &Builder,
+ const CallLoweringRequest &Request) const;
+ [[nodiscard]] Expected<void> lowerReturn(MachineIRBuilder &Builder,
+ const ReturnLoweringRequest &Request) const;
+
+ [[nodiscard]] virtual Expected<CallLoweringPlan>
+ planFormalArguments(const FormalArgumentRequest &Request) const = 0;
+ [[nodiscard]] virtual Expected<CallLoweringPlan>
+ planCall(const CallLoweringRequest &Request) const = 0;
+ [[nodiscard]] virtual Expected<CallLoweringPlan>
+ planReturn(const ReturnLoweringRequest &Request) const = 0;
+ [[nodiscard]] virtual Expected<void> emitPlan(MachineIRBuilder &Builder,
+ const CallLoweringPlan &Plan) const = 0;
+};
+
+} // namespace rcc
+
+#endif // RCC_CODEGEN_TARGETCALLLOWERING_H
diff --git a/backend/include/rcc/Target/RISCV/RISCVCallLowering.h b/backend/include/rcc/Target/RISCV/RISCVCallLowering.h
new file mode 100644
index 0000000..445c922
--- /dev/null
+++ b/backend/include/rcc/Target/RISCV/RISCVCallLowering.h
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_TARGET_RISCV_RISCVCALLLOWERING_H
+#define RCC_TARGET_RISCV_RISCVCALLLOWERING_H
+
+#include "rcc/CodeGen/TargetCallLowering.h"
+#include "rcc/Target/RISCV/RISCVInstructions.h"
+
+#include <cstdint>
+
+namespace rcc::riscv {
+
+enum class CallTargetFlag : std::uint32_t {
+ DirectCall = 1U << 0U,
+};
+
+enum class CallStackObjectFlag : std::uint64_t {
+ IncomingArgument = 1ULL << 0U,
+ OutgoingArgument = 1ULL << 1U,
+ ByValCopy = 1ULL << 2U,
+ VarArgSaveArea = 1ULL << 3U,
+ VarArgAlignmentPadding = 1ULL << 4U,
+};
+
+enum class CallValueTransform : std::uint8_t {
+ Full,
+ AnyExtend,
+ SignExtend,
+ ZeroExtend,
+ Truncate,
+ Bitcast,
+};
+
+[[nodiscard]] constexpr CallingConventionID
+callingConventionID(CallingConvention Convention) noexcept {
+ return CallingConventionID::fromValue(static_cast<std::uint32_t>(Convention));
+}
+
+[[nodiscard]] const TargetCallLowering &callLowering() noexcept;
+
+} // namespace rcc::riscv
+
+#endif // RCC_TARGET_RISCV_RISCVCALLLOWERING_H
diff --git a/backend/lib/CodeGen/CMakeLists.txt b/backend/lib/CodeGen/CMakeLists.txt
index 34e9ef6..5a12055 100644
--- a/backend/lib/CodeGen/CMakeLists.txt
+++ b/backend/lib/CodeGen/CMakeLists.txt
@@ -3,6 +3,7 @@
set(
RCCBackendCodeGenSources
CompilationUnit.cpp
+ GenericOpcodes.cpp
GenericConstant.cpp
InstructionDescriptor.cpp
LowLevelType.cpp
@@ -19,11 +20,13 @@ set(
MachineFunctionCloner.cpp
MachineIRTypes.cpp
MachineIRBuilder.cpp
+ MachineAnalyses.cpp
MachineInstr.cpp
MachineJumpTableInfo.cpp
MachineMemOperand.cpp
MachineMetadata.cpp
MachineOperand.cpp
+ MachinePass.cpp
MachineRegisterInfo.cpp
MachineVerifier.cpp
MachineFunctionProperties.cpp
@@ -31,6 +34,7 @@ set(
Register.cpp
RegisterMask.cpp
TargetConstant.cpp
+ TargetCallLowering.cpp
TargetDescription.cpp
TargetFunctionState.cpp
TargetMIRTextInfo.cpp
@@ -39,6 +43,7 @@ set(
set(
RCCBackendCodeGenHeaders
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/CompilationUnit.h"
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/GenericOpcodes.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/GenericConstant.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/InstructionDescriptor.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/LowLevelType.h"
@@ -55,11 +60,13 @@ set(
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineFunctionCloner.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineIRTypes.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineIRBuilder.h"
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineAnalyses.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineInstr.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineJumpTableInfo.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineMemOperand.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineMetadata.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineOperand.h"
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachinePass.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineRegisterInfo.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineVerifier.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineFunctionProperties.h"
@@ -68,6 +75,7 @@ set(
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/RegisterMask.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/StableID.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/TargetConstant.h"
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/TargetCallLowering.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/TargetDescription.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/TargetFunctionState.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/TargetMIRTextInfo.h"
@@ -104,6 +112,7 @@ install(
rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/CompilationUnit.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/GenericOpcodes.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/GenericConstant.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptor.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/LowLevelType.cpp"
@@ -120,11 +129,13 @@ rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionCloner.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineIRTypes.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineIRBuilder.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MachineAnalyses.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineInstr.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineJumpTableInfo.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineMemOperand.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineMetadata.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineOperand.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MachinePass.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineRegisterInfo.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifier.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionProperties.cpp"
@@ -132,6 +143,7 @@ rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/Register.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RegisterMask.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetConstant.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/TargetCallLowering.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetDescription.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetFunctionState.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetMIRTextInfo.cpp"
@@ -140,6 +152,7 @@ rcc_backend_register_format_files(
)
rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/CompilationUnit.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/GenericOpcodes.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/GenericConstant.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptor.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/LowLevelType.cpp"
@@ -156,11 +169,13 @@ rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionCloner.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineIRTypes.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineIRBuilder.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MachineAnalyses.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineInstr.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineJumpTableInfo.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineMemOperand.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineMetadata.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineOperand.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MachinePass.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineRegisterInfo.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifier.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionProperties.cpp"
@@ -168,6 +183,7 @@ rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/Register.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RegisterMask.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetConstant.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/TargetCallLowering.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetDescription.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetFunctionState.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetMIRTextInfo.cpp"
diff --git a/backend/lib/CodeGen/GenericOpcodes.cpp b/backend/lib/CodeGen/GenericOpcodes.cpp
new file mode 100644
index 0000000..0b0e0fe
--- /dev/null
+++ b/backend/lib/CodeGen/GenericOpcodes.cpp
@@ -0,0 +1,1498 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/GenericOpcodes.h"
+
+#include "rcc/CodeGen/MachineInstr.h"
+#include "rcc/CodeGen/MachineMemOperand.h"
+#include "rcc/CodeGen/MachineRegisterInfo.h"
+
+#include <algorithm>
+#include <array>
+#include <bit>
+#include <cstddef>
+#include <cstdint>
+#include <exception>
+#include <initializer_list>
+#include <limits>
+#include <optional>
+#include <ranges>
+#include <span>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace rcc {
+namespace {
+
+using GO = GenericOpcode;
+
+[[nodiscard]] Expected<void> verifyGenericExplicitOperands(Opcode InstructionOpcode,
+ std::span<const MachineOperand> Operands,
+ const MachineRegisterInfo &RegInfo);
+[[nodiscard]] Expected<void> verifyGenericInstructionSemantics(const MachineInstr &Instruction,
+ const MachineRegisterInfo &RegInfo);
+
+[[nodiscard]] LLTKindSet anyValueKinds() {
+ return {LLT::Kind::AnyScalar, LLT::Kind::Integer, LLT::Kind::Float,
+ LLT::Kind::Pointer, LLT::Kind::VectorAny, LLT::Kind::VectorInteger,
+ LLT::Kind::VectorFloat, LLT::Kind::VectorPointer};
+}
+
+[[nodiscard]] LLTKindSet integerKinds() {
+ return {LLT::Kind::AnyScalar, LLT::Kind::Integer, LLT::Kind::VectorAny, LLT::Kind::VectorInteger};
+}
+
+[[nodiscard]] LLTKindSet scalarIntegerKinds() {
+ return {LLT::Kind::AnyScalar, LLT::Kind::Integer};
+}
+
+[[nodiscard]] LLTKindSet scalarValueKinds() {
+ return {LLT::Kind::AnyScalar, LLT::Kind::Integer, LLT::Kind::Float, LLT::Kind::Pointer};
+}
+
+[[nodiscard]] LLTKindSet floatingKinds() {
+ return {LLT::Kind::AnyScalar, LLT::Kind::Float, LLT::Kind::VectorAny, LLT::Kind::VectorFloat};
+}
+
+[[nodiscard]] LLTKindSet scalarFloatingKinds() {
+ return {LLT::Kind::AnyScalar, LLT::Kind::Float};
+}
+
+[[nodiscard]] LLTKindSet pointerKinds() {
+ return {LLT::Kind::Pointer, LLT::Kind::VectorPointer};
+}
+
+[[nodiscard]] LLTKindSet scalarPointerKinds() {
+ return {LLT::Kind::Pointer};
+}
+
+[[nodiscard]] LLTKindSet vectorKinds() {
+ return {LLT::Kind::VectorAny, LLT::Kind::VectorInteger, LLT::Kind::VectorFloat,
+ LLT::Kind::VectorPointer};
+}
+
+[[nodiscard]] OperandKindSet allOperandKinds() {
+ return {MachineOperand::Kind::Register,
+ MachineOperand::Kind::Immediate,
+ MachineOperand::Kind::IntegerImmediate,
+ MachineOperand::Kind::FloatingImmediate,
+ MachineOperand::Kind::BasicBlock,
+ MachineOperand::Kind::FrameIndex,
+ MachineOperand::Kind::ConstantPoolIndex,
+ MachineOperand::Kind::JumpTableIndex,
+ MachineOperand::Kind::GlobalSymbol,
+ MachineOperand::Kind::ExternalSymbol,
+ MachineOperand::Kind::BlockAddress,
+ MachineOperand::Kind::RegisterMask,
+ MachineOperand::Kind::LiveOutRegisterMask,
+ MachineOperand::Kind::Metadata,
+ MachineOperand::Kind::TargetIndex,
+ MachineOperand::Kind::MCSymbol,
+ MachineOperand::Kind::CFIIndex,
+ MachineOperand::Kind::Intrinsic,
+ MachineOperand::Kind::Predicate,
+ MachineOperand::Kind::ShuffleMask,
+ MachineOperand::Kind::DebugInstrRef,
+ MachineOperand::Kind::LaneMask};
+}
+
+[[nodiscard]] OperandDescriptor typedRegister(RegisterOperandRole Role, LLTKindSet Kinds,
+ std::uint16_t Group) {
+ OperandDescriptor Result;
+ Result.Kinds = {MachineOperand::Kind::Register};
+ Result.RegisterRole = Role;
+ Result.TypeConstraint = OperandTypeConstraint{std::move(Kinds), Group};
+ return Result;
+}
+
+[[nodiscard]] OperandDescriptor untypedRegister(RegisterOperandRole Role, bool AllowSubReg = true) {
+ OperandDescriptor Result;
+ Result.Kinds = {MachineOperand::Kind::Register};
+ Result.RegisterRole = Role;
+ Result.AllowSubReg = AllowSubReg;
+ return Result;
+}
+
+[[nodiscard]] OperandDescriptor operand(OperandKindSet Kinds) {
+ OperandDescriptor Result;
+ Result.Kinds = Kinds;
+ if (Kinds.contains(MachineOperand::Kind::Register)) {
+ Result.RegisterRole = RegisterOperandRole::Any;
+ Result.AllowSubReg = true;
+ }
+ return Result;
+}
+
+[[nodiscard]] OperandDescriptor kind(MachineOperand::Kind Kind) {
+ return operand(OperandKindSet{Kind});
+}
+
+[[nodiscard]] OperandDescriptor immediate() {
+ return operand({MachineOperand::Kind::Immediate, MachineOperand::Kind::IntegerImmediate});
+}
+
+[[nodiscard]] OperandDescriptor symbol() {
+ return operand({MachineOperand::Kind::GlobalSymbol, MachineOperand::Kind::ExternalSymbol,
+ MachineOperand::Kind::MCSymbol});
+}
+
+[[nodiscard]] OperandDescriptor anyOperand() {
+ return operand(allOperandKinds());
+}
+
+[[nodiscard]] OperandDescriptor repeated(OperandDescriptor Descriptor) {
+ Descriptor.VariadicGroup = true;
+ return Descriptor;
+}
+
+class DescriptorRegistry final {
+public:
+ DescriptorRegistry() {
+ Descriptors.reserve(CorePseudoOpcodeCount + GenericOpcodeCount);
+ Configured.reserve(CorePseudoOpcodeCount + GenericOpcodeCount);
+#define RCC_CORE_OPCODE(Name) add(GO::Name, #Name, false);
+#define RCC_GENERIC_OPCODE(Name) add(GO::Name, #Name, true);
+#include "rcc/CodeGen/GenericOpcode.def"
+#undef RCC_GENERIC_OPCODE
+#undef RCC_CORE_OPCODE
+ }
+
+ void configure(std::initializer_list<GO> Opcodes, std::vector<OperandDescriptor> Operands,
+ std::size_t NumDefs, std::initializer_list<InstructionFlag> Flags = {},
+ bool Variadic = false, std::size_t MinimumRepetitions = 0,
+ bool RequiresMemoryOperands = false, PseudoOwner Owner = PseudoOwner::None,
+ PipelineStage Deadline = PipelineStage::Emission,
+ bool AllowExtraImplicitOperands = false) {
+ for (const GO OpcodeValue : Opcodes) {
+ const std::size_t Index = index(OpcodeValue);
+ if (Index >= Descriptors.size() || Configured[Index])
+ std::terminate();
+ Configured[Index] = true;
+ InstructionDescriptor &Descriptor = Descriptors[Index];
+ Descriptor.Operands = Operands;
+ Descriptor.NumExplicitDefs = NumDefs;
+ Descriptor.Variadic = Variadic;
+ Descriptor.MinimumVariadicRepetitions = MinimumRepetitions;
+ Descriptor.Flags = {};
+ Descriptor.Flags.set(isGenericOpcode(Descriptor.InstructionOpcode) ? InstructionFlag::Generic
+ : InstructionFlag::Pseudo);
+ Descriptor.Flags.set(InstructionFlag::AllowedPreLegalizer);
+ for (const InstructionFlag Flag : Flags)
+ Descriptor.Flags.set(Flag);
+ Descriptor.RequiresMemoryOperands = RequiresMemoryOperands;
+ Descriptor.Owner = Owner;
+ Descriptor.Deadline = Deadline;
+ Descriptor.AllowExtraImplicitOperands = AllowExtraImplicitOperands;
+ if (isGenericOpcode(Descriptor.InstructionOpcode)) {
+ Descriptor.VerifyExplicitOperands = &verifyGenericExplicitOperands;
+ Descriptor.VerifyInstructionSemantics = &verifyGenericInstructionSemantics;
+ }
+ }
+ }
+
+ void addFlag(std::initializer_list<GO> Opcodes, InstructionFlag Flag) {
+ for (const GO OpcodeValue : Opcodes) {
+ const std::size_t Index = index(OpcodeValue);
+ if (Index >= Descriptors.size() || !Configured[Index])
+ std::terminate();
+ Descriptors[Index].Flags.set(Flag);
+ }
+ }
+
+ void tie(GO OpcodeValue, std::size_t Left, std::size_t Right) {
+ InstructionDescriptor &Descriptor = Descriptors[index(OpcodeValue)];
+ if (!Configured[index(OpcodeValue)] || Left >= Descriptor.Operands.size() ||
+ Right >= Descriptor.Operands.size()) {
+ std::terminate();
+ }
+ Descriptor.Operands[Left].DefaultTiedOperand = Right;
+ Descriptor.Operands[Right].DefaultTiedOperand = Left;
+ }
+
+ [[nodiscard]] std::vector<InstructionDescriptor> take() {
+ if (!std::ranges::all_of(Configured, [](bool Value) { return Value; }))
+ std::terminate();
+ return std::move(Descriptors);
+ }
+
+private:
+ static std::size_t index(GO OpcodeValue) {
+ const std::size_t Value = static_cast<std::size_t>(OpcodeValue);
+ if (Value < static_cast<std::size_t>(GO::CORE_PSEUDO_END))
+ return Value - static_cast<std::size_t>(GO::PHI);
+ return CorePseudoOpcodeCount + Value - static_cast<std::size_t>(GO::G_ASSERT_SEXT);
+ }
+
+ void add(GO OpcodeValue, std::string_view Name, bool IsGeneric) {
+ InstructionDescriptor Descriptor;
+ Descriptor.InstructionOpcode = opcode(OpcodeValue);
+ Descriptor.Name = Name;
+ if (IsGeneric != isGenericOpcode(Descriptor.InstructionOpcode))
+ std::terminate();
+ Descriptors.push_back(std::move(Descriptor));
+ Configured.push_back(false);
+ }
+
+ std::vector<InstructionDescriptor> Descriptors;
+ std::vector<bool> Configured;
+};
+
+[[nodiscard]] Expected<void> semanticError(std::string_view Message) {
+ return makeUnexpected(ErrorCode::VerificationError, std::string(Message));
+}
+
+[[nodiscard]] std::optional<std::int64_t> plainImmediate(const MachineOperand &Operand) noexcept {
+ const auto *Value = Operand.getIf<std::int64_t>();
+ if (Operand.kind() != MachineOperand::Kind::Immediate || Value == nullptr)
+ return std::nullopt;
+ return *Value;
+}
+
+[[nodiscard]] std::uint64_t minimumBitWidth(LLT Type) noexcept {
+ const std::uint64_t Elements = Type.isVector() ? Type.minimumElementCount() : 1;
+ return Elements * Type.scalarBitWidth();
+}
+
+[[nodiscard]] std::uint64_t minimumStoreBytes(LLT Type) noexcept {
+ return (minimumBitWidth(Type) + 7) / 8;
+}
+
+[[nodiscard]] LLT scalarElementType(LLT Type) noexcept {
+ if (const auto Element = Type.scalarElementType())
+ return *Element;
+ return Type;
+}
+
+[[nodiscard]] bool hasSameShape(LLT Left, LLT Right) noexcept {
+ if (Left.isVector() != Right.isVector())
+ return false;
+ if (!Left.isVector())
+ return true;
+ return Left.isScalableVector() == Right.isScalableVector() &&
+ Left.minimumElementCount() == Right.minimumElementCount();
+}
+
+[[nodiscard]] bool isI1(LLT Type) noexcept {
+ return Type.isIntegerOrIntegerVector() && Type.scalarBitWidth() == 1;
+}
+
+[[nodiscard]] Expected<void> requireSameShape(LLT Left, LLT Right, std::string_view Message) {
+ if (!hasSameShape(Left, Right))
+ return semanticError(Message);
+ return {};
+}
+
+[[nodiscard]] Expected<void> requireI1WithShape(LLT Condition, LLT Value,
+ bool AllowScalarCondition) {
+ if (!isI1(Condition))
+ return semanticError("generic condition or status result must use an i1 LLT");
+ if (AllowScalarCondition && !Condition.isVector())
+ return {};
+ return requireSameShape(Condition, Value,
+ "generic condition or status vector shape does not match its value");
+}
+
+[[nodiscard]] Expected<void> verifyGenericExplicitOperands(Opcode InstructionOpcode,
+ std::span<const MachineOperand> Operands,
+ const MachineRegisterInfo &RegInfo) {
+ std::vector<std::optional<LLT>> Types(Operands.size());
+ bool SawNonDefinition = false;
+ for (std::size_t Index = 0; Index < Operands.size(); ++Index) {
+ const MachineOperand &Operand = Operands[Index];
+ if (!Operand.isRegister()) {
+ SawNonDefinition = true;
+ continue;
+ }
+
+ if (Operand.isDef() == Operand.isUse())
+ return semanticError("generic register operand must be either a pure definition or use");
+ if (Operand.isDef()) {
+ if (SawNonDefinition)
+ return semanticError("generic register definitions must form the explicit operand prefix");
+ } else {
+ SawNonDefinition = true;
+ if (Operand.registerFlags().has(RegisterOperandFlag::Undef))
+ return semanticError("generic virtual-register uses cannot carry the undef flag");
+ }
+
+ const Register *Reg = Operand.getIf<Register>();
+ if (Reg == nullptr || !Reg->isVirtual())
+ return semanticError("generic instruction register operands must be virtual registers");
+ auto Info = RegInfo.virtualRegisterInfo(*Reg);
+ if (!Info)
+ return std::unexpected(Info.error());
+ if (!Info->get().Type)
+ return semanticError("generic virtual register is missing its LLT");
+ Types[Index] = *Info->get().Type;
+ }
+
+ const auto TypeAt = [&Types](std::size_t Index) -> LLT { return *Types[Index]; };
+ const GO Generic = static_cast<GO>(InstructionOpcode.value());
+
+ const auto VerifyWidthChange = [&](bool IsExtension) -> Expected<void> {
+ const LLT Dst = TypeAt(0);
+ const LLT Src = TypeAt(1);
+ if (auto Shape =
+ requireSameShape(Dst, Src, "generic width conversion must preserve vector shape");
+ !Shape) {
+ return Shape;
+ }
+ if ((IsExtension && Dst.scalarBitWidth() <= Src.scalarBitWidth()) ||
+ (!IsExtension && Dst.scalarBitWidth() >= Src.scalarBitWidth())) {
+ return semanticError(IsExtension
+ ? "generic extension destination must be wider than its source"
+ : "generic truncation destination must be narrower than its source");
+ }
+ return {};
+ };
+
+ const auto VerifyImmediateBitWidth = [&](std::size_t ImmediateIndex, std::size_t TypeIndex,
+ bool StrictlySmaller) -> Expected<void> {
+ const auto Value = plainImmediate(Operands[ImmediateIndex]);
+ if (!Value || *Value <= 0)
+ return semanticError("generic bit-width immediate must be a positive machine immediate");
+ const std::uint64_t Width = TypeAt(TypeIndex).scalarBitWidth();
+ if ((StrictlySmaller && static_cast<std::uint64_t>(*Value) >= Width) ||
+ (!StrictlySmaller && static_cast<std::uint64_t>(*Value) > Width)) {
+ return semanticError("generic bit-width immediate exceeds its register element width");
+ }
+ return {};
+ };
+
+ switch (Generic) {
+ case GO::G_ASSERT_SEXT:
+ case GO::G_ASSERT_ZEXT:
+ case GO::G_SEXT_INREG:
+ return VerifyImmediateBitWidth(2, 1, true);
+ case GO::G_ASSERT_ALIGN: {
+ const auto AlignmentValue = plainImmediate(Operands[2]);
+ if (!AlignmentValue || *AlignmentValue <= 0 ||
+ !std::has_single_bit(static_cast<std::uint64_t>(*AlignmentValue))) {
+ return semanticError("G_ASSERT_ALIGN requires a positive power-of-two alignment");
+ }
+ return {};
+ }
+ case GO::G_CONSTANT: {
+ if (const auto *Payload = Operands[1].getIf<IntegerImmediateOperand>();
+ Payload != nullptr && Payload->Value.bitWidth() != TypeAt(0).scalarBitWidth()) {
+ return semanticError("G_CONSTANT integer width must match its destination LLT");
+ }
+ return {};
+ }
+ case GO::G_FCONSTANT: {
+ const auto *Payload = Operands[1].getIf<FloatingImmediateOperand>();
+ if (Payload == nullptr || Payload->Bits.semantics() != TypeAt(0).floatSemantics())
+ return semanticError("G_FCONSTANT semantics must match its destination LLT");
+ return {};
+ }
+ case GO::G_BITCAST: {
+ const LLT Dst = TypeAt(0);
+ const LLT Src = TypeAt(1);
+ if (Dst.isPointerOrPointerVector() != Src.isPointerOrPointerVector())
+ return semanticError("G_BITCAST cannot cross the pointer/non-pointer boundary");
+ if (minimumBitWidth(Dst) != minimumBitWidth(Src) ||
+ Dst.isScalableVector() != Src.isScalableVector()) {
+ return semanticError("G_BITCAST source and destination sizes must match");
+ }
+ if (Dst == Src)
+ return semanticError("G_BITCAST must change the LLT");
+ return {};
+ }
+ case GO::G_INTTOPTR:
+ case GO::G_PTRTOINT:
+ return requireSameShape(TypeAt(0), TypeAt(1),
+ "pointer/integer conversion must preserve vector shape");
+ case GO::G_ADDRSPACE_CAST: {
+ const LLT Dst = TypeAt(0);
+ const LLT Src = TypeAt(1);
+ if (auto Shape = requireSameShape(Dst, Src, "G_ADDRSPACE_CAST must preserve vector shape");
+ !Shape) {
+ return Shape;
+ }
+ if (Dst.addressSpace() == Src.addressSpace())
+ return semanticError("G_ADDRSPACE_CAST must change the address space");
+ return {};
+ }
+ case GO::G_ANYEXT:
+ case GO::G_SEXT:
+ case GO::G_ZEXT:
+ case GO::G_FPEXT:
+ return VerifyWidthChange(true);
+ case GO::G_TRUNC:
+ case GO::G_TRUNC_SSAT_S:
+ case GO::G_TRUNC_SSAT_U:
+ case GO::G_TRUNC_USAT_U:
+ case GO::G_FPTRUNC:
+ return VerifyWidthChange(false);
+ case GO::G_PTR_ADD:
+ case GO::G_PTRMASK:
+ return requireSameShape(TypeAt(1), TypeAt(2),
+ "pointer arithmetic operands must have matching scalar/vector shape");
+ case GO::G_ICMP:
+ case GO::G_FCMP:
+ case GO::G_STRICT_FCMP:
+ case GO::G_STRICT_FCMPS: {
+ const PredicateOperand *Predicate = Operands[1].getIf<PredicateOperand>();
+ if (Predicate == nullptr || ((Generic == GO::G_ICMP) != Predicate->isInteger())) {
+ return semanticError("generic comparison predicate kind does not match its opcode");
+ }
+ return requireI1WithShape(TypeAt(0), TypeAt(2), false);
+ }
+ case GO::G_SCMP:
+ case GO::G_UCMP:
+ if (TypeAt(0).scalarBitWidth() < 2)
+ return semanticError("G_SCMP/G_UCMP result elements must be at least two bits wide");
+ return requireSameShape(TypeAt(0), TypeAt(1),
+ "G_SCMP/G_UCMP must preserve scalar/vector shape");
+ case GO::G_SELECT:
+ return requireI1WithShape(TypeAt(1), TypeAt(0), true);
+ case GO::G_UADDO:
+ case GO::G_USUBO:
+ case GO::G_SADDO:
+ case GO::G_SSUBO:
+ case GO::G_UMULO:
+ case GO::G_SMULO:
+ case GO::G_UADDE:
+ case GO::G_USUBE:
+ case GO::G_SADDE:
+ case GO::G_SSUBE:
+ return requireI1WithShape(TypeAt(1), TypeAt(0), false);
+ case GO::G_CTTZ:
+ case GO::G_CTTZ_ZERO_POISON:
+ case GO::G_CTLZ:
+ case GO::G_CTLZ_ZERO_POISON:
+ case GO::G_CTLS:
+ case GO::G_CTPOP:
+ return requireSameShape(TypeAt(0), TypeAt(1),
+ "generic bit-count operation must preserve scalar/vector shape");
+ case GO::G_BSWAP:
+ if ((TypeAt(0).scalarBitWidth() % 16) != 0)
+ return semanticError("G_BSWAP element width must be a multiple of 16 bits");
+ return {};
+ case GO::G_FPTOSI:
+ case GO::G_FPTOUI:
+ case GO::G_FPTOSI_SAT:
+ case GO::G_FPTOUI_SAT:
+ case GO::G_SITOFP:
+ case GO::G_UITOFP:
+ case GO::G_LROUND:
+ case GO::G_LLROUND:
+ return requireSameShape(TypeAt(0), TypeAt(1),
+ "generic numeric conversion must preserve scalar/vector shape");
+ case GO::G_FCOPYSIGN:
+ return requireSameShape(TypeAt(0), TypeAt(2),
+ "G_FCOPYSIGN operands must have matching scalar/vector shape");
+ case GO::G_IS_FPCLASS: {
+ if (auto Shape = requireI1WithShape(TypeAt(0), TypeAt(1), false); !Shape)
+ return Shape;
+ const auto Classes = plainImmediate(Operands[2]);
+ if (!Classes || *Classes < 0 || *Classes > 0x3ff)
+ return semanticError("G_IS_FPCLASS class mask must fit the supported ten-bit mask");
+ return {};
+ }
+ case GO::G_FPOWI:
+ case GO::G_FLDEXP:
+ case GO::G_STRICT_FLDEXP:
+ return requireSameShape(TypeAt(0), TypeAt(2),
+ "floating operation exponent must match the result vector shape");
+ case GO::G_FFREXP:
+ return requireSameShape(TypeAt(0), TypeAt(1),
+ "G_FFREXP mantissa and exponent results must have matching shape");
+ case GO::G_MERGE_VALUES: {
+ if (TypeAt(0).isVector() || TypeAt(1).isVector())
+ return semanticError("G_MERGE_VALUES only merges scalar values");
+ if (minimumBitWidth(TypeAt(0)) != minimumBitWidth(TypeAt(1)) * (Operands.size() - 1))
+ return semanticError("G_MERGE_VALUES source widths do not fill the destination");
+ return {};
+ }
+ case GO::G_UNMERGE_VALUES: {
+ const std::size_t DestinationCount = Operands.size() - 1;
+ const LLT Dst = TypeAt(0);
+ const LLT Src = TypeAt(DestinationCount);
+ if (minimumBitWidth(Src) != minimumBitWidth(Dst) * DestinationCount)
+ return semanticError("G_UNMERGE_VALUES destination widths do not cover the source");
+ if (Dst.isVector() && (!Src.isVector() || Dst.isScalableVector() != Src.isScalableVector() ||
+ scalarElementType(Dst) != scalarElementType(Src))) {
+ return semanticError("vector G_UNMERGE_VALUES operands have incompatible element types");
+ }
+ return {};
+ }
+ case GO::G_EXTRACT: {
+ const auto Offset = plainImmediate(Operands[2]);
+ const std::uint64_t DstBits = minimumBitWidth(TypeAt(0));
+ const std::uint64_t SrcBits = minimumBitWidth(TypeAt(1));
+ if (!Offset || *Offset < 0 || DstBits >= SrcBits ||
+ static_cast<std::uint64_t>(*Offset) > SrcBits - DstBits) {
+ return semanticError("G_EXTRACT offset and result width must stay inside its source");
+ }
+ return {};
+ }
+ case GO::G_INSERT: {
+ const auto Offset = plainImmediate(Operands[3]);
+ const std::uint64_t DstBits = minimumBitWidth(TypeAt(0));
+ const std::uint64_t InsertBits = minimumBitWidth(TypeAt(2));
+ if (!Offset || *Offset < 0 || InsertBits >= DstBits ||
+ static_cast<std::uint64_t>(*Offset) > DstBits - InsertBits) {
+ return semanticError("G_INSERT offset and inserted width must stay inside its result");
+ }
+ return {};
+ }
+ case GO::G_BUILD_VECTOR:
+ case GO::G_BUILD_VECTOR_TRUNC: {
+ const LLT Dst = TypeAt(0);
+ const LLT Src = TypeAt(1);
+ if (!Dst.isVector() || Dst.isScalableVector() || Src.isVector())
+ return semanticError("G_BUILD_VECTOR requires a fixed vector result and scalar sources");
+ if (Dst.minimumElementCount() != Operands.size() - 1)
+ return semanticError("G_BUILD_VECTOR source count must equal the result lane count");
+ const LLT Element = scalarElementType(Dst);
+ if (Generic == GO::G_BUILD_VECTOR && Element != Src)
+ return semanticError("G_BUILD_VECTOR source LLT must equal the result element LLT");
+ if (Generic == GO::G_BUILD_VECTOR_TRUNC && Src.scalarBitWidth() <= Element.scalarBitWidth())
+ return semanticError("G_BUILD_VECTOR_TRUNC sources must be wider than result elements");
+ return {};
+ }
+ case GO::G_CONCAT_VECTORS: {
+ const LLT Dst = TypeAt(0);
+ const LLT Src = TypeAt(1);
+ if (!Dst.isVector() || !Src.isVector() || Dst.isScalableVector() != Src.isScalableVector() ||
+ scalarElementType(Dst) != scalarElementType(Src) ||
+ Dst.minimumElementCount() != Src.minimumElementCount() * (Operands.size() - 1)) {
+ return semanticError("G_CONCAT_VECTORS operands do not exactly tile the result vector");
+ }
+ return {};
+ }
+ case GO::G_VSCALE: {
+ const auto *Scale = Operands[1].getIf<IntegerImmediateOperand>();
+ if (TypeAt(0).isVector() || Scale == nullptr || Scale->Value.isZero() ||
+ Scale->Value.bitWidth() != TypeAt(0).scalarBitWidth()) {
+ return semanticError("G_VSCALE requires a nonzero typed constant matching a scalar result");
+ }
+ return {};
+ }
+ case GO::G_SHL:
+ case GO::G_LSHR:
+ case GO::G_ASHR:
+ case GO::G_ROTR:
+ case GO::G_ROTL:
+ case GO::G_USHLSAT:
+ case GO::G_SSHLSAT:
+ case GO::G_FSHL:
+ case GO::G_FSHR:
+ return requireSameShape(TypeAt(1), TypeAt(Operands.size() - 1),
+ "shift amount must match the shifted value's scalar/vector shape");
+ case GO::G_INSERT_SUBVECTOR:
+ case GO::G_EXTRACT_SUBVECTOR: {
+ const bool IsInsert = Generic == GO::G_INSERT_SUBVECTOR;
+ const LLT Outer = TypeAt(IsInsert ? 0 : 1);
+ const LLT Inner = TypeAt(IsInsert ? 2 : 0);
+ const auto Index = plainImmediate(Operands[IsInsert ? 3 : 2]);
+ if (!Outer.isVector() || !Inner.isVector() ||
+ scalarElementType(Outer) != scalarElementType(Inner) || !Index || *Index < 0) {
+ return semanticError("subvector operation has incompatible vector types or index");
+ }
+ if ((!Outer.isScalableVector() && Inner.isScalableVector()) ||
+ Inner.minimumElementCount() > Outer.minimumElementCount()) {
+ return semanticError("subvector operation cannot place a larger vector inside a smaller one");
+ }
+ const std::uint64_t Start = static_cast<std::uint64_t>(*Index);
+ const std::uint64_t InnerLength = Inner.minimumElementCount();
+ const std::uint64_t OuterLength = Outer.minimumElementCount();
+ const bool MixedScalability = Outer.isScalableVector() && !Inner.isScalableVector();
+ if ((Start % InnerLength) != 0 || Start >= OuterLength ||
+ (!MixedScalability && Start + InnerLength > OuterLength)) {
+ return semanticError("subvector index is misaligned or outside the containing vector");
+ }
+ return {};
+ }
+ case GO::G_SHUFFLE_VECTOR: {
+ const LLT Dst = TypeAt(0);
+ const LLT Src = TypeAt(1);
+ const auto *Mask = Operands[3].getIf<ShuffleMaskOperand>();
+ if (!Dst.isVector() || !Src.isVector() || Dst.isScalableVector() || Src.isScalableVector() ||
+ scalarElementType(Dst) != scalarElementType(Src) || Mask == nullptr ||
+ Mask->Elements.size() != Dst.minimumElementCount()) {
+ return semanticError("G_SHUFFLE_VECTOR types and mask length are inconsistent");
+ }
+ const std::uint64_t SourceLanes = Src.minimumElementCount();
+ for (const std::int32_t Element : Mask->Elements) {
+ if (Element < ShuffleMaskOperand::Undef ||
+ (Element >= 0 && static_cast<std::uint64_t>(Element) >= 2 * SourceLanes)) {
+ return semanticError("G_SHUFFLE_VECTOR mask contains an invalid lane index");
+ }
+ }
+ return {};
+ }
+ case GO::G_SPLAT_VECTOR: {
+ const LLT Dst = TypeAt(0);
+ const LLT Src = TypeAt(1);
+ if (!Dst.isScalableVector() || Src.isVector() ||
+ scalarElementType(Dst).scalarBitWidth() > Src.scalarBitWidth()) {
+ return semanticError("G_SPLAT_VECTOR requires a scalable result with no wider elements");
+ }
+ return {};
+ }
+ case GO::G_STEP_VECTOR: {
+ const LLT Dst = TypeAt(0);
+ const auto *Step = Operands[1].getIf<IntegerImmediateOperand>();
+ if (!Dst.isScalableVector() || Step == nullptr || Step->Value.isZero() ||
+ Step->Value.isNegative() || Step->Value.bitWidth() != Dst.scalarBitWidth()) {
+ return semanticError("G_STEP_VECTOR step must be a positive typed element-width constant");
+ }
+ return {};
+ }
+ case GO::G_INSERT_VECTOR_ELT: {
+ const LLT Vector = TypeAt(0);
+ if (scalarElementType(Vector) != TypeAt(2) || TypeAt(3).isVector())
+ return semanticError("G_INSERT_VECTOR_ELT element or index LLT is incompatible");
+ return {};
+ }
+ case GO::G_EXTRACT_VECTOR_ELT: {
+ const LLT Vector = TypeAt(1);
+ if (scalarElementType(Vector) != TypeAt(0) || TypeAt(2).isVector())
+ return semanticError("G_EXTRACT_VECTOR_ELT result or index LLT is incompatible");
+ return {};
+ }
+ case GO::G_VECTOR_COMPRESS:
+ return requireI1WithShape(TypeAt(2), TypeAt(0), false);
+ case GO::G_VECREDUCE_SEQ_FADD:
+ case GO::G_VECREDUCE_SEQ_FMUL:
+ if (TypeAt(0).isVector() || TypeAt(1).isVector() || !TypeAt(2).isVector() ||
+ scalarElementType(TypeAt(2)) != TypeAt(0) || TypeAt(0) != TypeAt(1)) {
+ return semanticError("sequential vector reduction accumulator and element LLTs must match");
+ }
+ return {};
+ case GO::G_VECREDUCE_FADD:
+ case GO::G_VECREDUCE_FMUL:
+ case GO::G_VECREDUCE_FMAX:
+ case GO::G_VECREDUCE_FMIN:
+ case GO::G_VECREDUCE_FMAXIMUM:
+ case GO::G_VECREDUCE_FMINIMUM:
+ case GO::G_VECREDUCE_ADD:
+ case GO::G_VECREDUCE_MUL:
+ case GO::G_VECREDUCE_AND:
+ case GO::G_VECREDUCE_OR:
+ case GO::G_VECREDUCE_XOR:
+ case GO::G_VECREDUCE_SMAX:
+ case GO::G_VECREDUCE_SMIN:
+ case GO::G_VECREDUCE_UMAX:
+ case GO::G_VECREDUCE_UMIN:
+ if (TypeAt(0).isVector() || !TypeAt(1).isVector() ||
+ scalarElementType(TypeAt(1)) != TypeAt(0)) {
+ return semanticError("vector reduction result must equal the source element LLT");
+ }
+ return {};
+ case GO::G_SBFX:
+ case GO::G_UBFX:
+ if (TypeAt(0).isVector() || TypeAt(2).isVector() || TypeAt(3).isVector())
+ return semanticError("generic bit-field extraction only supports scalar operands");
+ return {};
+ case GO::G_BRCOND:
+ if (!isI1(TypeAt(0)) || TypeAt(0).isVector())
+ return semanticError("G_BRCOND condition must be a scalar i1 virtual register");
+ return {};
+ case GO::G_BRJT:
+ if (TypeAt(0).isVector() || TypeAt(2).isVector())
+ return semanticError("G_BRJT table pointer and index must be scalar values");
+ return {};
+ case GO::G_INTRINSIC:
+ case GO::G_INTRINSIC_W_SIDE_EFFECTS:
+ case GO::G_INTRINSIC_CONVERGENT:
+ case GO::G_INTRINSIC_CONVERGENT_W_SIDE_EFFECTS: {
+ const auto FirstUse = std::ranges::find_if_not(Operands, [](const MachineOperand &Operand) {
+ return Operand.isRegister() && Operand.isDef();
+ });
+ if (FirstUse == Operands.end() || FirstUse->kind() != MachineOperand::Kind::Intrinsic)
+ return semanticError("generic intrinsic must place its intrinsic ID after all definitions");
+ return {};
+ }
+ case GO::G_INDEXED_LOAD:
+ case GO::G_INDEXED_SEXTLOAD:
+ case GO::G_INDEXED_ZEXTLOAD:
+ if (const auto Mode = plainImmediate(Operands[4]); !Mode || (*Mode != 0 && *Mode != 1))
+ return semanticError("generic indexed-load address mode must be immediate 0 or 1");
+ return {};
+ case GO::G_INDEXED_STORE:
+ if (const auto Mode = plainImmediate(Operands[4]); !Mode || (*Mode != 0 && *Mode != 1))
+ return semanticError("G_INDEXED_STORE address mode must be immediate 0 or 1");
+ return {};
+ case GO::G_ATOMIC_CMPXCHG_WITH_SUCCESS:
+ return requireI1WithShape(TypeAt(1), TypeAt(0), false);
+ case GO::G_FENCE: {
+ const auto Ordering = plainImmediate(Operands[0]);
+ const auto Scope = plainImmediate(Operands[1]);
+ if (!Ordering || *Ordering < 4 || *Ordering > 7 || !Scope || *Scope < 0 ||
+ static_cast<std::uint64_t>(*Scope) > std::numeric_limits<std::uint32_t>::max()) {
+ return semanticError("G_FENCE requires acquire-or-stronger ordering and a uint32 scope");
+ }
+ return {};
+ }
+ case GO::G_PREFETCH: {
+ const auto ReadWrite = plainImmediate(Operands[1]);
+ const auto Locality = plainImmediate(Operands[2]);
+ const auto CacheType = plainImmediate(Operands[3]);
+ if (!ReadWrite || *ReadWrite < 0 || *ReadWrite > 1 || !Locality || *Locality < 0 ||
+ *Locality > 3 || !CacheType || *CacheType < 0 || *CacheType > 1) {
+ return semanticError("G_PREFETCH control operands are outside their defined ranges");
+ }
+ return {};
+ }
+ case GO::G_VAARG: {
+ const auto Align = plainImmediate(Operands[2]);
+ if (!Align || *Align <= 0 || !std::has_single_bit(static_cast<std::uint64_t>(*Align)))
+ return semanticError("G_VAARG alignment must be a positive power of two");
+ return {};
+ }
+ case GO::G_DYN_STACKALLOC: {
+ const auto Align = plainImmediate(Operands[2]);
+ if (!Align || *Align <= 0 || !std::has_single_bit(static_cast<std::uint64_t>(*Align)))
+ return semanticError("G_DYN_STACKALLOC alignment must be a positive power of two");
+ return {};
+ }
+ case GO::G_MEMCPY:
+ case GO::G_MEMMOVE:
+ if (const auto Tail = plainImmediate(Operands[3]); !Tail || (*Tail != 0 && *Tail != 1))
+ return semanticError("generic memory-transfer tail flag must be immediate 0 or 1");
+ return {};
+ case GO::G_MEMSET:
+ if (TypeAt(1).isVector() || TypeAt(1).scalarBitWidth() != 8)
+ return semanticError("G_MEMSET fill value must be a scalar i8 value");
+ if (const auto Tail = plainImmediate(Operands[3]); !Tail || (*Tail != 0 && *Tail != 1))
+ return semanticError("G_MEMSET tail flag must be immediate 0 or 1");
+ return {};
+ case GO::G_BZERO:
+ if (const auto Tail = plainImmediate(Operands[2]); !Tail || (*Tail != 0 && *Tail != 1))
+ return semanticError("G_BZERO tail flag must be immediate 0 or 1");
+ return {};
+ case GO::G_UBSANTRAP: {
+ const auto Kind = plainImmediate(Operands[0]);
+ if (!Kind || *Kind < -128 || *Kind > 127)
+ return semanticError("G_UBSANTRAP kind must fit in a signed eight-bit immediate");
+ return {};
+ }
+ case GO::G_INTRINSIC_FPTRUNC_ROUND: {
+ const auto Rounding = plainImmediate(Operands[2]);
+ if (!Rounding || !((*Rounding >= 0 && *Rounding <= 4) || *Rounding == 7))
+ return semanticError("floating truncation rounding mode is invalid");
+ return VerifyWidthChange(false);
+ }
+ default:
+ return {};
+ }
+}
+
+[[nodiscard]] Expected<void> verifyGenericInstructionSemantics(const MachineInstr &Instruction,
+ const MachineRegisterInfo &RegInfo) {
+ const auto Operands = Instruction.operands();
+ const auto MemoryOperands = Instruction.memOperands();
+ const GO Generic = static_cast<GO>(Instruction.opcode().value());
+
+ const auto TypeAt = [&](std::size_t Index) -> Expected<LLT> {
+ if (Index >= Operands.size())
+ return makeUnexpected(ErrorCode::InternalError,
+ "generic semantic verifier operand index is out of range");
+ const Register *Reg = Operands[Index].getIf<Register>();
+ if (Reg == nullptr || !Reg->isVirtual())
+ return makeUnexpected(ErrorCode::InternalError,
+ "generic semantic verifier expected a virtual register");
+ auto Info = RegInfo.virtualRegisterInfo(*Reg);
+ if (!Info)
+ return std::unexpected(Info.error());
+ if (!Info->get().Type)
+ return makeUnexpected(ErrorCode::InternalError,
+ "generic semantic verifier found a register without an LLT");
+ return *Info->get().Type;
+ };
+
+ const auto VerifyAddress = [&](std::size_t OperandIndex,
+ const MachineMemOperand &Memory) -> Expected<void> {
+ auto Type = TypeAt(OperandIndex);
+ if (!Type)
+ return std::unexpected(Type.error());
+ if (!Type->isPointer() || Type->addressSpace() != Memory.description().AddressSpace)
+ return semanticError("generic memory address LLT and MMO address space do not match");
+ return {};
+ };
+
+ enum class SizeRelation : std::uint8_t {
+ Equal,
+ NoLarger,
+ StrictlySmaller,
+ };
+ const auto VerifySize = [&](LLT ValueType, const MachineMemOperand &Memory,
+ SizeRelation Relation) -> Expected<void> {
+ const MemorySize Size = Memory.description().Size;
+ if (!Size.isKnown())
+ return semanticError("typed generic memory access requires a known MMO size");
+ if (Size.isScalable() != ValueType.isScalableVector())
+ return semanticError("generic value and MMO scalability do not match");
+ const std::uint64_t MemoryBytes = Size.minimumBytes();
+ const std::uint64_t ValueBytes = minimumStoreBytes(ValueType);
+ const bool Valid = Relation == SizeRelation::Equal
+ ? MemoryBytes == ValueBytes
+ : (Relation == SizeRelation::NoLarger ? MemoryBytes <= ValueBytes
+ : MemoryBytes < ValueBytes);
+ if (!Valid)
+ return semanticError("generic memory access size is incompatible with its value LLT");
+ return {};
+ };
+
+ const auto VerifySingleAccess = [&](std::size_t AddressIndex, std::size_t ValueIndex,
+ bool MustLoad, bool MustStore,
+ SizeRelation Relation) -> Expected<void> {
+ if (MemoryOperands.size() != 1)
+ return semanticError("generic scalar memory instruction requires exactly one MMO");
+ const MachineMemOperand &Memory = *MemoryOperands.front();
+ if (Memory.isLoad() != MustLoad || Memory.isStore() != MustStore)
+ return semanticError("generic instruction and MMO access directions do not match");
+ if (auto Address = VerifyAddress(AddressIndex, Memory); !Address)
+ return Address;
+ auto ValueType = TypeAt(ValueIndex);
+ if (!ValueType)
+ return std::unexpected(ValueType.error());
+ return VerifySize(*ValueType, Memory, Relation);
+ };
+
+ switch (Generic) {
+ case GO::G_LOAD:
+ return VerifySingleAccess(1, 0, true, false, SizeRelation::NoLarger);
+ case GO::G_SEXTLOAD:
+ case GO::G_ZEXTLOAD:
+ case GO::G_FPEXTLOAD:
+ return VerifySingleAccess(1, 0, true, false, SizeRelation::StrictlySmaller);
+ case GO::G_STORE:
+ return VerifySingleAccess(1, 0, false, true, SizeRelation::NoLarger);
+ case GO::G_FPTRUNCSTORE:
+ return VerifySingleAccess(1, 0, false, true, SizeRelation::StrictlySmaller);
+ case GO::G_INDEXED_LOAD:
+ return VerifySingleAccess(2, 0, true, false, SizeRelation::NoLarger);
+ case GO::G_INDEXED_SEXTLOAD:
+ case GO::G_INDEXED_ZEXTLOAD:
+ return VerifySingleAccess(2, 0, true, false, SizeRelation::StrictlySmaller);
+ case GO::G_INDEXED_STORE:
+ return VerifySingleAccess(2, 1, false, true, SizeRelation::NoLarger);
+ case GO::G_ATOMIC_CMPXCHG_WITH_SUCCESS:
+ if (MemoryOperands.size() != 1 ||
+ MemoryOperands.front()->description().AtomicKind != AtomicOperation::CompareExchange)
+ return semanticError("G_ATOMIC_CMPXCHG_WITH_SUCCESS requires one compare-exchange MMO");
+ return VerifySingleAccess(2, 0, true, true, SizeRelation::Equal);
+ case GO::G_ATOMIC_CMPXCHG:
+ if (MemoryOperands.size() != 1 ||
+ MemoryOperands.front()->description().AtomicKind != AtomicOperation::CompareExchange)
+ return semanticError("G_ATOMIC_CMPXCHG requires one compare-exchange MMO");
+ return VerifySingleAccess(1, 0, true, true, SizeRelation::Equal);
+ case GO::G_ATOMICRMW_XCHG:
+ case GO::G_ATOMICRMW_ADD:
+ case GO::G_ATOMICRMW_SUB:
+ case GO::G_ATOMICRMW_AND:
+ case GO::G_ATOMICRMW_NAND:
+ case GO::G_ATOMICRMW_OR:
+ case GO::G_ATOMICRMW_XOR:
+ case GO::G_ATOMICRMW_MAX:
+ case GO::G_ATOMICRMW_MIN:
+ case GO::G_ATOMICRMW_UMAX:
+ case GO::G_ATOMICRMW_UMIN:
+ case GO::G_ATOMICRMW_FADD:
+ case GO::G_ATOMICRMW_FSUB:
+ case GO::G_ATOMICRMW_FMAX:
+ case GO::G_ATOMICRMW_FMIN:
+ case GO::G_ATOMICRMW_FMAXIMUM:
+ case GO::G_ATOMICRMW_FMINIMUM:
+ case GO::G_ATOMICRMW_FMAXIMUMNUM:
+ case GO::G_ATOMICRMW_FMINIMUMNUM:
+ case GO::G_ATOMICRMW_UINC_WRAP:
+ case GO::G_ATOMICRMW_UDEC_WRAP:
+ case GO::G_ATOMICRMW_USUB_COND:
+ case GO::G_ATOMICRMW_USUB_SAT:
+ if (MemoryOperands.size() != 1 ||
+ MemoryOperands.front()->description().AtomicKind != AtomicOperation::ReadModifyWrite)
+ return semanticError("generic atomic RMW requires one read-modify-write MMO");
+ return VerifySingleAccess(1, 0, true, true, SizeRelation::Equal);
+ case GO::G_PREFETCH: {
+ if (MemoryOperands.size() != 1)
+ return semanticError("G_PREFETCH requires exactly one MMO");
+ const auto ReadWrite = plainImmediate(Operands[1]);
+ if (!ReadWrite)
+ return semanticError("G_PREFETCH is missing its validated read/write selector");
+ const MachineMemOperand &Memory = *MemoryOperands.front();
+ if ((*ReadWrite == 0 && (!Memory.isLoad() || Memory.isStore())) ||
+ (*ReadWrite == 1 && (Memory.isLoad() || !Memory.isStore()))) {
+ return semanticError("G_PREFETCH selector and MMO access direction do not match");
+ }
+ return VerifyAddress(0, Memory);
+ }
+ case GO::G_VASTART:
+ if (MemoryOperands.size() != 1 || MemoryOperands.front()->isLoad() ||
+ !MemoryOperands.front()->isStore()) {
+ return semanticError("G_VASTART requires exactly one store-only MMO");
+ }
+ return VerifyAddress(0, *MemoryOperands.front());
+ case GO::G_VAARG: {
+ if (MemoryOperands.size() != 2)
+ return semanticError("G_VAARG requires separate value-load and list-update MMOs");
+ const MachineMemOperand *ValueLoad = nullptr;
+ const MachineMemOperand *ListStore = nullptr;
+ for (const MachineMemOperand *Memory : MemoryOperands) {
+ if (Memory->isLoad() == Memory->isStore()) {
+ return semanticError("G_VAARG MMOs must each describe exactly one access direction");
+ }
+ if (Memory->isLoad())
+ ValueLoad = Memory;
+ else
+ ListStore = Memory;
+ if (auto Address = VerifyAddress(1, *Memory); !Address)
+ return Address;
+ }
+ if (ValueLoad == nullptr || ListStore == nullptr)
+ return semanticError("G_VAARG MMOs must cover both the value load and list update");
+ auto ResultType = TypeAt(0);
+ if (!ResultType)
+ return std::unexpected(ResultType.error());
+ return VerifySize(*ResultType, *ValueLoad, SizeRelation::Equal);
+ }
+ case GO::G_MEMCPY:
+ case GO::G_MEMCPY_INLINE:
+ case GO::G_MEMMOVE: {
+ if (MemoryOperands.size() != 2 || MemoryOperands[0]->isLoad() ||
+ !MemoryOperands[0]->isStore() || !MemoryOperands[1]->isLoad() ||
+ MemoryOperands[1]->isStore()) {
+ return semanticError("generic memory transfer requires store and load MMOs in that order");
+ }
+ if (MemoryOperands[0]->description().Size != MemoryOperands[1]->description().Size)
+ return semanticError("generic memory-transfer MMOs must have identical sizes");
+ if (auto Destination = VerifyAddress(0, *MemoryOperands[0]); !Destination)
+ return Destination;
+ return VerifyAddress(1, *MemoryOperands[1]);
+ }
+ case GO::G_MEMSET:
+ case GO::G_BZERO:
+ if (MemoryOperands.size() != 1 || MemoryOperands.front()->isLoad() ||
+ !MemoryOperands.front()->isStore()) {
+ return semanticError("generic memory fill requires exactly one store-only MMO");
+ }
+ return VerifyAddress(0, *MemoryOperands.front());
+ default:
+ return {};
+ }
+}
+
+void configureCoreOpcodes(DescriptorRegistry &Registry) {
+ const auto RegDef = untypedRegister(RegisterOperandRole::Def);
+ const auto RegUse = untypedRegister(RegisterOperandRole::Use);
+ const auto Any = anyOperand();
+ const auto VariadicAny = repeated(Any);
+ const auto VariadicUse = repeated(RegUse);
+
+ Registry.configure({GO::PHI},
+ {RegDef, VariadicUse, repeated(kind(MachineOperand::Kind::BasicBlock))}, 1,
+ {InstructionFlag::Phi}, true, 1, false, PseudoOwner::PhiElimination,
+ PipelineStage::SSAEliminated);
+ Registry.configure({GO::INLINEASM}, {VariadicAny}, 0,
+ {InstructionFlag::ProducesCode, InstructionFlag::TerminalEmission,
+ InstructionFlag::UnmodeledSideEffects},
+ true, 0, false, PseudoOwner::AsmPrinter, PipelineStage::Emission, true);
+ Registry.configure({GO::INLINEASM_BR}, {VariadicAny}, 0,
+ {InstructionFlag::ProducesCode, InstructionFlag::TerminalEmission,
+ InstructionFlag::UnmodeledSideEffects, InstructionFlag::HasSideEffects},
+ true, 0, false, PseudoOwner::AsmPrinter, PipelineStage::Emission, true);
+ Registry.configure({GO::CFI_INSTRUCTION}, {kind(MachineOperand::Kind::CFIIndex)}, 0,
+ {InstructionFlag::Meta, InstructionFlag::NotDuplicable});
+ Registry.configure({GO::EH_LABEL, GO::GC_LABEL, GO::ANNOTATION_LABEL},
+ {kind(MachineOperand::Kind::MCSymbol)}, 0,
+ {InstructionFlag::Meta, InstructionFlag::NotDuplicable});
+ Registry.configure({GO::KILL}, {VariadicUse}, 0, {InstructionFlag::Meta}, true);
+ Registry.configure({GO::EXTRACT_SUBREG}, {RegDef, RegUse, immediate()}, 1, {}, false, 0, false,
+ PseudoOwner::PostRAExpansion, PipelineStage::PostRAFinalized);
+ Registry.configure({GO::INSERT_SUBREG}, {RegDef, RegUse, RegUse, immediate()}, 1, {}, false, 0,
+ false, PseudoOwner::TwoAddressRewrite, PipelineStage::TiedOpsRewritten);
+ Registry.tie(GO::INSERT_SUBREG, 0, 1);
+ Registry.configure({GO::IMPLICIT_DEF}, {RegDef}, 1,
+ {InstructionFlag::Meta, InstructionFlag::Rematerializable});
+ Registry.configure({GO::INIT_UNDEF}, {RegDef}, 1, {}, false, 0, false,
+ PseudoOwner::RegisterAllocator, PipelineStage::RegisterAllocated);
+ Registry.configure({GO::SUBREG_TO_REG}, {RegDef, RegUse, immediate()}, 1, {}, false, 0, false,
+ PseudoOwner::PostRAExpansion, PipelineStage::PostRAFinalized);
+ Registry.configure({GO::COPY_TO_REGCLASS}, {RegDef, RegUse, immediate()}, 1, {}, false, 0, false,
+ PseudoOwner::RegisterAllocator, PipelineStage::RegisterAllocated);
+ Registry.configure({GO::DBG_VALUE, GO::DBG_VALUE_LIST, GO::DBG_INSTR_REF, GO::DBG_PHI},
+ {VariadicAny}, 0, {InstructionFlag::Meta}, true);
+ Registry.configure({GO::DBG_LABEL}, {kind(MachineOperand::Kind::Metadata)}, 0,
+ {InstructionFlag::Meta});
+ Registry.configure({GO::REG_SEQUENCE}, {RegDef, repeated(RegUse), repeated(immediate())}, 1, {},
+ true, 1, false, PseudoOwner::TwoAddressRewrite,
+ PipelineStage::TiedOpsRewritten);
+ Registry.configure({GO::COPY}, {RegDef, RegUse}, 1, {}, false, 0, false,
+ PseudoOwner::PostRAExpansion, PipelineStage::PostRAFinalized);
+ Registry.configure({GO::COPY_LANEMASK}, {RegDef, RegUse, kind(MachineOperand::Kind::LaneMask)}, 1,
+ {}, false, 0, false, PseudoOwner::PostRAExpansion,
+ PipelineStage::PostRAFinalized);
+ Registry.configure({GO::BUNDLE}, {VariadicAny}, 0, {InstructionFlag::Meta}, true, 0, false,
+ PseudoOwner::None, PipelineStage::Emission, true);
+ Registry.configure({GO::LIFETIME_START, GO::LIFETIME_END}, {immediate()}, 0,
+ {InstructionFlag::Meta});
+ Registry.configure({GO::PSEUDO_PROBE}, {immediate(), immediate(), immediate(), immediate()}, 0,
+ {InstructionFlag::Meta, InstructionFlag::HasSideEffects});
+ Registry.configure({GO::ARITH_FENCE}, {RegDef, RegUse}, 1, {InstructionFlag::Meta});
+ Registry.tie(GO::ARITH_FENCE, 0, 1);
+ Registry.configure({GO::STACKMAP}, {immediate(), immediate(), VariadicAny}, 0,
+ {InstructionFlag::Call, InstructionFlag::MayLoad,
+ InstructionFlag::HasSideEffects, InstructionFlag::ProducesCode,
+ InstructionFlag::TerminalEmission},
+ true, 0, false, PseudoOwner::AsmPrinter, PipelineStage::Emission, true);
+ Registry.configure({GO::FENTRY_CALL}, {}, 0,
+ {InstructionFlag::Call, InstructionFlag::MayLoad, InstructionFlag::MayStore,
+ InstructionFlag::HasSideEffects, InstructionFlag::ProducesCode,
+ InstructionFlag::TerminalEmission},
+ false, 0, false, PseudoOwner::AsmPrinter, PipelineStage::Emission, true);
+ Registry.configure(
+ {GO::PATCHPOINT},
+ {RegDef, immediate(), immediate(), Any, immediate(), immediate(), VariadicAny}, 1,
+ {InstructionFlag::Call, InstructionFlag::MayLoad, InstructionFlag::HasSideEffects,
+ InstructionFlag::ProducesCode, InstructionFlag::TerminalEmission},
+ true, 0, false, PseudoOwner::AsmPrinter, PipelineStage::Emission, true);
+ Registry.configure({GO::LOAD_STACK_GUARD}, {RegDef}, 1,
+ {InstructionFlag::MayLoad, InstructionFlag::Rematerializable}, false, 0, false,
+ PseudoOwner::InstructionSelect, PipelineStage::Selected);
+ Registry.configure({GO::PREALLOCATED_SETUP}, {immediate()}, 0, {InstructionFlag::HasSideEffects},
+ false, 0, false, PseudoOwner::InstructionSelect, PipelineStage::Selected);
+ Registry.configure({GO::PREALLOCATED_ARG}, {RegDef, immediate(), immediate()}, 1,
+ {InstructionFlag::HasSideEffects}, false, 0, false,
+ PseudoOwner::InstructionSelect, PipelineStage::Selected);
+ Registry.configure({GO::STATEPOINT}, {VariadicAny}, 0,
+ {InstructionFlag::Call, InstructionFlag::MayLoad, InstructionFlag::MayStore,
+ InstructionFlag::HasSideEffects, InstructionFlag::ProducesCode,
+ InstructionFlag::TerminalEmission},
+ true, 0, false, PseudoOwner::AsmPrinter, PipelineStage::Emission, true);
+ Registry.configure({GO::LOCAL_ESCAPE}, {symbol(), kind(MachineOperand::Kind::FrameIndex)}, 0,
+ {InstructionFlag::Meta});
+ Registry.configure({GO::FAULTING_OP}, {RegDef, VariadicAny}, 1,
+ {InstructionFlag::MayLoad, InstructionFlag::MayStore,
+ InstructionFlag::HasSideEffects, InstructionFlag::Terminator,
+ InstructionFlag::Branch, InstructionFlag::ProducesCode,
+ InstructionFlag::TerminalEmission},
+ true, 0, false, PseudoOwner::AsmPrinter, PipelineStage::Emission, true);
+ Registry.configure({GO::PATCHABLE_OP}, {VariadicAny}, 0,
+ {InstructionFlag::MayLoad, InstructionFlag::MayStore,
+ InstructionFlag::HasSideEffects, InstructionFlag::ProducesCode,
+ InstructionFlag::TerminalEmission},
+ true, 0, false, PseudoOwner::AsmPrinter, PipelineStage::Emission, true);
+ Registry.configure({GO::PATCHABLE_FUNCTION_ENTER, GO::PATCHABLE_FUNCTION_EXIT}, {}, 0,
+ {InstructionFlag::HasSideEffects, InstructionFlag::ProducesCode,
+ InstructionFlag::TerminalEmission},
+ false, 0, false, PseudoOwner::AsmPrinter, PipelineStage::Emission);
+ Registry.configure({GO::PATCHABLE_RET, GO::PATCHABLE_TAIL_CALL}, {VariadicAny}, 0,
+ {InstructionFlag::HasSideEffects, InstructionFlag::ProducesCode,
+ InstructionFlag::TerminalEmission, InstructionFlag::Terminator,
+ InstructionFlag::Return},
+ true, 0, false, PseudoOwner::AsmPrinter, PipelineStage::Emission, true);
+ Registry.configure({GO::PATCHABLE_EVENT_CALL}, {RegUse, Any}, 0,
+ {InstructionFlag::Call, InstructionFlag::MayLoad, InstructionFlag::MayStore,
+ InstructionFlag::HasSideEffects, InstructionFlag::ProducesCode,
+ InstructionFlag::TerminalEmission},
+ false, 0, false, PseudoOwner::AsmPrinter, PipelineStage::Emission, true);
+ Registry.configure({GO::PATCHABLE_TYPED_EVENT_CALL}, {Any, RegUse, Any}, 0,
+ {InstructionFlag::Call, InstructionFlag::MayLoad, InstructionFlag::MayStore,
+ InstructionFlag::HasSideEffects, InstructionFlag::ProducesCode,
+ InstructionFlag::TerminalEmission},
+ false, 0, false, PseudoOwner::AsmPrinter, PipelineStage::Emission, true);
+ Registry.configure({GO::ICALL_BRANCH_FUNNEL}, {VariadicAny}, 0, {InstructionFlag::HasSideEffects},
+ true, 0, false, PseudoOwner::PostRAExpansion, PipelineStage::PostRAFinalized,
+ true);
+ Registry.configure({GO::FAKE_USE}, {VariadicUse}, 0,
+ {InstructionFlag::Meta, InstructionFlag::FakeUse}, true);
+ Registry.configure({GO::MEMBARRIER}, {}, 0,
+ {InstructionFlag::Meta, InstructionFlag::HasSideEffects});
+ Registry.configure({GO::JUMP_TABLE_DEBUG_INFO}, {kind(MachineOperand::Kind::JumpTableIndex)}, 0,
+ {InstructionFlag::Meta});
+ Registry.configure({GO::RELOC_NONE}, {symbol()}, 0,
+ {InstructionFlag::Meta, InstructionFlag::HasSideEffects});
+ Registry.configure({GO::CONVERGENCECTRL_ENTRY, GO::CONVERGENCECTRL_ANCHOR}, {RegDef}, 1,
+ {InstructionFlag::Meta, InstructionFlag::Convergent});
+ Registry.configure({GO::CONVERGENCECTRL_LOOP}, {RegDef, RegUse}, 1,
+ {InstructionFlag::Meta, InstructionFlag::Convergent});
+ Registry.configure({GO::CONVERGENCECTRL_GLUE}, {RegUse}, 0,
+ {InstructionFlag::Meta, InstructionFlag::Convergent});
+}
+
+void configureGenericOpcodes(DescriptorRegistry &Registry) {
+ const LLTKindSet AnyTypes = anyValueKinds();
+ const LLTKindSet IntTypes = integerKinds();
+ const LLTKindSet ScalarValues = scalarValueKinds();
+ const LLTKindSet ScalarInts = scalarIntegerKinds();
+ const LLTKindSet FloatTypes = floatingKinds();
+ const LLTKindSet ScalarFloats = scalarFloatingKinds();
+ const LLTKindSet PointerTypes = pointerKinds();
+ const LLTKindSet ScalarPointers = scalarPointerKinds();
+ const LLTKindSet VectorTypes = vectorKinds();
+
+ const auto AnyDef0 = typedRegister(RegisterOperandRole::Def, AnyTypes, 0);
+ const auto AnyUse0 = typedRegister(RegisterOperandRole::Use, AnyTypes, 0);
+ const auto AnyUse1 = typedRegister(RegisterOperandRole::Use, AnyTypes, 1);
+ const auto ScalarDef0 = typedRegister(RegisterOperandRole::Def, ScalarValues, 0);
+ const auto ScalarUse0 = typedRegister(RegisterOperandRole::Use, ScalarValues, 0);
+ const auto IntDef0 = typedRegister(RegisterOperandRole::Def, IntTypes, 0);
+ const auto IntDef1 = typedRegister(RegisterOperandRole::Def, IntTypes, 1);
+ const auto IntUse0 = typedRegister(RegisterOperandRole::Use, IntTypes, 0);
+ const auto IntUse1 = typedRegister(RegisterOperandRole::Use, IntTypes, 1);
+ const auto ScalarIntDef0 = typedRegister(RegisterOperandRole::Def, ScalarInts, 0);
+ const auto ScalarIntDef1 = typedRegister(RegisterOperandRole::Def, ScalarInts, 1);
+ const auto ScalarIntUse0 = typedRegister(RegisterOperandRole::Use, ScalarInts, 0);
+ const auto ScalarIntUse1 = typedRegister(RegisterOperandRole::Use, ScalarInts, 1);
+ const auto ScalarIntUse2 = typedRegister(RegisterOperandRole::Use, ScalarInts, 2);
+ const auto FloatDef0 = typedRegister(RegisterOperandRole::Def, FloatTypes, 0);
+ const auto FloatUse0 = typedRegister(RegisterOperandRole::Use, FloatTypes, 0);
+ const auto FloatUse1 = typedRegister(RegisterOperandRole::Use, FloatTypes, 1);
+ const auto ScalarFloatDef0 = typedRegister(RegisterOperandRole::Def, ScalarFloats, 0);
+ const auto ScalarFloatUse0 = typedRegister(RegisterOperandRole::Use, ScalarFloats, 0);
+ const auto PointerDef0 = typedRegister(RegisterOperandRole::Def, PointerTypes, 0);
+ const auto PointerUse0 = typedRegister(RegisterOperandRole::Use, PointerTypes, 0);
+ const auto PointerUse1 = typedRegister(RegisterOperandRole::Use, PointerTypes, 1);
+ const auto ScalarPointerDef0 = typedRegister(RegisterOperandRole::Def, ScalarPointers, 0);
+ const auto ScalarPointerDef1 = typedRegister(RegisterOperandRole::Def, ScalarPointers, 1);
+ const auto ScalarPointerUse0 = typedRegister(RegisterOperandRole::Use, ScalarPointers, 0);
+ const auto ScalarPointerUse1 = typedRegister(RegisterOperandRole::Use, ScalarPointers, 1);
+ const auto ScalarPointerUse2 = typedRegister(RegisterOperandRole::Use, ScalarPointers, 2);
+
+ Registry.configure({GO::G_ASSERT_SEXT, GO::G_ASSERT_ZEXT}, {IntDef0, IntUse0, immediate()}, 1);
+ Registry.configure({GO::G_ASSERT_ALIGN}, {AnyDef0, AnyUse0, immediate()}, 1);
+
+ Registry.configure(
+ {GO::G_ADD, GO::G_SUB, GO::G_MUL, GO::G_SDIV, GO::G_UDIV, GO::G_SREM,
+ GO::G_UREM, GO::G_AND, GO::G_OR, GO::G_XOR, GO::G_ABDS, GO::G_ABDU,
+ GO::G_UAVGFLOOR, GO::G_UAVGCEIL, GO::G_SAVGFLOOR, GO::G_SAVGCEIL, GO::G_UMULH, GO::G_SMULH,
+ GO::G_UADDSAT, GO::G_SADDSAT, GO::G_USUBSAT, GO::G_SSUBSAT, GO::G_SMIN, GO::G_SMAX,
+ GO::G_UMIN, GO::G_UMAX, GO::G_CLMUL},
+ {IntDef0, IntUse0, IntUse0}, 1);
+ Registry.addFlag({GO::G_ADD, GO::G_MUL, GO::G_AND, GO::G_OR, GO::G_XOR, GO::G_ABDS, GO::G_ABDU,
+ GO::G_UMULH, GO::G_SMULH, GO::G_UADDSAT, GO::G_SADDSAT, GO::G_SMIN, GO::G_SMAX,
+ GO::G_UMIN, GO::G_UMAX, GO::G_CLMUL},
+ InstructionFlag::Commutable);
+
+ Registry.configure({GO::G_SDIVREM, GO::G_UDIVREM}, {IntDef0, IntDef0, IntUse0, IntUse0}, 2);
+ Registry.configure({GO::G_IMPLICIT_DEF}, {AnyDef0}, 1);
+ Registry.configure({GO::G_PHI},
+ {AnyDef0, repeated(AnyUse0), repeated(kind(MachineOperand::Kind::BasicBlock))},
+ 1, {InstructionFlag::Phi}, true, 1);
+ Registry.configure({GO::G_FRAME_INDEX},
+ {ScalarPointerDef0, kind(MachineOperand::Kind::FrameIndex)}, 1);
+ Registry.configure({GO::G_GLOBAL_VALUE},
+ {ScalarPointerDef0, kind(MachineOperand::Kind::GlobalSymbol)}, 1);
+ Registry.configure(
+ {GO::G_PTRAUTH_GLOBAL_VALUE},
+ {ScalarPointerDef0, ScalarPointerUse1, immediate(), ScalarIntUse2, immediate()}, 1);
+ Registry.configure({GO::G_CONSTANT_POOL},
+ {ScalarPointerDef0, kind(MachineOperand::Kind::ConstantPoolIndex)}, 1);
+ Registry.configure({GO::G_EXTRACT}, {AnyDef0, AnyUse1, immediate()}, 1);
+ Registry.configure({GO::G_UNMERGE_VALUES}, {AnyDef0, repeated(AnyDef0), AnyUse1}, 2, {}, true, 1);
+ Registry.configure({GO::G_INSERT}, {AnyDef0, AnyUse0, AnyUse1, immediate()}, 1);
+ Registry.configure({GO::G_MERGE_VALUES}, {AnyDef0, AnyUse1, repeated(AnyUse1)}, 1, {}, true, 1);
+
+ const auto VectorDef0 = typedRegister(RegisterOperandRole::Def, VectorTypes, 0);
+ Registry.configure({GO::G_BUILD_VECTOR, GO::G_BUILD_VECTOR_TRUNC},
+ {VectorDef0, AnyUse1, repeated(AnyUse1)}, 1, {}, true);
+ Registry.configure({GO::G_CONCAT_VECTORS},
+ {VectorDef0, typedRegister(RegisterOperandRole::Use, VectorTypes, 1),
+ repeated(typedRegister(RegisterOperandRole::Use, VectorTypes, 1))},
+ 1, {}, true, 1);
+
+ Registry.configure({GO::G_PTRTOINT}, {IntDef0, PointerUse1}, 1);
+ Registry.configure({GO::G_INTTOPTR}, {PointerDef0, IntUse1}, 1);
+ Registry.configure({GO::G_BITCAST}, {AnyDef0, AnyUse1}, 1);
+ Registry.configure({GO::G_FREEZE, GO::G_CONSTANT_FOLD_BARRIER}, {AnyDef0, AnyUse0}, 1);
+ Registry.configure({GO::G_INTRINSIC_FPTRUNC_ROUND}, {FloatDef0, FloatUse1, immediate()}, 1);
+ Registry.configure({GO::G_INTRINSIC_TRUNC, GO::G_INTRINSIC_ROUND, GO::G_INTRINSIC_ROUNDEVEN},
+ {FloatDef0, FloatUse0}, 1);
+ Registry.configure({GO::G_INTRINSIC_LRINT, GO::G_INTRINSIC_LLRINT}, {IntDef0, FloatUse1}, 1);
+ Registry.configure({GO::G_READCYCLECOUNTER, GO::G_READSTEADYCOUNTER},
+ {typedRegister(RegisterOperandRole::Def, ScalarInts, 0)}, 1,
+ {InstructionFlag::HasSideEffects});
+
+ Registry.configure({GO::G_LOAD}, {AnyDef0, ScalarPointerUse1}, 1, {InstructionFlag::MayLoad},
+ false, 0, true);
+ Registry.configure({GO::G_SEXTLOAD, GO::G_ZEXTLOAD}, {IntDef0, ScalarPointerUse1}, 1,
+ {InstructionFlag::MayLoad}, false, 0, true);
+ Registry.configure({GO::G_FPEXTLOAD}, {FloatDef0, ScalarPointerUse1}, 1,
+ {InstructionFlag::MayLoad}, false, 0, true);
+ Registry.configure({GO::G_INDEXED_LOAD},
+ {AnyDef0, ScalarPointerDef1, ScalarPointerUse1, ScalarIntUse2, immediate()}, 2,
+ {InstructionFlag::MayLoad}, false, 0, true);
+ Registry.configure({GO::G_INDEXED_SEXTLOAD, GO::G_INDEXED_ZEXTLOAD},
+ {IntDef0, ScalarPointerDef1, ScalarPointerUse1, ScalarIntUse2, immediate()}, 2,
+ {InstructionFlag::MayLoad}, false, 0, true);
+ Registry.configure({GO::G_STORE}, {AnyUse0, ScalarPointerUse1}, 0, {InstructionFlag::MayStore},
+ false, 0, true);
+ Registry.configure({GO::G_FPTRUNCSTORE}, {FloatUse0, ScalarPointerUse1}, 0,
+ {InstructionFlag::MayStore}, false, 0, true);
+ Registry.configure({GO::G_INDEXED_STORE},
+ {ScalarPointerDef0, AnyUse1, ScalarPointerUse0, ScalarIntUse2, immediate()}, 1,
+ {InstructionFlag::MayStore}, false, 0, true);
+ Registry.configure({GO::G_ATOMIC_CMPXCHG_WITH_SUCCESS},
+ {ScalarDef0, ScalarIntDef1, ScalarPointerUse2, ScalarUse0, ScalarUse0}, 2,
+ {InstructionFlag::MayLoad, InstructionFlag::MayStore}, false, 0, true);
+ Registry.configure({GO::G_ATOMIC_CMPXCHG},
+ {ScalarDef0, ScalarPointerUse1, ScalarUse0, ScalarUse0}, 1,
+ {InstructionFlag::MayLoad, InstructionFlag::MayStore}, false, 0, true);
+ Registry.configure({GO::G_ATOMICRMW_XCHG}, {ScalarDef0, ScalarPointerUse1, ScalarUse0}, 1,
+ {InstructionFlag::MayLoad, InstructionFlag::MayStore}, false, 0, true);
+ Registry.configure({GO::G_ATOMICRMW_ADD, GO::G_ATOMICRMW_SUB, GO::G_ATOMICRMW_AND,
+ GO::G_ATOMICRMW_NAND, GO::G_ATOMICRMW_OR, GO::G_ATOMICRMW_XOR,
+ GO::G_ATOMICRMW_MAX, GO::G_ATOMICRMW_MIN, GO::G_ATOMICRMW_UMAX,
+ GO::G_ATOMICRMW_UMIN, GO::G_ATOMICRMW_UINC_WRAP, GO::G_ATOMICRMW_UDEC_WRAP,
+ GO::G_ATOMICRMW_USUB_COND, GO::G_ATOMICRMW_USUB_SAT},
+ {ScalarIntDef0, ScalarPointerUse1, ScalarIntUse0}, 1,
+ {InstructionFlag::MayLoad, InstructionFlag::MayStore}, false, 0, true);
+ Registry.configure({GO::G_ATOMICRMW_FADD, GO::G_ATOMICRMW_FSUB, GO::G_ATOMICRMW_FMAX,
+ GO::G_ATOMICRMW_FMIN, GO::G_ATOMICRMW_FMAXIMUM, GO::G_ATOMICRMW_FMINIMUM,
+ GO::G_ATOMICRMW_FMAXIMUMNUM, GO::G_ATOMICRMW_FMINIMUMNUM},
+ {ScalarFloatDef0, ScalarPointerUse1, ScalarFloatUse0}, 1,
+ {InstructionFlag::MayLoad, InstructionFlag::MayStore}, false, 0, true);
+ Registry.configure({GO::G_FENCE}, {immediate(), immediate()}, 0,
+ {InstructionFlag::HasSideEffects});
+ Registry.configure(
+ {GO::G_PREFETCH}, {ScalarPointerUse0, immediate(), immediate(), immediate()}, 0,
+ {InstructionFlag::MayLoad, InstructionFlag::MayStore, InstructionFlag::HasSideEffects}, false,
+ 0, true);
+ Registry.configure({GO::G_BRCOND}, {ScalarIntUse0, kind(MachineOperand::Kind::BasicBlock)}, 0,
+ {InstructionFlag::Terminator, InstructionFlag::Branch});
+ Registry.configure({GO::G_BRINDIRECT}, {ScalarPointerUse0}, 0,
+ {InstructionFlag::Terminator, InstructionFlag::Branch,
+ InstructionFlag::IndirectBranch, InstructionFlag::Barrier});
+ Registry.configure({GO::G_INVOKE_REGION_START}, {}, 0, {InstructionFlag::Terminator});
+ Registry.configure({GO::G_INTRINSIC, GO::G_INTRINSIC_W_SIDE_EFFECTS, GO::G_INTRINSIC_CONVERGENT,
+ GO::G_INTRINSIC_CONVERGENT_W_SIDE_EFFECTS},
+ {repeated(anyOperand())}, 0, {}, true, 1, false, PseudoOwner::None,
+ PipelineStage::Emission, true);
+ Registry.addFlag({GO::G_INTRINSIC_W_SIDE_EFFECTS, GO::G_INTRINSIC_CONVERGENT_W_SIDE_EFFECTS},
+ InstructionFlag::HasSideEffects);
+ Registry.addFlag({GO::G_INTRINSIC_W_SIDE_EFFECTS, GO::G_INTRINSIC_CONVERGENT_W_SIDE_EFFECTS},
+ InstructionFlag::MayLoad);
+ Registry.addFlag({GO::G_INTRINSIC_W_SIDE_EFFECTS, GO::G_INTRINSIC_CONVERGENT_W_SIDE_EFFECTS},
+ InstructionFlag::MayStore);
+ Registry.addFlag({GO::G_INTRINSIC_CONVERGENT, GO::G_INTRINSIC_CONVERGENT_W_SIDE_EFFECTS},
+ InstructionFlag::Convergent);
+
+ Registry.configure({GO::G_ANYEXT, GO::G_SEXT, GO::G_ZEXT}, {IntDef0, IntUse1}, 1);
+ Registry.configure({GO::G_TRUNC, GO::G_TRUNC_SSAT_S, GO::G_TRUNC_SSAT_U, GO::G_TRUNC_USAT_U},
+ {IntDef0, IntUse1}, 1);
+ Registry.configure(
+ {GO::G_CONSTANT},
+ {typedRegister(RegisterOperandRole::Def, ScalarInts, 0),
+ operand({MachineOperand::Kind::Immediate, MachineOperand::Kind::IntegerImmediate})},
+ 1);
+ Registry.configure({GO::G_FCONSTANT},
+ {ScalarFloatDef0, kind(MachineOperand::Kind::FloatingImmediate)}, 1);
+ Registry.configure({GO::G_VASTART}, {ScalarPointerUse0}, 0, {InstructionFlag::MayStore}, false, 0,
+ true);
+ Registry.configure({GO::G_VAARG}, {AnyDef0, ScalarPointerUse1, immediate()}, 1,
+ {InstructionFlag::MayLoad, InstructionFlag::MayStore}, false, 0, true);
+ Registry.configure({GO::G_SEXT_INREG}, {IntDef0, IntUse0, immediate()}, 1);
+ Registry.configure(
+ {GO::G_SHL, GO::G_LSHR, GO::G_ASHR, GO::G_ROTR, GO::G_ROTL, GO::G_USHLSAT, GO::G_SSHLSAT},
+ {IntDef0, IntUse0, IntUse1}, 1);
+ Registry.configure({GO::G_FSHL, GO::G_FSHR}, {IntDef0, IntUse0, IntUse0, IntUse1}, 1);
+ Registry.configure({GO::G_ICMP},
+ {IntDef0, kind(MachineOperand::Kind::Predicate), IntUse1, IntUse1}, 1);
+ Registry.configure({GO::G_FCMP},
+ {IntDef0, kind(MachineOperand::Kind::Predicate), FloatUse1, FloatUse1}, 1);
+ Registry.configure({GO::G_SCMP, GO::G_UCMP}, {IntDef0, IntUse1, IntUse1}, 1);
+ Registry.configure({GO::G_SELECT}, {AnyDef0, IntUse1, AnyUse0, AnyUse0}, 1);
+
+ Registry.configure({GO::G_UADDO, GO::G_USUBO, GO::G_SADDO, GO::G_SSUBO, GO::G_UMULO, GO::G_SMULO},
+ {IntDef0, IntDef1, IntUse0, IntUse0}, 2);
+ Registry.configure({GO::G_UADDE, GO::G_USUBE, GO::G_SADDE, GO::G_SSUBE},
+ {IntDef0, IntDef1, IntUse0, IntUse0, IntUse1}, 2);
+ Registry.addFlag({GO::G_UADDO, GO::G_SADDO, GO::G_UMULO, GO::G_SMULO},
+ InstructionFlag::Commutable);
+ Registry.configure({GO::G_SMULFIX, GO::G_UMULFIX, GO::G_SMULFIXSAT, GO::G_UMULFIXSAT,
+ GO::G_SDIVFIX, GO::G_UDIVFIX, GO::G_SDIVFIXSAT, GO::G_UDIVFIXSAT},
+ {IntDef0, IntUse0, IntUse0, immediate()}, 1);
+ Registry.addFlag({GO::G_SMULFIX, GO::G_UMULFIX, GO::G_SMULFIXSAT, GO::G_UMULFIXSAT},
+ InstructionFlag::Commutable);
+
+ Registry.configure({GO::G_FNEG, GO::G_FABS, GO::G_FCANONICALIZE, GO::G_FEXP, GO::G_FEXP2,
+ GO::G_FEXP10, GO::G_FLOG, GO::G_FLOG2, GO::G_FLOG10, GO::G_FCEIL,
+ GO::G_FCOS, GO::G_FSIN, GO::G_FTAN, GO::G_FACOS, GO::G_FASIN,
+ GO::G_FATAN, GO::G_FCOSH, GO::G_FSINH, GO::G_FTANH, GO::G_FSQRT,
+ GO::G_FFLOOR, GO::G_FRINT, GO::G_FNEARBYINT},
+ {FloatDef0, FloatUse0}, 1);
+ Registry.configure({GO::G_FPEXT, GO::G_FPTRUNC}, {FloatDef0, FloatUse1}, 1);
+ Registry.configure({GO::G_FPTOSI, GO::G_FPTOUI, GO::G_FPTOSI_SAT, GO::G_FPTOUI_SAT},
+ {IntDef0, FloatUse1}, 1);
+ Registry.configure({GO::G_SITOFP, GO::G_UITOFP}, {FloatDef0, IntUse1}, 1);
+ Registry.configure({GO::G_FCOPYSIGN}, {FloatDef0, FloatUse0, FloatUse1}, 1);
+ Registry.configure({GO::G_IS_FPCLASS}, {IntDef0, FloatUse1, immediate()}, 1);
+ Registry.configure({GO::G_FADD, GO::G_FSUB, GO::G_FMUL, GO::G_FDIV, GO::G_FREM, GO::G_FPOW,
+ GO::G_FATAN2, GO::G_FMINNUM, GO::G_FMAXNUM, GO::G_FMINNUM_IEEE,
+ GO::G_FMAXNUM_IEEE, GO::G_FMINIMUM, GO::G_FMAXIMUM, GO::G_FMINIMUMNUM,
+ GO::G_FMAXIMUMNUM},
+ {FloatDef0, FloatUse0, FloatUse0}, 1);
+ Registry.addFlag({GO::G_FADD, GO::G_FMUL, GO::G_FMINNUM, GO::G_FMAXNUM, GO::G_FMINNUM_IEEE,
+ GO::G_FMAXNUM_IEEE, GO::G_FMINIMUM, GO::G_FMAXIMUM, GO::G_FMINIMUMNUM,
+ GO::G_FMAXIMUMNUM},
+ InstructionFlag::Commutable);
+ Registry.configure({GO::G_FMA, GO::G_FMAD}, {FloatDef0, FloatUse0, FloatUse0, FloatUse0}, 1);
+ Registry.configure({GO::G_FMODF, GO::G_FSINCOS}, {FloatDef0, FloatDef0, FloatUse0}, 2);
+ Registry.configure({GO::G_FPOWI, GO::G_FLDEXP}, {FloatDef0, FloatUse0, IntUse1}, 1);
+ Registry.configure({GO::G_FFREXP}, {FloatDef0, IntDef1, FloatUse0}, 2);
+
+ Registry.configure({GO::G_GET_FPENV, GO::G_GET_FPMODE}, {AnyDef0}, 1,
+ {InstructionFlag::HasSideEffects});
+ Registry.configure({GO::G_SET_FPENV, GO::G_SET_FPMODE}, {AnyUse0}, 0,
+ {InstructionFlag::HasSideEffects});
+ Registry.configure({GO::G_RESET_FPENV, GO::G_RESET_FPMODE}, {}, 0,
+ {InstructionFlag::HasSideEffects});
+ Registry.configure({GO::G_GET_ROUNDING}, {IntDef0}, 1, {InstructionFlag::HasSideEffects});
+ Registry.configure({GO::G_SET_ROUNDING}, {IntUse0}, 0, {InstructionFlag::HasSideEffects});
+ Registry.configure({GO::G_PTR_ADD}, {PointerDef0, PointerUse0, IntUse1}, 1);
+ Registry.configure({GO::G_PTRMASK}, {PointerDef0, PointerUse0, IntUse1}, 1);
+ Registry.configure({GO::G_ABS}, {IntDef0, IntUse0}, 1);
+ Registry.configure({GO::G_LROUND, GO::G_LLROUND}, {IntDef0, FloatUse1}, 1);
+ Registry.configure(
+ {GO::G_BR}, {kind(MachineOperand::Kind::BasicBlock)}, 0,
+ {InstructionFlag::Terminator, InstructionFlag::Branch, InstructionFlag::Barrier});
+ Registry.configure({GO::G_BRJT},
+ {ScalarPointerUse0, kind(MachineOperand::Kind::JumpTableIndex), ScalarIntUse1},
+ 0,
+ {InstructionFlag::Terminator, InstructionFlag::Branch,
+ InstructionFlag::IndirectBranch, InstructionFlag::Barrier});
+ Registry.configure({GO::G_VSCALE}, {ScalarIntDef0, immediate()}, 1);
+
+ Registry.configure({GO::G_INSERT_SUBVECTOR},
+ {VectorDef0, typedRegister(RegisterOperandRole::Use, VectorTypes, 0),
+ typedRegister(RegisterOperandRole::Use, VectorTypes, 1), immediate()},
+ 1);
+ Registry.configure(
+ {GO::G_EXTRACT_SUBVECTOR},
+ {VectorDef0, typedRegister(RegisterOperandRole::Use, VectorTypes, 1), immediate()}, 1);
+ Registry.configure(
+ {GO::G_INSERT_VECTOR_ELT},
+ {VectorDef0, typedRegister(RegisterOperandRole::Use, VectorTypes, 0), AnyUse1, ScalarIntUse2},
+ 1);
+ Registry.configure(
+ {GO::G_EXTRACT_VECTOR_ELT},
+ {AnyDef0, typedRegister(RegisterOperandRole::Use, VectorTypes, 1), ScalarIntUse2}, 1);
+ Registry.configure({GO::G_SHUFFLE_VECTOR},
+ {VectorDef0, typedRegister(RegisterOperandRole::Use, VectorTypes, 1),
+ typedRegister(RegisterOperandRole::Use, VectorTypes, 1),
+ kind(MachineOperand::Kind::ShuffleMask)},
+ 1);
+ Registry.configure({GO::G_SPLAT_VECTOR}, {VectorDef0, AnyUse1}, 1);
+ Registry.configure({GO::G_STEP_VECTOR}, {VectorDef0, immediate()}, 1);
+ Registry.configure({GO::G_VECTOR_COMPRESS},
+ {VectorDef0, typedRegister(RegisterOperandRole::Use, VectorTypes, 0),
+ typedRegister(RegisterOperandRole::Use, VectorTypes, 1),
+ typedRegister(RegisterOperandRole::Use, VectorTypes, 0)},
+ 1);
+
+ Registry.configure({GO::G_CTTZ, GO::G_CTTZ_ZERO_POISON, GO::G_CTLZ, GO::G_CTLZ_ZERO_POISON,
+ GO::G_CTLS, GO::G_CTPOP, GO::G_BSWAP, GO::G_BITREVERSE},
+ {IntDef0, IntUse0}, 1);
+ Registry.configure({GO::G_ADDRSPACE_CAST}, {PointerDef0, PointerUse1}, 1);
+ Registry.configure({GO::G_BLOCK_ADDR},
+ {ScalarPointerDef0, kind(MachineOperand::Kind::BlockAddress)}, 1);
+ Registry.configure({GO::G_JUMP_TABLE},
+ {ScalarPointerDef0, kind(MachineOperand::Kind::JumpTableIndex)}, 1);
+ Registry.configure({GO::G_DYN_STACKALLOC}, {ScalarPointerDef0, ScalarIntUse1, immediate()}, 1,
+ {InstructionFlag::HasSideEffects});
+ Registry.configure({GO::G_STACKSAVE}, {ScalarPointerDef0}, 1, {InstructionFlag::HasSideEffects});
+ Registry.configure({GO::G_STACKRESTORE}, {ScalarPointerUse0}, 0,
+ {InstructionFlag::HasSideEffects});
+
+ Registry.configure({GO::G_STRICT_FADD, GO::G_STRICT_FSUB, GO::G_STRICT_FMUL, GO::G_STRICT_FDIV,
+ GO::G_STRICT_FREM},
+ {FloatDef0, FloatUse0, FloatUse0}, 1, {InstructionFlag::HasSideEffects});
+ Registry.configure({GO::G_STRICT_FMA}, {FloatDef0, FloatUse0, FloatUse0, FloatUse0}, 1,
+ {InstructionFlag::HasSideEffects});
+ Registry.configure({GO::G_STRICT_FSQRT}, {FloatDef0, FloatUse0}, 1,
+ {InstructionFlag::HasSideEffects});
+ Registry.configure({GO::G_STRICT_FLDEXP}, {FloatDef0, FloatUse0, IntUse1}, 1,
+ {InstructionFlag::HasSideEffects});
+ Registry.configure({GO::G_STRICT_FCMP, GO::G_STRICT_FCMPS},
+ {IntDef0, kind(MachineOperand::Kind::Predicate), FloatUse1, FloatUse1}, 1,
+ {InstructionFlag::HasSideEffects});
+ Registry.configure({GO::G_READ_REGISTER}, {AnyDef0, kind(MachineOperand::Kind::Metadata)}, 1,
+ {InstructionFlag::HasSideEffects, InstructionFlag::Convergent});
+ Registry.configure({GO::G_WRITE_REGISTER}, {kind(MachineOperand::Kind::Metadata), AnyUse0}, 0,
+ {InstructionFlag::HasSideEffects, InstructionFlag::Convergent});
+ Registry.configure({GO::G_MEMCPY, GO::G_MEMMOVE},
+ {ScalarPointerUse0, ScalarPointerUse1, ScalarIntUse2, immediate()}, 0,
+ {InstructionFlag::MayLoad, InstructionFlag::MayStore}, false, 0, true);
+ Registry.configure({GO::G_MEMCPY_INLINE}, {ScalarPointerUse0, ScalarPointerUse1, ScalarIntUse2},
+ 0, {InstructionFlag::MayLoad, InstructionFlag::MayStore}, false, 0, true);
+ Registry.configure({GO::G_MEMSET}, {ScalarPointerUse0, ScalarIntUse1, ScalarIntUse2, immediate()},
+ 0, {InstructionFlag::MayStore}, false, 0, true);
+ Registry.configure({GO::G_BZERO}, {ScalarPointerUse0, ScalarIntUse1, immediate()}, 0,
+ {InstructionFlag::MayStore}, false, 0, true);
+ Registry.configure({GO::G_TRAP}, {}, 0,
+ {InstructionFlag::HasSideEffects, InstructionFlag::MayStore});
+ Registry.configure({GO::G_DEBUGTRAP}, {}, 0, {InstructionFlag::HasSideEffects});
+ Registry.configure({GO::G_UBSANTRAP}, {immediate()}, 0, {InstructionFlag::HasSideEffects});
+
+ Registry.configure({GO::G_VECREDUCE_SEQ_FADD, GO::G_VECREDUCE_SEQ_FMUL},
+ {ScalarFloatDef0, typedRegister(RegisterOperandRole::Use, ScalarFloats, 1),
+ typedRegister(RegisterOperandRole::Use, VectorTypes, 2)},
+ 1);
+ Registry.configure({GO::G_VECREDUCE_FADD, GO::G_VECREDUCE_FMUL, GO::G_VECREDUCE_FMAX,
+ GO::G_VECREDUCE_FMIN, GO::G_VECREDUCE_FMAXIMUM, GO::G_VECREDUCE_FMINIMUM},
+ {ScalarFloatDef0, typedRegister(RegisterOperandRole::Use, VectorTypes, 1)}, 1);
+ Registry.configure({GO::G_VECREDUCE_ADD, GO::G_VECREDUCE_MUL, GO::G_VECREDUCE_AND,
+ GO::G_VECREDUCE_OR, GO::G_VECREDUCE_XOR, GO::G_VECREDUCE_SMAX,
+ GO::G_VECREDUCE_SMIN, GO::G_VECREDUCE_UMAX, GO::G_VECREDUCE_UMIN},
+ {ScalarIntDef0, typedRegister(RegisterOperandRole::Use, VectorTypes, 1)}, 1);
+ Registry.configure({GO::G_SBFX, GO::G_UBFX},
+ {ScalarIntDef0, ScalarIntUse0, ScalarIntUse1, ScalarIntUse1}, 1);
+}
+
+[[nodiscard]] std::vector<InstructionDescriptor> buildDescriptors() {
+ DescriptorRegistry Registry;
+ configureCoreOpcodes(Registry);
+ configureGenericOpcodes(Registry);
+ return Registry.take();
+}
+
+[[nodiscard]] const std::vector<InstructionDescriptor> &descriptors() {
+ static const std::vector<InstructionDescriptor> Value = buildDescriptors();
+ return Value;
+}
+
+} // namespace
+
+std::string_view getGenericOpcodeName(GenericOpcode OpcodeValue) noexcept {
+ const Opcode Value = opcode(OpcodeValue);
+ const auto Position =
+ std::ranges::find_if(descriptors(), [Value](const InstructionDescriptor &Descriptor) {
+ return Descriptor.InstructionOpcode == Value;
+ });
+ return Position == descriptors().end() ? std::string_view{} : std::string_view(Position->Name);
+}
+
+std::span<const InstructionDescriptor> genericInstructionDescriptors() noexcept {
+ return descriptors();
+}
+
+std::span<const ImmediatePredicate> genericImmediatePredicates() noexcept {
+ static const std::vector<ImmediatePredicate> Predicates;
+ return Predicates;
+}
+
+Expected<void> appendGenericInstructionInfo(std::vector<InstructionDescriptor> &Descriptors,
+ std::vector<ImmediatePredicate> &Predicates) {
+ const auto ExistingOpcode = [&Descriptors](Opcode Value) {
+ return std::ranges::any_of(Descriptors, [Value](const InstructionDescriptor &Descriptor) {
+ return Descriptor.InstructionOpcode == Value;
+ });
+ };
+ for (const InstructionDescriptor &Descriptor : descriptors()) {
+ if (ExistingOpcode(Descriptor.InstructionOpcode)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "target instruction set already contains a target-independent opcode");
+ }
+ Descriptors.push_back(Descriptor);
+ }
+ for (const ImmediatePredicate &Predicate : genericImmediatePredicates())
+ Predicates.push_back(Predicate);
+ return {};
+}
+
+} // namespace rcc
diff --git a/backend/lib/CodeGen/MachineAnalyses.cpp b/backend/lib/CodeGen/MachineAnalyses.cpp
new file mode 100644
index 0000000..cbc46fd
--- /dev/null
+++ b/backend/lib/CodeGen/MachineAnalyses.cpp
@@ -0,0 +1,850 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/MachineAnalyses.h"
+
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineInstr.h"
+#include "rcc/CodeGen/MachineMemOperand.h"
+#include "rcc/CodeGen/TargetDescription.h"
+
+#include <algorithm>
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <optional>
+#include <ranges>
+#include <span>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+namespace rcc {
+namespace {
+
+[[nodiscard]] std::vector<MachineBasicBlock *> linkedBlocks(MachineFunction &Function) {
+ std::vector<MachineBasicBlock *> Result;
+ Result.reserve(Function.blockCount());
+ for (MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; Block = Block->next())
+ Result.push_back(Block);
+ return Result;
+}
+
+[[nodiscard]] std::unordered_map<const MachineBasicBlock *, std::size_t>
+blockOrder(std::span<MachineBasicBlock *const> Blocks) {
+ std::unordered_map<const MachineBasicBlock *, std::size_t> Result;
+ for (std::size_t Index = 0; Index < Blocks.size(); ++Index)
+ Result.emplace(Blocks[Index], Index);
+ return Result;
+}
+
+[[nodiscard]] bool containsBlock(std::span<MachineBasicBlock *const> Blocks,
+ const MachineBasicBlock *Block) {
+ return std::ranges::find(Blocks, Block) != Blocks.end();
+}
+
+[[nodiscard]] std::uint64_t saturatingAdd(std::uint64_t Left, std::uint64_t Right) noexcept {
+ if (std::numeric_limits<std::uint64_t>::max() - Left < Right)
+ return std::numeric_limits<std::uint64_t>::max();
+ return Left + Right;
+}
+
+[[nodiscard]] std::uint64_t saturatingMultiply(std::uint64_t Value,
+ std::uint64_t Multiplier) noexcept {
+ if (Value != 0 && Multiplier > std::numeric_limits<std::uint64_t>::max() / Value)
+ return std::numeric_limits<std::uint64_t>::max();
+ return Value * Multiplier;
+}
+
+[[nodiscard]] std::uint64_t scaleByProbability(std::uint64_t Value,
+ BranchProbability Probability) noexcept {
+ constexpr std::uint64_t Denominator = std::numeric_limits<std::uint32_t>::max();
+ const std::uint64_t Numerator = Probability.rawValue();
+ const std::uint64_t Quotient = Value / Denominator;
+ const std::uint64_t Remainder = Value % Denominator;
+ return saturatingAdd(saturatingMultiply(Quotient, Numerator),
+ (Remainder * Numerator) / Denominator);
+}
+
+[[nodiscard]] bool isKnownDistinctObject(const MachineMemOperandDesc &Left,
+ const MachineMemOperandDesc &Right) noexcept {
+ const auto IsUniqueObject = [](MemoryBase::Kind Kind) {
+ return Kind == MemoryBase::Kind::FrameIndex || Kind == MemoryBase::Kind::ConstantPool ||
+ Kind == MemoryBase::Kind::JumpTable;
+ };
+ if (IsUniqueObject(Left.Base.kind()) && IsUniqueObject(Right.Base.kind()) &&
+ Left.Base.kind() != Right.Base.kind()) {
+ return true;
+ }
+ if (Left.Base.kind() != Right.Base.kind())
+ return false;
+ switch (Left.Base.kind()) {
+ case MemoryBase::Kind::FrameIndex:
+ return Left.Base.frameIndex() != Right.Base.frameIndex();
+ case MemoryBase::Kind::ConstantPool:
+ return Left.Base.constantPoolIndex() != Right.Base.constantPoolIndex();
+ case MemoryBase::Kind::JumpTable:
+ return Left.Base.jumpTableIndex() != Right.Base.jumpTableIndex();
+ default:
+ return false;
+ }
+}
+
+[[nodiscard]] bool hasOrderedMemory(const MachineInstr &Instruction) noexcept {
+ for (const MachineMemOperand *Memory : Instruction.memOperands()) {
+ if (Memory != nullptr && (Memory->isAtomic() || Memory->isVolatile()))
+ return true;
+ }
+ return false;
+}
+
+} // namespace
+
+Expected<void> MachineDominatorTreeAnalysis::Result::build(MachineFunction &Function) {
+ Blocks = linkedBlocks(Function);
+ BlockToIndex = blockOrder(Blocks);
+ Dominators.assign(Blocks.size(), std::vector<bool>(Blocks.size(), false));
+ ImmediateDominators.assign(Blocks.size(), nullptr);
+ Reachable.assign(Blocks.size(), false);
+ UnreachableBlocks.clear();
+ if (Blocks.empty())
+ return {};
+
+ std::vector<MachineBasicBlock *> Worklist{Blocks.front()};
+ Reachable[0] = true;
+ for (std::size_t Position = 0; Position < Worklist.size(); ++Position) {
+ MachineBasicBlock *Block = Worklist[Position];
+ for (const MachineSuccessor &Successor : Block->successors()) {
+ const auto Found = BlockToIndex.find(Successor.Block);
+ if (Successor.Block == nullptr || Found == BlockToIndex.end()) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "dominator analysis encountered an out-of-function CFG edge");
+ }
+ if (!Reachable[Found->second]) {
+ Reachable[Found->second] = true;
+ Worklist.push_back(Successor.Block);
+ }
+ }
+ }
+
+ for (std::size_t BlockIndex = 0; BlockIndex < Blocks.size(); ++BlockIndex) {
+ if (!Reachable[BlockIndex]) {
+ UnreachableBlocks.push_back(Blocks[BlockIndex]);
+ Dominators[BlockIndex][BlockIndex] = true;
+ continue;
+ }
+ if (BlockIndex == 0) {
+ Dominators[BlockIndex][BlockIndex] = true;
+ continue;
+ }
+ for (std::size_t Candidate = 0; Candidate < Blocks.size(); ++Candidate)
+ Dominators[BlockIndex][Candidate] = Reachable[Candidate];
+ }
+
+ bool Changed = true;
+ while (Changed) {
+ Changed = false;
+ for (std::size_t BlockIndex = 1; BlockIndex < Blocks.size(); ++BlockIndex) {
+ if (!Reachable[BlockIndex])
+ continue;
+ std::vector<bool> NewDominators(Blocks.size(), false);
+ bool HasReachablePredecessor = false;
+ for (MachineBasicBlock *Predecessor : Blocks[BlockIndex]->predecessors()) {
+ const auto Found = BlockToIndex.find(Predecessor);
+ if (Found == BlockToIndex.end() || !Reachable[Found->second])
+ continue;
+ if (!HasReachablePredecessor) {
+ NewDominators = Dominators[Found->second];
+ HasReachablePredecessor = true;
+ } else {
+ for (std::size_t Candidate = 0; Candidate < Blocks.size(); ++Candidate)
+ NewDominators[Candidate] =
+ NewDominators[Candidate] && Dominators[Found->second][Candidate];
+ }
+ }
+ if (!HasReachablePredecessor) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "reachable non-entry block has no reachable predecessor");
+ }
+ NewDominators[BlockIndex] = true;
+ if (NewDominators != Dominators[BlockIndex]) {
+ Dominators[BlockIndex] = std::move(NewDominators);
+ Changed = true;
+ }
+ }
+ }
+
+ for (std::size_t BlockIndex = 1; BlockIndex < Blocks.size(); ++BlockIndex) {
+ if (!Reachable[BlockIndex])
+ continue;
+ std::size_t Best = Blocks.size();
+ std::size_t BestDepth = 0;
+ for (std::size_t Candidate = 0; Candidate < Blocks.size(); ++Candidate) {
+ if (Candidate == BlockIndex || !Dominators[BlockIndex][Candidate])
+ continue;
+ const std::size_t Depth =
+ static_cast<std::size_t>(std::ranges::count(Dominators[Candidate], true));
+ if (Best == Blocks.size() || Depth > BestDepth) {
+ Best = Candidate;
+ BestDepth = Depth;
+ }
+ }
+ if (Best == Blocks.size())
+ return makeUnexpected(ErrorCode::InternalError, "reachable block has no immediate dominator");
+ ImmediateDominators[BlockIndex] = Blocks[Best];
+ }
+ return {};
+}
+
+Expected<void>
+MachineDominatorTreeAnalysis::Result::updateAfterCFGChange(MachineFunction &Function) {
+ return build(Function);
+}
+
+bool MachineDominatorTreeAnalysis::Result::isReachable(
+ const MachineBasicBlock &Block) const noexcept {
+ const auto Found = BlockToIndex.find(&Block);
+ return Found != BlockToIndex.end() && Reachable[Found->second];
+}
+
+bool MachineDominatorTreeAnalysis::Result::dominates(
+ const MachineBasicBlock &Dominator, const MachineBasicBlock &Block) const noexcept {
+ const auto DominatorPosition = BlockToIndex.find(&Dominator);
+ const auto BlockPosition = BlockToIndex.find(&Block);
+ if (DominatorPosition == BlockToIndex.end() || BlockPosition == BlockToIndex.end() ||
+ !Reachable[BlockPosition->second]) {
+ return false;
+ }
+ return Dominators[BlockPosition->second][DominatorPosition->second];
+}
+
+bool MachineDominatorTreeAnalysis::Result::dominates(
+ const MachineInstr &Dominator, const MachineInstr &Instruction) const noexcept {
+ const MachineBasicBlock *DominatorBlock = Dominator.parent();
+ const MachineBasicBlock *InstructionBlock = Instruction.parent();
+ if (DominatorBlock == nullptr || InstructionBlock == nullptr)
+ return false;
+ if (DominatorBlock != InstructionBlock)
+ return dominates(*DominatorBlock, *InstructionBlock);
+ for (const MachineInstr *Current = DominatorBlock->firstInstruction(); Current != nullptr;
+ Current = Current->next()) {
+ if (Current == &Dominator)
+ return true;
+ if (Current == &Instruction)
+ return false;
+ }
+ return false;
+}
+
+const MachineBasicBlock *MachineDominatorTreeAnalysis::Result::immediateDominator(
+ const MachineBasicBlock &Block) const noexcept {
+ const auto Found = BlockToIndex.find(&Block);
+ return Found == BlockToIndex.end() ? nullptr : ImmediateDominators[Found->second];
+}
+
+const MachineBasicBlock *MachineDominatorTreeAnalysis::Result::nearestCommonDominator(
+ const MachineBasicBlock &Left, const MachineBasicBlock &Right) const noexcept {
+ if (!isReachable(Left) || !isReachable(Right))
+ return nullptr;
+ std::unordered_set<const MachineBasicBlock *> LeftAncestors;
+ for (const MachineBasicBlock *Current = &Left; Current != nullptr;
+ Current = immediateDominator(*Current)) {
+ LeftAncestors.insert(Current);
+ }
+ for (const MachineBasicBlock *Current = &Right; Current != nullptr;
+ Current = immediateDominator(*Current)) {
+ if (LeftAncestors.contains(Current))
+ return Current;
+ }
+ return nullptr;
+}
+
+Expected<MachineDominatorTreeAnalysis::Result>
+MachineDominatorTreeAnalysis::run(MachineFunction &Function, const TargetDescription &,
+ MachineAnalysisManager &) {
+ Result ResultValue;
+ auto Built = ResultValue.build(Function);
+ if (!Built)
+ return std::unexpected(Built.error());
+ return ResultValue;
+}
+
+const MachineLoop *
+MachineLoopAnalysis::Result::loopFor(const MachineBasicBlock &Block) const noexcept {
+ const auto Found = InnermostLoop.find(&Block);
+ return Found == InnermostLoop.end() ? nullptr : &Loops[Found->second];
+}
+
+std::uint32_t
+MachineLoopAnalysis::Result::loopDepth(const MachineBasicBlock &Block) const noexcept {
+ const MachineLoop *Loop = loopFor(Block);
+ return Loop == nullptr ? 0 : Loop->Depth;
+}
+
+bool MachineLoopAnalysis::Result::isIrreducible(const MachineBasicBlock &Block) const noexcept {
+ return IrreducibleBlocks.contains(&Block);
+}
+
+Expected<MachineLoopAnalysis::Result> MachineLoopAnalysis::run(MachineFunction &Function,
+ const TargetDescription &Target,
+ MachineAnalysisManager &Analyses) {
+ auto Dominators = Analyses.getResult<MachineDominatorTreeAnalysis>(Function, Target);
+ if (!Dominators)
+ return std::unexpected(Dominators.error());
+
+ Result ResultValue;
+ std::vector<MachineBasicBlock *> Blocks = linkedBlocks(Function);
+ const auto Order = blockOrder(Blocks);
+ std::unordered_map<MachineBasicBlock *, std::unordered_set<MachineBasicBlock *>> LoopBlocks;
+ std::unordered_map<MachineBasicBlock *, std::vector<MachineBasicBlock *>> LoopLatches;
+
+ for (MachineBasicBlock *Latch : Blocks) {
+ if (!(*Dominators)->isReachable(*Latch))
+ continue;
+ for (const MachineSuccessor &Successor : Latch->successors()) {
+ MachineBasicBlock *Header = Successor.Block;
+ if (Header == nullptr || !(*Dominators)->dominates(*Header, *Latch))
+ continue;
+ auto &Members = LoopBlocks[Header];
+ Members.insert(Header);
+ Members.insert(Latch);
+ LoopLatches[Header].push_back(Latch);
+ std::vector<MachineBasicBlock *> Worklist;
+ if (Latch != Header)
+ Worklist.push_back(Latch);
+ for (std::size_t Position = 0; Position < Worklist.size(); ++Position) {
+ MachineBasicBlock *Current = Worklist[Position];
+ for (MachineBasicBlock *Predecessor : Current->predecessors()) {
+ if (!(*Dominators)->isReachable(*Predecessor) ||
+ !(*Dominators)->dominates(*Header, *Predecessor)) {
+ continue;
+ }
+ if (Members.insert(Predecessor).second && Predecessor != Header)
+ Worklist.push_back(Predecessor);
+ }
+ }
+ }
+ }
+
+ std::vector<MachineBasicBlock *> Headers;
+ Headers.reserve(LoopBlocks.size());
+ for (const auto &[Header, Members] : LoopBlocks) {
+ (void)Members;
+ Headers.push_back(Header);
+ }
+ std::ranges::sort(Headers,
+ [&Order](const MachineBasicBlock *Left, const MachineBasicBlock *Right) {
+ return Order.at(Left) < Order.at(Right);
+ });
+ for (MachineBasicBlock *Header : Headers) {
+ MachineLoop Loop;
+ Loop.Header = Header;
+ Loop.Blocks.assign(LoopBlocks[Header].begin(), LoopBlocks[Header].end());
+ Loop.Latches = LoopLatches[Header];
+ std::ranges::sort(Loop.Blocks,
+ [&Order](const MachineBasicBlock *Left, const MachineBasicBlock *Right) {
+ return Order.at(Left) < Order.at(Right);
+ });
+ std::ranges::sort(Loop.Latches,
+ [&Order](const MachineBasicBlock *Left, const MachineBasicBlock *Right) {
+ return Order.at(Left) < Order.at(Right);
+ });
+ Loop.Latches.erase(std::ranges::unique(Loop.Latches).begin(), Loop.Latches.end());
+ ResultValue.Loops.push_back(std::move(Loop));
+ }
+
+ for (std::size_t Index = 0; Index < ResultValue.Loops.size(); ++Index) {
+ const MachineLoop &Loop = ResultValue.Loops[Index];
+ std::size_t Parent = static_cast<std::size_t>(-1);
+ for (std::size_t Candidate = 0; Candidate < ResultValue.Loops.size(); ++Candidate) {
+ if (Candidate == Index || ResultValue.Loops[Candidate].Blocks.size() <= Loop.Blocks.size()) {
+ continue;
+ }
+ const bool Contains = std::ranges::all_of(Loop.Blocks, [&](MachineBasicBlock *Block) {
+ return containsBlock(ResultValue.Loops[Candidate].Blocks, Block);
+ });
+ if (Contains &&
+ (Parent == static_cast<std::size_t>(-1) ||
+ ResultValue.Loops[Candidate].Blocks.size() < ResultValue.Loops[Parent].Blocks.size())) {
+ Parent = Candidate;
+ }
+ }
+ ResultValue.Loops[Index].Parent = Parent;
+ }
+ for (std::size_t Index = 0; Index < ResultValue.Loops.size(); ++Index) {
+ const std::size_t Parent = ResultValue.Loops[Index].Parent;
+ if (Parent != static_cast<std::size_t>(-1))
+ ResultValue.Loops[Parent].Children.push_back(Index);
+ std::uint32_t Depth = 1;
+ for (std::size_t Ancestor = Parent; Ancestor != static_cast<std::size_t>(-1);
+ Ancestor = ResultValue.Loops[Ancestor].Parent) {
+ ++Depth;
+ }
+ ResultValue.Loops[Index].Depth = Depth;
+ }
+ for (std::size_t Index = 0; Index < ResultValue.Loops.size(); ++Index) {
+ for (MachineBasicBlock *Block : ResultValue.Loops[Index].Blocks) {
+ const auto Existing = ResultValue.InnermostLoop.find(Block);
+ if (Existing == ResultValue.InnermostLoop.end() ||
+ ResultValue.Loops[Existing->second].Depth < ResultValue.Loops[Index].Depth) {
+ ResultValue.InnermostLoop[Block] = Index;
+ }
+ }
+ }
+
+ std::unordered_set<MachineBasicBlock *> Visited;
+ std::vector<MachineBasicBlock *> FinishOrder;
+ for (MachineBasicBlock *Root : Blocks) {
+ if (!(*Dominators)->isReachable(*Root) || Visited.contains(Root))
+ continue;
+ std::vector<std::pair<MachineBasicBlock *, std::size_t>> Stack{{Root, 0}};
+ Visited.insert(Root);
+ while (!Stack.empty()) {
+ auto &[Current, NextSuccessor] = Stack.back();
+ if (NextSuccessor < Current->successors().size()) {
+ MachineBasicBlock *Next = Current->successors()[NextSuccessor++].Block;
+ if (Next != nullptr && (*Dominators)->isReachable(*Next) && Visited.insert(Next).second)
+ Stack.emplace_back(Next, 0);
+ continue;
+ }
+ FinishOrder.push_back(Current);
+ Stack.pop_back();
+ }
+ }
+
+ Visited.clear();
+ for (auto RootPosition = FinishOrder.rbegin(); RootPosition != FinishOrder.rend();
+ ++RootPosition) {
+ MachineBasicBlock *Root = *RootPosition;
+ if (!Visited.insert(Root).second)
+ continue;
+ std::vector<MachineBasicBlock *> Component;
+ std::vector<MachineBasicBlock *> Worklist{Root};
+ for (std::size_t Position = 0; Position < Worklist.size(); ++Position) {
+ MachineBasicBlock *Current = Worklist[Position];
+ Component.push_back(Current);
+ for (MachineBasicBlock *Predecessor : Current->predecessors()) {
+ if ((*Dominators)->isReachable(*Predecessor) && Visited.insert(Predecessor).second)
+ Worklist.push_back(Predecessor);
+ }
+ }
+ bool Cyclic = Component.size() > 1;
+ if (!Cyclic) {
+ Cyclic = std::ranges::any_of(
+ Component.front()->successors(),
+ [Root](const MachineSuccessor &Successor) { return Successor.Block == Root; });
+ }
+ if (!Cyclic)
+ continue;
+
+ std::unordered_set<MachineBasicBlock *> ComponentSet(Component.begin(), Component.end());
+ std::unordered_set<MachineBasicBlock *> EntrySet;
+ for (MachineBasicBlock *Block : Component) {
+ if (std::ranges::any_of(Block->predecessors(), [&](MachineBasicBlock *Predecessor) {
+ return !ComponentSet.contains(Predecessor);
+ })) {
+ EntrySet.insert(Block);
+ }
+ }
+ if (EntrySet.size() <= 1)
+ continue;
+
+ std::ranges::sort(Component,
+ [&Order](const MachineBasicBlock *Left, const MachineBasicBlock *Right) {
+ return Order.at(Left) < Order.at(Right);
+ });
+ IrreducibleMachineRegion Region;
+ Region.Blocks = std::move(Component);
+ Region.Entries.assign(EntrySet.begin(), EntrySet.end());
+ std::ranges::sort(Region.Entries,
+ [&Order](const MachineBasicBlock *Left, const MachineBasicBlock *Right) {
+ return Order.at(Left) < Order.at(Right);
+ });
+ for (MachineBasicBlock *Block : Region.Blocks)
+ ResultValue.IrreducibleBlocks.insert(Block);
+ ResultValue.IrreducibleRegions.push_back(std::move(Region));
+ }
+ std::ranges::sort(
+ ResultValue.IrreducibleRegions,
+ [&Order](const IrreducibleMachineRegion &Left, const IrreducibleMachineRegion &Right) {
+ return Order.at(Left.Blocks.front()) < Order.at(Right.Blocks.front());
+ });
+ return ResultValue;
+}
+
+BranchProbability
+MachineBranchProbabilityAnalysis::Result::probability(const MachineBasicBlock &From,
+ const MachineBasicBlock &To) const noexcept {
+ const auto Found = Probabilities.find(&From);
+ if (Found == Probabilities.end())
+ return BranchProbability::zero();
+ for (const MachineSuccessor &Successor : Found->second) {
+ if (Successor.Block == &To)
+ return Successor.Probability;
+ }
+ return BranchProbability::zero();
+}
+
+std::span<const MachineSuccessor> MachineBranchProbabilityAnalysis::Result::successors(
+ const MachineBasicBlock &Block) const noexcept {
+ const auto Found = Probabilities.find(&Block);
+ return Found == Probabilities.end() ? std::span<const MachineSuccessor>{}
+ : std::span<const MachineSuccessor>{Found->second};
+}
+
+Expected<MachineBranchProbabilityAnalysis::Result>
+MachineBranchProbabilityAnalysis::run(MachineFunction &Function, const TargetDescription &,
+ MachineAnalysisManager &) {
+ Result ResultValue;
+ const std::vector<MachineBasicBlock *> Blocks = linkedBlocks(Function);
+ const auto Order = blockOrder(Blocks);
+ for (MachineBasicBlock *Block : Blocks) {
+ std::vector<MachineSuccessor> Successors(Block->successors().begin(),
+ Block->successors().end());
+ for (const MachineSuccessor &Successor : Successors) {
+ if (Successor.Block == nullptr || !Order.contains(Successor.Block)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "branch-probability analysis encountered an invalid CFG edge");
+ }
+ }
+ ResultValue.Probabilities.emplace(Block, std::move(Successors));
+ }
+ return ResultValue;
+}
+
+std::uint64_t
+MachineBlockFrequencyAnalysis::Result::frequency(const MachineBasicBlock &Block) const noexcept {
+ const auto Found = Frequencies.find(&Block);
+ return Found == Frequencies.end() ? 0 : Found->second;
+}
+
+Expected<MachineBlockFrequencyAnalysis::Result>
+MachineBlockFrequencyAnalysis::run(MachineFunction &Function, const TargetDescription &Target,
+ MachineAnalysisManager &Analyses) {
+ auto Dominators = Analyses.getResult<MachineDominatorTreeAnalysis>(Function, Target);
+ auto Loops = Analyses.getResult<MachineLoopAnalysis>(Function, Target);
+ auto Probabilities = Analyses.getResult<MachineBranchProbabilityAnalysis>(Function, Target);
+ if (!Dominators)
+ return std::unexpected(Dominators.error());
+ if (!Loops)
+ return std::unexpected(Loops.error());
+ if (!Probabilities)
+ return std::unexpected(Probabilities.error());
+
+ Result ResultValue;
+ MachineBasicBlock *Entry = Function.firstBlock();
+ if (Entry == nullptr)
+ return ResultValue;
+
+ std::unordered_set<MachineBasicBlock *> Visited;
+ std::vector<MachineBasicBlock *> PostOrder;
+ std::vector<std::pair<MachineBasicBlock *, std::size_t>> Stack{{Entry, 0}};
+ Visited.insert(Entry);
+ while (!Stack.empty()) {
+ auto &[Current, NextSuccessor] = Stack.back();
+ const auto Successors = (*Probabilities)->successors(*Current);
+ if (NextSuccessor < Successors.size()) {
+ MachineBasicBlock *Next = Successors[NextSuccessor++].Block;
+ if (Next != nullptr && (*Dominators)->isReachable(*Next) && Visited.insert(Next).second)
+ Stack.emplace_back(Next, 0);
+ continue;
+ }
+ PostOrder.push_back(Current);
+ Stack.pop_back();
+ }
+ std::ranges::reverse(PostOrder);
+ std::unordered_map<MachineBasicBlock *, std::uint64_t> BaseFrequencies;
+ BaseFrequencies[Entry] = EntryFrequency;
+ for (MachineBasicBlock *Block : PostOrder) {
+ const std::uint64_t Frequency = BaseFrequencies[Block];
+
+ const auto Successors = (*Probabilities)->successors(*Block);
+ const bool UseUniformProbability =
+ !Successors.empty() && std::ranges::all_of(Successors, [](const MachineSuccessor &Edge) {
+ return Edge.Probability == BranchProbability::zero();
+ });
+ for (std::size_t SuccessorIndex = 0; SuccessorIndex < Successors.size(); ++SuccessorIndex) {
+ const MachineSuccessor &Successor = Successors[SuccessorIndex];
+ if (Successor.Block == nullptr || (*Dominators)->dominates(*Successor.Block, *Block))
+ continue;
+ std::uint64_t Contribution = 0;
+ if (UseUniformProbability) {
+ const std::uint64_t Count = Successors.size();
+ Contribution = Frequency / Count;
+ if (SuccessorIndex < Frequency % Count)
+ ++Contribution;
+ } else {
+ Contribution = scaleByProbability(Frequency, Successor.Probability);
+ }
+ BaseFrequencies[Successor.Block] =
+ saturatingAdd(BaseFrequencies[Successor.Block], Contribution);
+ }
+ }
+ for (MachineBasicBlock *Block : linkedBlocks(Function)) {
+ if (!(*Dominators)->isReachable(*Block)) {
+ ResultValue.Frequencies[Block] = 0;
+ continue;
+ }
+ std::uint64_t Frequency = std::max<std::uint64_t>(BaseFrequencies[Block], 1);
+ for (std::uint32_t Level = 0; Level < (*Loops)->loopDepth(*Block); ++Level)
+ Frequency = saturatingMultiply(Frequency, 8);
+ ResultValue.Frequencies[Block] = Frequency;
+ }
+ return ResultValue;
+}
+
+MachineAliasResult
+MachineAliasAnalysis::Result::alias(const MachineMemOperand &Left,
+ const MachineMemOperand &Right) const noexcept {
+ const MachineMemOperandDesc &LeftDesc = Left.description();
+ const MachineMemOperandDesc &RightDesc = Right.description();
+ if (isKnownDistinctObject(LeftDesc, RightDesc))
+ return MachineAliasResult::NoAlias;
+
+ const bool SameBase =
+ LeftDesc.Base == RightDesc.Base && LeftDesc.Base.kind() != MemoryBase::Kind::Unknown;
+ const bool SameAliasSource = LeftDesc.AliasSource && RightDesc.AliasSource &&
+ LeftDesc.AliasSource == RightDesc.AliasSource;
+ if (!SameBase && !SameAliasSource)
+ return MachineAliasResult::MayAlias;
+ if (!LeftDesc.Size.isKnown() || !RightDesc.Size.isKnown() || LeftDesc.Size.isScalable() ||
+ RightDesc.Size.isScalable()) {
+ return MachineAliasResult::MayAlias;
+ }
+
+ const std::uint64_t LeftSize = LeftDesc.Size.minimumBytes();
+ const std::uint64_t RightSize = RightDesc.Size.minimumBytes();
+ if (LeftDesc.Offset == RightDesc.Offset)
+ return LeftSize == RightSize ? MachineAliasResult::MustAlias : MachineAliasResult::PartialAlias;
+
+ const bool LeftFirst = LeftDesc.Offset < RightDesc.Offset;
+ const std::int64_t Lower = LeftFirst ? LeftDesc.Offset : RightDesc.Offset;
+ const std::int64_t Higher = LeftFirst ? RightDesc.Offset : LeftDesc.Offset;
+ const std::uint64_t LowerSize = LeftFirst ? LeftSize : RightSize;
+ const std::uint64_t Distance =
+ static_cast<std::uint64_t>(Higher) - static_cast<std::uint64_t>(Lower);
+ return Distance >= LowerSize ? MachineAliasResult::NoAlias : MachineAliasResult::PartialAlias;
+}
+
+Expected<MachineAliasAnalysis::Result>
+MachineAliasAnalysis::run(MachineFunction &, const TargetDescription &, MachineAnalysisManager &) {
+ return Result{};
+}
+
+std::span<const MachineMemoryDependency> MachineMemoryDependenceAnalysis::Result::dependencies(
+ const MachineInstr &Instruction) const noexcept {
+ const auto Found = DestinationRanges.find(Instruction.stableID());
+ if (Found == DestinationRanges.end())
+ return {};
+ return std::span<const MachineMemoryDependency>(Dependencies)
+ .subspan(Found->second.Begin, Found->second.Size);
+}
+
+const MachineMemoryDependenceRegion *
+MachineMemoryDependenceAnalysis::Result::regionFor(const MachineBasicBlock &Block) const noexcept {
+ const auto Found = BlockToRegion.find(&Block);
+ return Found == BlockToRegion.end() ? nullptr : &Regions[Found->second];
+}
+
+std::span<const MachineMemoryDependency> MachineMemoryDependenceAnalysis::Result::dependencies(
+ const MachineBasicBlock &Block) const noexcept {
+ const auto Found = BlockRanges.find(&Block);
+ if (Found == BlockRanges.end())
+ return {};
+ return std::span<const MachineMemoryDependency>(Dependencies)
+ .subspan(Found->second.Begin, Found->second.Size);
+}
+
+Expected<MachineMemoryDependenceAnalysis::Result>
+MachineMemoryDependenceAnalysis::run(MachineFunction &Function, const TargetDescription &Target,
+ MachineAnalysisManager &Analyses) {
+ auto Alias = Analyses.getResult<MachineAliasAnalysis>(Function, Target);
+ if (!Alias)
+ return std::unexpected(Alias.error());
+
+ struct MemoryNode final {
+ MachineInstr *Instruction = nullptr;
+ bool Reads = false;
+ bool Writes = false;
+ bool Ordered = false;
+ bool Barrier = false;
+ };
+
+ Result ResultValue;
+ for (MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; Block = Block->next()) {
+ std::vector<MemoryNode> Nodes;
+ for (MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr;
+ Instruction = Instruction->next()) {
+ const InstructionDescriptor *Descriptor = Target.instructionDescriptor(Instruction->opcode());
+ if (Descriptor == nullptr) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "memory-dependence analysis encountered an unknown opcode");
+ }
+ MemoryNode Node;
+ Node.Instruction = Instruction;
+ Node.Reads = Descriptor->Flags.has(InstructionFlag::MayLoad);
+ Node.Writes = Descriptor->Flags.has(InstructionFlag::MayStore);
+ Node.Ordered = hasOrderedMemory(*Instruction);
+ Node.Barrier = Descriptor->Flags.has(InstructionFlag::Call) ||
+ Descriptor->Flags.has(InstructionFlag::UnmodeledSideEffects) ||
+ (Descriptor->Flags.has(InstructionFlag::HasSideEffects) &&
+ Instruction->memOperands().empty());
+ for (const MachineMemOperand *Memory : Instruction->memOperands()) {
+ if (Memory == nullptr)
+ continue;
+ Node.Reads = Node.Reads || Memory->isLoad();
+ Node.Writes = Node.Writes || Memory->isStore();
+ }
+ if (Node.Reads || Node.Writes || Node.Ordered || Node.Barrier)
+ Nodes.push_back(Node);
+ }
+
+ MachineMemoryDependenceRegion Region;
+ Region.Block = Block;
+ Region.Instructions.reserve(Nodes.size());
+ for (const MemoryNode &Node : Nodes)
+ Region.Instructions.push_back(Node.Instruction->stableID());
+ ResultValue.BlockToRegion.emplace(Block, ResultValue.Regions.size());
+ ResultValue.Regions.push_back(std::move(Region));
+
+ const std::size_t RegionBegin = ResultValue.Dependencies.size();
+ for (std::size_t Destination = 0; Destination < Nodes.size(); ++Destination) {
+ for (std::size_t Source = 0; Source < Destination; ++Source) {
+ const MemoryNode &From = Nodes[Source];
+ const MemoryNode &To = Nodes[Destination];
+ if (!From.Writes && !To.Writes && !From.Ordered && !To.Ordered && !From.Barrier &&
+ !To.Barrier) {
+ continue;
+ }
+
+ bool MayAlias = From.Barrier || To.Barrier || From.Ordered || To.Ordered ||
+ From.Instruction->memOperands().empty() ||
+ To.Instruction->memOperands().empty();
+ if (!MayAlias) {
+ for (const MachineMemOperand *Left : From.Instruction->memOperands()) {
+ for (const MachineMemOperand *Right : To.Instruction->memOperands()) {
+ if (Left != nullptr && Right != nullptr &&
+ (*Alias)->alias(*Left, *Right) != MachineAliasResult::NoAlias) {
+ MayAlias = true;
+ break;
+ }
+ }
+ if (MayAlias)
+ break;
+ }
+ }
+ if (!MayAlias)
+ continue;
+
+ MachineMemoryDependencyKind Kind = MachineMemoryDependencyKind::Ordered;
+ if (From.Writes && To.Reads)
+ Kind = MachineMemoryDependencyKind::Flow;
+ else if (From.Reads && To.Writes)
+ Kind = MachineMemoryDependencyKind::Anti;
+ else if (From.Writes && To.Writes)
+ Kind = MachineMemoryDependencyKind::Output;
+ ResultValue.Dependencies.push_back(
+ {From.Instruction->stableID(), To.Instruction->stableID(), Kind});
+ }
+ }
+
+ const std::size_t RegionEnd = ResultValue.Dependencies.size();
+ ResultValue.BlockRanges.emplace(Block, Result::Range{RegionBegin, RegionEnd - RegionBegin});
+ for (std::size_t Begin = RegionBegin; Begin < RegionEnd;) {
+ std::size_t End = Begin + 1;
+ while (End < RegionEnd && ResultValue.Dependencies[End].Destination ==
+ ResultValue.Dependencies[Begin].Destination) {
+ ++End;
+ }
+ ResultValue.DestinationRanges.emplace(ResultValue.Dependencies[Begin].Destination,
+ Result::Range{Begin, End - Begin});
+ Begin = End;
+ }
+ }
+ return ResultValue;
+}
+
+Expected<void> registerStage8MachineAnalyses(MachineAnalysisManager &Analyses) {
+ const std::array IDs{
+ MachineAnalysisManager::id<MachineDominatorTreeAnalysis>(),
+ MachineAnalysisManager::id<MachineLoopAnalysis>(),
+ MachineAnalysisManager::id<MachineBranchProbabilityAnalysis>(),
+ MachineAnalysisManager::id<MachineBlockFrequencyAnalysis>(),
+ MachineAnalysisManager::id<MachineAliasAnalysis>(),
+ MachineAnalysisManager::id<MachineMemoryDependenceAnalysis>(),
+ };
+ const std::size_t Registered = static_cast<std::size_t>(
+ std::ranges::count_if(IDs, [&Analyses](AnalysisID ID) { return Analyses.isRegistered(ID); }));
+ if (Registered == IDs.size())
+ return {};
+ if (Registered != 0) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "standard machine analyses are only partially registered");
+ }
+
+ auto Candidate = Analyses.forkEmpty();
+ if (!Candidate)
+ return std::unexpected(Candidate.error());
+ MachineAnalysisManager &Staging = **Candidate;
+
+ auto Dominators = Staging.registerAnalysis<MachineDominatorTreeAnalysis>({
+ .Name = "MachineDominatorTree",
+ .Dependencies = {},
+ .InvalidatedBy = {MachinePassChange::CFG, MachinePassChange::BlockLayout},
+ .ThreadSafety = PassThreadSafety::ThreadSafe,
+ });
+ if (!Dominators)
+ return std::unexpected(Dominators.error());
+ auto Loops = Staging.registerAnalysis<MachineLoopAnalysis>({
+ .Name = "MachineLoopInfo",
+ .Dependencies = {MachineAnalysisManager::id<MachineDominatorTreeAnalysis>()},
+ .InvalidatedBy = {MachinePassChange::CFG, MachinePassChange::BlockLayout},
+ .ThreadSafety = PassThreadSafety::ThreadSafe,
+ });
+ if (!Loops)
+ return std::unexpected(Loops.error());
+ auto Probabilities = Staging.registerAnalysis<MachineBranchProbabilityAnalysis>({
+ .Name = "MachineBranchProbability",
+ .Dependencies = {},
+ .InvalidatedBy = {MachinePassChange::CFG},
+ .ThreadSafety = PassThreadSafety::ThreadSafe,
+ });
+ if (!Probabilities)
+ return std::unexpected(Probabilities.error());
+ auto Frequencies = Staging.registerAnalysis<MachineBlockFrequencyAnalysis>({
+ .Name = "MachineBlockFrequency",
+ .Dependencies = {MachineAnalysisManager::id<MachineDominatorTreeAnalysis>(),
+ MachineAnalysisManager::id<MachineLoopAnalysis>(),
+ MachineAnalysisManager::id<MachineBranchProbabilityAnalysis>()},
+ .InvalidatedBy = {MachinePassChange::CFG, MachinePassChange::BlockLayout},
+ .ThreadSafety = PassThreadSafety::ThreadSafe,
+ });
+ if (!Frequencies)
+ return std::unexpected(Frequencies.error());
+ auto Alias = Staging.registerAnalysis<MachineAliasAnalysis>({
+ .Name = "MachineAliasAnalysis",
+ .Dependencies = {},
+ .InvalidatedBy = {MachinePassChange::Operands, MachinePassChange::Frame,
+ MachinePassChange::ConstantPool, MachinePassChange::JumpTable,
+ MachinePassChange::TargetState},
+ .ThreadSafety = PassThreadSafety::ThreadSafe,
+ });
+ if (!Alias)
+ return std::unexpected(Alias.error());
+ auto MemoryDependence = Staging.registerAnalysis<MachineMemoryDependenceAnalysis>({
+ .Name = "MachineMemoryDependence",
+ .Dependencies = {MachineAnalysisManager::id<MachineAliasAnalysis>()},
+ .InvalidatedBy = {MachinePassChange::Instructions, MachinePassChange::Operands,
+ MachinePassChange::CFG, MachinePassChange::BlockLayout,
+ MachinePassChange::Frame, MachinePassChange::ConstantPool,
+ MachinePassChange::JumpTable, MachinePassChange::TargetState},
+ .ThreadSafety = PassThreadSafety::ThreadSafe,
+ });
+ if (!MemoryDependence)
+ return std::unexpected(MemoryDependence.error());
+ Analyses.Registry = std::move(Staging.Registry);
+ return {};
+}
+
+} // namespace rcc
diff --git a/backend/lib/CodeGen/MachineIRBuilder.cpp b/backend/lib/CodeGen/MachineIRBuilder.cpp
index e1e8006..6887804 100644
--- a/backend/lib/CodeGen/MachineIRBuilder.cpp
+++ b/backend/lib/CodeGen/MachineIRBuilder.cpp
@@ -650,6 +650,19 @@ Expected<void> MachineIRBuilder::validateInstruction(const MachineInstr &Instruc
return {};
}
+Expected<void> MachineIRBuilder::verifyInsertPoint() const {
+ if (InsertBlock == nullptr)
+ return makeUnexpected(ErrorCode::InvalidArgument, "machine IR builder has no insertion point");
+ auto BlockValidation = validateBlock(*InsertBlock);
+ if (!BlockValidation)
+ return std::unexpected(BlockValidation.error());
+ if (InsertBefore != nullptr && InsertBefore->parent() != InsertBlock) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "machine IR builder insertion cursor is no longer in its block");
+ }
+ return {};
+}
+
Expected<void> MachineIRBuilder::setInsertPoint(MachineBasicBlock &Block) {
auto Validation = validateBlock(Block);
if (!Validation)
diff --git a/backend/lib/CodeGen/MachinePass.cpp b/backend/lib/CodeGen/MachinePass.cpp
new file mode 100644
index 0000000..e6dccd0
--- /dev/null
+++ b/backend/lib/CodeGen/MachinePass.cpp
@@ -0,0 +1,1370 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/MachinePass.h"
+
+#include "rcc/CodeGen/CompilationUnit.h"
+#include "rcc/CodeGen/MachineBasicBlock.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineFunctionCloner.h"
+#include "rcc/CodeGen/MachineInstr.h"
+#include "rcc/CodeGen/MachineOperand.h"
+#include "rcc/CodeGen/MachineVerifier.h"
+#include "rcc/CodeGen/TargetDescription.h"
+
+#include <algorithm>
+#include <atomic>
+#include <chrono>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <memory>
+#include <optional>
+#include <ranges>
+#include <string>
+#include <string_view>
+#include <thread>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+namespace rcc {
+
+thread_local MachinePassInstrumentation::BufferedEvents *MachinePassInstrumentation::ActiveBuffer =
+ nullptr;
+
+namespace {
+
+using Clock = std::chrono::steady_clock;
+
+[[nodiscard]] std::uint64_t elapsedNanoseconds(Clock::time_point Start) noexcept {
+ const auto Elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(Clock::now() - Start);
+ if (Elapsed.count() <= 0)
+ return 0;
+ return static_cast<std::uint64_t>(Elapsed.count());
+}
+
+[[nodiscard]] Error prefixedError(const Error &Source, std::string Prefix) {
+ Prefix.append(Source.message());
+ return Error(Source.code(), std::move(Prefix));
+}
+
+[[nodiscard]] bool hasDuplicateAnalysisIDs(std::span<const AnalysisID> IDs) {
+ std::unordered_set<AnalysisID> Unique;
+ for (const AnalysisID ID : IDs) {
+ if (ID == nullptr || !Unique.insert(ID).second)
+ return true;
+ }
+ return false;
+}
+
+[[nodiscard]] Expected<void> validateParameters(std::span<const PassParameter> Parameters) {
+ std::unordered_set<std::string> Names;
+ for (const PassParameter &Parameter : Parameters) {
+ if (Parameter.Name.empty())
+ return makeUnexpected(ErrorCode::InvalidArgument, "pass parameter name cannot be empty");
+ if (!Names.insert(Parameter.Name).second)
+ return makeUnexpected(ErrorCode::InvalidArgument, "pass parameter names must be unique");
+ if (Parameter.Required && Parameter.DefaultValue) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "a required pass parameter cannot have a default value");
+ }
+ }
+ return {};
+}
+
+void combineFingerprint(std::uint64_t &State, std::uint64_t Value) noexcept {
+ State ^= Value + 0x9e3779b97f4a7c15ULL + (State << 6U) + (State >> 2U);
+}
+
+[[nodiscard]] std::uint64_t machineFunctionFingerprint(const MachineFunction &Function) noexcept {
+ std::uint64_t Result = 0xcbf29ce484222325ULL;
+ combineFingerprint(Result, Function.stableID().value());
+ combineFingerprint(Result, Function.properties().rawValue());
+ combineFingerprint(Result, Function.blockCount());
+ combineFingerprint(Result, Function.allInstructions().size());
+ combineFingerprint(Result, Function.allMemOperands().size());
+
+ for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr;
+ Block = Block->next()) {
+ combineFingerprint(Result, Block->stableID().value());
+ combineFingerprint(Result, Block->instructionCount());
+ combineFingerprint(Result, Block->successors().size());
+ for (const MachineSuccessor &Successor : Block->successors()) {
+ combineFingerprint(Result,
+ Successor.Block == nullptr ? 0 : Successor.Block->stableID().value());
+ combineFingerprint(Result, Successor.Probability.rawValue());
+ }
+ for (const MachineBasicBlockLiveIn &LiveIn : Block->liveIns()) {
+ combineFingerprint(Result, LiveIn.PhysReg.number());
+ combineFingerprint(Result, LiveIn.Lanes.rawValue());
+ }
+
+ for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr;
+ Instruction = Instruction->next()) {
+ combineFingerprint(Result, Instruction->stableID().value());
+ combineFingerprint(Result, Instruction->opcode().value());
+ combineFingerprint(Result, Instruction->flags().rawValue());
+ combineFingerprint(Result, Instruction->operandCount());
+ combineFingerprint(Result, Instruction->memOperands().size());
+ for (const MachineOperand &Operand : Instruction->operands()) {
+ combineFingerprint(Result, static_cast<std::uint64_t>(Operand.kind()));
+ combineFingerprint(Result, Operand.targetFlags());
+ combineFingerprint(Result, Operand.registerFlags().rawValue());
+ combineFingerprint(Result, Operand.subReg().value());
+ if (const Register *Reg = Operand.getIf<Register>())
+ combineFingerprint(Result, Reg->opaqueValue());
+ else if (const std::int64_t *Immediate = Operand.getIf<std::int64_t>())
+ combineFingerprint(Result, static_cast<std::uint64_t>(*Immediate));
+ }
+ }
+ }
+ return Result;
+}
+
+[[nodiscard]] Expected<void> verifyCompilationUnit(const CompilationUnit &Unit,
+ const TargetDescription &Target) {
+ auto UnitVerification = Unit.verify();
+ if (!UnitVerification)
+ return std::unexpected(UnitVerification.error());
+ for (std::size_t Index = 0; Index < Unit.functionCount(); ++Index) {
+ const MachineFunction *Function = Unit.functionAt(Index);
+ if (Function == nullptr)
+ return makeUnexpected(ErrorCode::InternalError,
+ "compilation unit function order contains a null entry");
+ auto FunctionVerification = MachineVerifier::verify(*Function, Target);
+ if (!FunctionVerification)
+ return std::unexpected(FunctionVerification.error());
+ }
+ return {};
+}
+
+} // namespace
+
+Expected<void>
+MachineAnalysisManager::registerAnalysisImpl(MachineAnalysisRegistration Registration,
+ ErasedRunner Runner, ErasedInvalidator Invalidator) {
+ if (Registration.ID == nullptr || Registration.Name.empty() || !Runner || !Invalidator) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "machine analysis registration is incomplete");
+ }
+ if (hasDuplicateAnalysisIDs(Registration.Dependencies)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "machine analysis dependencies must be non-null and unique");
+ }
+ if (std::ranges::find(Registration.Dependencies, Registration.ID) !=
+ Registration.Dependencies.end()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "machine analysis cannot directly depend on itself");
+ }
+ if (Registry.contains(Registration.ID))
+ return makeUnexpected(ErrorCode::InvalidArgument, "machine analysis ID is already registered");
+ if (std::ranges::any_of(Registry, [&Registration](const auto &Entry) {
+ return Entry.second.Info.Name == Registration.Name;
+ })) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "machine analysis name is already registered");
+ }
+ Registry.emplace(Registration.ID, RegisteredAnalysis{std::move(Registration), std::move(Runner),
+ std::move(Invalidator)});
+ return {};
+}
+
+bool MachineAnalysisManager::isRegistered(AnalysisID ID) const noexcept {
+ return ID != nullptr && Registry.contains(ID);
+}
+
+bool MachineAnalysisManager::hasCached(const MachineFunction &Function,
+ AnalysisID ID) const noexcept {
+ const auto FunctionPosition = Cache.find(const_cast<MachineFunction *>(&Function));
+ if (FunctionPosition == Cache.end())
+ return false;
+ const auto AnalysisPosition = FunctionPosition->second.find(ID);
+ return AnalysisPosition != FunctionPosition->second.end() && AnalysisPosition->second.Value;
+}
+
+std::size_t
+MachineAnalysisManager::cachedResultCount(const MachineFunction &Function) const noexcept {
+ const auto Position = Cache.find(const_cast<MachineFunction *>(&Function));
+ return Position == Cache.end() ? 0 : Position->second.size();
+}
+
+Expected<void>
+MachineAnalysisManager::materializeDependencies(const RegisteredAnalysis &Registration,
+ MachineFunction &Function,
+ const TargetDescription &Target) {
+ for (const AnalysisID Dependency : Registration.Info.Dependencies) {
+ if (!Registry.contains(Dependency)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "machine analysis depends on an unregistered analysis");
+ }
+ auto Result = getResultImpl(Dependency, Function, Target);
+ if (!Result)
+ return std::unexpected(Result.error());
+ }
+ return {};
+}
+
+Expected<void *> MachineAnalysisManager::getResultImpl(AnalysisID ID, MachineFunction &Function,
+ const TargetDescription &Target) {
+ if (ID == nullptr)
+ return makeUnexpected(ErrorCode::InvalidArgument, "machine analysis ID cannot be null");
+
+ auto &FunctionCache = Cache[&Function];
+ if (const auto Cached = FunctionCache.find(ID);
+ Cached != FunctionCache.end() && Cached->second.Value) {
+ const auto Registration = Registry.find(ID);
+ const std::string_view Name = Registration == Registry.end()
+ ? std::string_view{"unknown-machine-analysis"}
+ : std::string_view{Registration->second.Info.Name};
+ if (Instrumentation != nullptr)
+ Instrumentation->afterAnalysis(Name, Function, true, 0);
+ return Cached->second.Value.get();
+ }
+
+ const auto RegistrationPosition = Registry.find(ID);
+ if (RegistrationPosition == Registry.end())
+ return makeUnexpected(ErrorCode::InvalidArgument, "machine analysis ID is not registered");
+ const RegisteredAnalysis &Registration = RegistrationPosition->second;
+
+ auto &Stack = ActiveComputations[&Function];
+ if (std::ranges::find(Stack, ID) != Stack.end()) {
+ Error Failure(ErrorCode::InvalidArgument,
+ "machine analysis dependency cycle reaches '" + Registration.Info.Name + "'");
+ if (Instrumentation != nullptr)
+ Instrumentation->analysisFailure(Registration.Info.Name, Function, Failure, 0);
+ return std::unexpected(std::move(Failure));
+ }
+
+ if (Instrumentation != nullptr)
+ Instrumentation->beforeAnalysis(Registration.Info.Name, Function);
+ const Clock::time_point Start = Clock::now();
+ Stack.push_back(ID);
+ auto Dependencies = materializeDependencies(Registration, Function, Target);
+ if (!Dependencies) {
+ Stack.pop_back();
+ if (Stack.empty())
+ ActiveComputations.erase(&Function);
+ Error Failure = prefixedError(Dependencies.error(),
+ "machine analysis '" + Registration.Info.Name + "' failed: ");
+ if (Instrumentation != nullptr) {
+ Instrumentation->analysisFailure(Registration.Info.Name, Function, Failure,
+ elapsedNanoseconds(Start));
+ }
+ return std::unexpected(std::move(Failure));
+ }
+
+ auto Computed = Registration.Runner(Function, Target, *this);
+ Stack.pop_back();
+ if (Stack.empty())
+ ActiveComputations.erase(&Function);
+ const std::uint64_t Duration = elapsedNanoseconds(Start);
+ if (!Computed) {
+ Error Failure = prefixedError(Computed.error(),
+ "machine analysis '" + Registration.Info.Name + "' failed: ");
+ if (Instrumentation != nullptr)
+ Instrumentation->analysisFailure(Registration.Info.Name, Function, Failure, Duration);
+ return std::unexpected(std::move(Failure));
+ }
+ if (!*Computed) {
+ Error Failure(ErrorCode::InternalError,
+ "machine analysis '" + Registration.Info.Name + "' returned a null result");
+ if (Instrumentation != nullptr)
+ Instrumentation->analysisFailure(Registration.Info.Name, Function, Failure, Duration);
+ return std::unexpected(std::move(Failure));
+ }
+
+ void *Result = Computed->get();
+ FunctionCache.emplace(ID, CacheEntry{std::move(*Computed), ID});
+ if (Instrumentation != nullptr)
+ Instrumentation->afterAnalysis(Registration.Info.Name, Function, false, Duration);
+ return Result;
+}
+
+void MachineAnalysisManager::invalidate(MachineFunction &Function,
+ const PreservedAnalyses &Preserved,
+ MachinePassChanges Changes) noexcept {
+ const auto FunctionPosition = Cache.find(&Function);
+ if (FunctionPosition == Cache.end() || Preserved.preservesAll() || Changes.empty())
+ return;
+
+ std::unordered_set<AnalysisID> Invalid;
+ for (auto &[ID, Entry] : FunctionPosition->second) {
+ if (Preserved.preserves(ID))
+ continue;
+ const auto Registration = Registry.find(ID);
+ if (Registration == Registry.end() ||
+ Registration->second.Info.InvalidatedBy.intersects(Changes)) {
+ const bool ShouldInvalidate =
+ Registration == Registry.end() || !Entry.Value || !Registration->second.Invalidator ||
+ Registration->second.Invalidator(Entry.Value.get(), Function, Preserved);
+ if (ShouldInvalidate)
+ Invalid.insert(ID);
+ }
+ }
+
+ bool Added = true;
+ while (Added) {
+ Added = false;
+ for (const auto &[ID, Entry] : FunctionPosition->second) {
+ (void)Entry;
+ if (Invalid.contains(ID) || Preserved.preserves(ID))
+ continue;
+ const auto Registration = Registry.find(ID);
+ if (Registration != Registry.end() &&
+ std::ranges::any_of(
+ Registration->second.Info.Dependencies,
+ [&Invalid](AnalysisID Dependency) { return Invalid.contains(Dependency); })) {
+ Added = Invalid.insert(ID).second || Added;
+ }
+ }
+ }
+
+ for (const AnalysisID ID : Invalid)
+ FunctionPosition->second.erase(ID);
+ if (FunctionPosition->second.empty())
+ Cache.erase(FunctionPosition);
+}
+
+void MachineAnalysisManager::clear(MachineFunction &Function) noexcept {
+ Cache.erase(&Function);
+ ActiveComputations.erase(&Function);
+}
+
+void MachineAnalysisManager::clearAll() noexcept {
+ Cache.clear();
+ ActiveComputations.clear();
+}
+
+Expected<std::unique_ptr<MachineAnalysisManager>> MachineAnalysisManager::forkEmpty() const {
+ if (!ActiveComputations.empty()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "cannot fork a machine analysis manager during analysis computation");
+ }
+ auto Result = std::make_unique<MachineAnalysisManager>();
+ Result->Registry = Registry;
+ return Result;
+}
+
+Expected<void> MachineAnalysisManager::validateParallelRequirements(
+ std::span<const AnalysisID> RequiredAnalyses) const {
+ std::vector<AnalysisID> Worklist(RequiredAnalyses.begin(), RequiredAnalyses.end());
+ std::unordered_set<AnalysisID> Visited;
+ for (std::size_t Index = 0; Index < Worklist.size(); ++Index) {
+ const AnalysisID ID = Worklist[Index];
+ if (ID == nullptr || !Visited.insert(ID).second)
+ continue;
+ const auto Position = Registry.find(ID);
+ if (Position == Registry.end()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "parallel machine pass requires an unregistered analysis");
+ }
+ if (Position->second.Info.ThreadSafety != PassThreadSafety::ThreadSafe) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "parallel machine pipeline requires analysis '" +
+ Position->second.Info.Name + "' to be marked thread-safe");
+ }
+ Worklist.insert(Worklist.end(), Position->second.Info.Dependencies.begin(),
+ Position->second.Info.Dependencies.end());
+ }
+ return {};
+}
+
+Expected<bool> MachineAnalysisManager::requestAction(std::string_view Action,
+ const MachineFunction &Function,
+ std::uint64_t Cost) {
+ if (Action.empty() || Cost == 0)
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "machine action requires a name and nonzero cost");
+ if (Instrumentation == nullptr)
+ return true;
+ return Instrumentation->requestAction(Action, Function, Cost);
+}
+
+Expected<void> CompilationUnitAnalysisManager::registerAnalysisImpl(
+ CompilationUnitAnalysisRegistration Registration, ErasedRunner Runner,
+ ErasedInvalidator Invalidator) {
+ if (Registration.ID == nullptr || Registration.Name.empty() || !Runner || !Invalidator) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "compilation-unit analysis registration is incomplete");
+ }
+ if (hasDuplicateAnalysisIDs(Registration.Dependencies)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "compilation-unit analysis dependencies must be non-null and unique");
+ }
+ if (std::ranges::find(Registration.Dependencies, Registration.ID) !=
+ Registration.Dependencies.end()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "compilation-unit analysis cannot directly depend on itself");
+ }
+ if (Registry.contains(Registration.ID)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "compilation-unit analysis ID is already registered");
+ }
+ if (std::ranges::any_of(Registry, [&Registration](const auto &Entry) {
+ return Entry.second.Info.Name == Registration.Name;
+ })) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "compilation-unit analysis name is already registered");
+ }
+ Registry.emplace(Registration.ID, RegisteredAnalysis{std::move(Registration), std::move(Runner),
+ std::move(Invalidator)});
+ return {};
+}
+
+bool CompilationUnitAnalysisManager::isRegistered(AnalysisID ID) const noexcept {
+ return ID != nullptr && Registry.contains(ID);
+}
+
+bool CompilationUnitAnalysisManager::hasCached(const CompilationUnit &Unit,
+ AnalysisID ID) const noexcept {
+ const auto UnitPosition = Cache.find(const_cast<CompilationUnit *>(&Unit));
+ if (UnitPosition == Cache.end())
+ return false;
+ const auto AnalysisPosition = UnitPosition->second.find(ID);
+ return AnalysisPosition != UnitPosition->second.end() && AnalysisPosition->second.Value;
+}
+
+Expected<void>
+CompilationUnitAnalysisManager::materializeDependencies(const RegisteredAnalysis &Registration,
+ CompilationUnit &Unit,
+ const TargetDescription &Target) {
+ for (const AnalysisID Dependency : Registration.Info.Dependencies) {
+ if (!Registry.contains(Dependency)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "compilation-unit analysis depends on an unregistered analysis");
+ }
+ auto Result = getResultImpl(Dependency, Unit, Target);
+ if (!Result)
+ return std::unexpected(Result.error());
+ }
+ return {};
+}
+
+Expected<void *> CompilationUnitAnalysisManager::getResultImpl(AnalysisID ID, CompilationUnit &Unit,
+ const TargetDescription &Target) {
+ if (ID == nullptr)
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "compilation-unit analysis ID cannot be null");
+ auto &UnitCache = Cache[&Unit];
+ if (const auto Cached = UnitCache.find(ID); Cached != UnitCache.end() && Cached->second.Value) {
+ const auto Registration = Registry.find(ID);
+ const std::string_view Name = Registration == Registry.end()
+ ? std::string_view{"unknown-compilation-unit-analysis"}
+ : std::string_view{Registration->second.Info.Name};
+ if (Instrumentation != nullptr)
+ Instrumentation->afterUnitAnalysis(Name, Unit, true, 0);
+ return Cached->second.Value.get();
+ }
+
+ const auto RegistrationPosition = Registry.find(ID);
+ if (RegistrationPosition == Registry.end()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "compilation-unit analysis ID is not registered");
+ }
+ const RegisteredAnalysis &Registration = RegistrationPosition->second;
+ auto &Stack = ActiveComputations[&Unit];
+ if (std::ranges::find(Stack, ID) != Stack.end()) {
+ Error Failure(ErrorCode::InvalidArgument,
+ "compilation-unit analysis dependency cycle reaches '" + Registration.Info.Name +
+ "'");
+ if (Instrumentation != nullptr)
+ Instrumentation->unitAnalysisFailure(Registration.Info.Name, Unit, Failure, 0);
+ return std::unexpected(std::move(Failure));
+ }
+
+ if (Instrumentation != nullptr)
+ Instrumentation->beforeUnitAnalysis(Registration.Info.Name, Unit);
+ const Clock::time_point Start = Clock::now();
+ Stack.push_back(ID);
+ auto Dependencies = materializeDependencies(Registration, Unit, Target);
+ if (!Dependencies) {
+ Stack.pop_back();
+ if (Stack.empty())
+ ActiveComputations.erase(&Unit);
+ Error Failure = prefixedError(Dependencies.error(), "compilation-unit analysis '" +
+ Registration.Info.Name + "' failed: ");
+ if (Instrumentation != nullptr) {
+ Instrumentation->unitAnalysisFailure(Registration.Info.Name, Unit, Failure,
+ elapsedNanoseconds(Start));
+ }
+ return std::unexpected(std::move(Failure));
+ }
+
+ auto Computed = Registration.Runner(Unit, Target, *this);
+ Stack.pop_back();
+ if (Stack.empty())
+ ActiveComputations.erase(&Unit);
+ const std::uint64_t Duration = elapsedNanoseconds(Start);
+ if (!Computed) {
+ Error Failure = prefixedError(Computed.error(), "compilation-unit analysis '" +
+ Registration.Info.Name + "' failed: ");
+ if (Instrumentation != nullptr)
+ Instrumentation->unitAnalysisFailure(Registration.Info.Name, Unit, Failure, Duration);
+ return std::unexpected(std::move(Failure));
+ }
+ if (!*Computed) {
+ Error Failure(ErrorCode::InternalError, "compilation-unit analysis '" + Registration.Info.Name +
+ "' returned a null result");
+ if (Instrumentation != nullptr)
+ Instrumentation->unitAnalysisFailure(Registration.Info.Name, Unit, Failure, Duration);
+ return std::unexpected(std::move(Failure));
+ }
+
+ void *Result = Computed->get();
+ UnitCache.emplace(ID, CacheEntry{std::move(*Computed), ID});
+ if (Instrumentation != nullptr)
+ Instrumentation->afterUnitAnalysis(Registration.Info.Name, Unit, false, Duration);
+ return Result;
+}
+
+void CompilationUnitAnalysisManager::invalidate(CompilationUnit &Unit,
+ const PreservedAnalyses &Preserved,
+ MachinePassChanges Changes) noexcept {
+ const auto UnitPosition = Cache.find(&Unit);
+ if (UnitPosition == Cache.end() || Preserved.preservesAll() || Changes.empty())
+ return;
+
+ std::unordered_set<AnalysisID> Invalid;
+ for (auto &[ID, Entry] : UnitPosition->second) {
+ if (Preserved.preserves(ID))
+ continue;
+ const auto Registration = Registry.find(ID);
+ if (Registration == Registry.end() ||
+ Registration->second.Info.InvalidatedBy.intersects(Changes)) {
+ const bool ShouldInvalidate =
+ Registration == Registry.end() || !Entry.Value || !Registration->second.Invalidator ||
+ Registration->second.Invalidator(Entry.Value.get(), Unit, Preserved);
+ if (ShouldInvalidate)
+ Invalid.insert(ID);
+ }
+ }
+
+ bool Added = true;
+ while (Added) {
+ Added = false;
+ for (const auto &[ID, Entry] : UnitPosition->second) {
+ (void)Entry;
+ if (Invalid.contains(ID) || Preserved.preserves(ID))
+ continue;
+ const auto Registration = Registry.find(ID);
+ if (Registration != Registry.end() &&
+ std::ranges::any_of(
+ Registration->second.Info.Dependencies,
+ [&Invalid](AnalysisID Dependency) { return Invalid.contains(Dependency); })) {
+ Added = Invalid.insert(ID).second || Added;
+ }
+ }
+ }
+ for (const AnalysisID InvalidID : Invalid)
+ UnitPosition->second.erase(InvalidID);
+ if (UnitPosition->second.empty())
+ Cache.erase(UnitPosition);
+}
+
+void CompilationUnitAnalysisManager::clear(CompilationUnit &Unit) noexcept {
+ Cache.erase(&Unit);
+ ActiveComputations.erase(&Unit);
+}
+
+void CompilationUnitAnalysisManager::clearAll() noexcept {
+ Cache.clear();
+ ActiveComputations.clear();
+}
+
+void MachinePassInstrumentation::addBeforeCallback(BeforeCallback Callback) {
+ if (!Callback)
+ return;
+ std::scoped_lock Lock(Mutex);
+ Before.push_back(std::move(Callback));
+}
+
+void MachinePassInstrumentation::addAfterCallback(AfterCallback Callback) {
+ if (!Callback)
+ return;
+ std::scoped_lock Lock(Mutex);
+ After.push_back(std::move(Callback));
+}
+
+void MachinePassInstrumentation::addFailureCallback(FailureCallback Callback) {
+ if (!Callback)
+ return;
+ std::scoped_lock Lock(Mutex);
+ Failure.push_back(std::move(Callback));
+}
+
+void MachinePassInstrumentation::addBeforeAnalysisCallback(BeforeAnalysisCallback Callback) {
+ if (!Callback)
+ return;
+ std::scoped_lock Lock(Mutex);
+ BeforeAnalysis.push_back(std::move(Callback));
+}
+
+void MachinePassInstrumentation::addAfterAnalysisCallback(AfterAnalysisCallback Callback) {
+ if (!Callback)
+ return;
+ std::scoped_lock Lock(Mutex);
+ AfterAnalysis.push_back(std::move(Callback));
+}
+
+void MachinePassInstrumentation::addAnalysisFailureCallback(AnalysisFailureCallback Callback) {
+ if (!Callback)
+ return;
+ std::scoped_lock Lock(Mutex);
+ AnalysisFailure.push_back(std::move(Callback));
+}
+
+void MachinePassInstrumentation::addIRPrintCallback(IRPrintCallback Callback) {
+ if (!Callback)
+ return;
+ std::scoped_lock Lock(Mutex);
+ IRPrint.push_back(std::move(Callback));
+}
+
+void MachinePassInstrumentation::addChangeCallback(ChangeCallback Callback) {
+ if (!Callback)
+ return;
+ std::scoped_lock Lock(Mutex);
+ Change.push_back(std::move(Callback));
+}
+
+void MachinePassInstrumentation::setActionFilter(ActionFilter NewFilter) {
+ std::scoped_lock Lock(Mutex);
+ Filter = std::move(NewFilter);
+}
+
+void MachinePassInstrumentation::setPerFunctionFuel(std::optional<std::uint64_t> Fuel) {
+ std::scoped_lock Lock(Mutex);
+ PerFunctionFuel = Fuel;
+ ConsumedFuel.clear();
+}
+
+void MachinePassInstrumentation::addBeforeUnitCallback(BeforeUnitCallback Callback) {
+ if (!Callback)
+ return;
+ std::scoped_lock Lock(Mutex);
+ BeforeUnit.push_back(std::move(Callback));
+}
+
+void MachinePassInstrumentation::addAfterUnitCallback(AfterUnitCallback Callback) {
+ if (!Callback)
+ return;
+ std::scoped_lock Lock(Mutex);
+ AfterUnit.push_back(std::move(Callback));
+}
+
+void MachinePassInstrumentation::addUnitFailureCallback(UnitFailureCallback Callback) {
+ if (!Callback)
+ return;
+ std::scoped_lock Lock(Mutex);
+ UnitFailure.push_back(std::move(Callback));
+}
+
+void MachinePassInstrumentation::addBeforeUnitAnalysisCallback(
+ BeforeUnitAnalysisCallback Callback) {
+ if (!Callback)
+ return;
+ std::scoped_lock Lock(Mutex);
+ BeforeUnitAnalysis.push_back(std::move(Callback));
+}
+
+void MachinePassInstrumentation::addAfterUnitAnalysisCallback(AfterUnitAnalysisCallback Callback) {
+ if (!Callback)
+ return;
+ std::scoped_lock Lock(Mutex);
+ AfterUnitAnalysis.push_back(std::move(Callback));
+}
+
+void MachinePassInstrumentation::addUnitAnalysisFailureCallback(
+ UnitAnalysisFailureCallback Callback) {
+ if (!Callback)
+ return;
+ std::scoped_lock Lock(Mutex);
+ UnitAnalysisFailure.push_back(std::move(Callback));
+}
+
+InstrumentationStatistic &MachinePassInstrumentation::statistic(std::string_view Name,
+ bool IsAnalysis) const {
+ const std::string Key = std::string(IsAnalysis ? "analysis:" : "pass:") + std::string(Name);
+ auto [Position, Inserted] = Statistics.try_emplace(Key);
+ if (Inserted) {
+ Position->second.Name = std::string(Name);
+ Position->second.IsAnalysis = IsAnalysis;
+ }
+ return Position->second;
+}
+
+bool MachinePassInstrumentation::bufferFunctionEvent(
+ const MachineFunction &Function, std::function<void(const MachineFunction &)> Replay) const {
+ if (ActiveBuffer == nullptr)
+ return false;
+
+ std::shared_ptr<const MachineFunction> Snapshot;
+ auto Cloned = cloneMachineFunction(
+ Function, *ActiveBuffer->Target,
+ MachineFunctionCloneOptions{.NewStableID = Function.stableID(), .NewName = std::nullopt});
+ if (Cloned) {
+ Snapshot = std::shared_ptr<const MachineFunction>(std::move(Cloned->ClonedFunction));
+ } else {
+ Snapshot =
+ std::shared_ptr<const MachineFunction>(&Function, [](const MachineFunction *) noexcept {});
+ }
+ ActiveBuffer->Events.emplace_back(
+ [Snapshot = std::move(Snapshot), Replay = std::move(Replay)]() { Replay(*Snapshot); });
+ return true;
+}
+
+void MachinePassInstrumentation::beginBuffering(BufferedEvents &Buffer,
+ const TargetDescription &Target) const noexcept {
+ Buffer.Target = &Target;
+ Buffer.Events.clear();
+ ActiveBuffer = &Buffer;
+}
+
+void MachinePassInstrumentation::endBuffering() const noexcept {
+ ActiveBuffer = nullptr;
+}
+
+void MachinePassInstrumentation::replayBufferedEvents(BufferedEvents &Buffer) const {
+ for (const std::function<void()> &Event : Buffer.Events)
+ Event();
+ Buffer.Events.clear();
+ Buffer.Target = nullptr;
+}
+
+void MachinePassInstrumentation::before(std::string_view Name,
+ const MachineFunction &Function) const {
+ if (bufferFunctionEvent(Function, [this, Name = std::string(Name)](const MachineFunction &Input) {
+ before(Name, Input);
+ })) {
+ return;
+ }
+ std::vector<BeforeCallback> Callbacks;
+ std::vector<IRPrintCallback> Printers;
+ {
+ std::scoped_lock Lock(Mutex);
+ Callbacks = Before;
+ Printers = IRPrint;
+ }
+ for (const BeforeCallback &Callback : Callbacks)
+ Callback(Name, Function);
+ for (const IRPrintCallback &Printer : Printers)
+ Printer(InstrumentationPhase::Before, Name, Function);
+}
+
+void MachinePassInstrumentation::after(std::string_view Name, const MachineFunction &Function,
+ const MachinePassResult &Result,
+ std::uint64_t Nanoseconds) const {
+ if (bufferFunctionEvent(Function, [this, Name = std::string(Name), Result,
+ Nanoseconds](const MachineFunction &Input) {
+ after(Name, Input, Result, Nanoseconds);
+ })) {
+ return;
+ }
+ std::vector<AfterCallback> Callbacks;
+ std::vector<IRPrintCallback> Printers;
+ {
+ std::scoped_lock Lock(Mutex);
+ InstrumentationStatistic &Record = statistic(Name, false);
+ ++Record.Invocations;
+ Record.Changes += Result.Changed ? 1U : 0U;
+ Record.TotalNanoseconds += Nanoseconds;
+ Callbacks = After;
+ Printers = IRPrint;
+ }
+ for (const AfterCallback &Callback : Callbacks)
+ Callback(Name, Function, Result);
+ for (const IRPrintCallback &Printer : Printers)
+ Printer(InstrumentationPhase::After, Name, Function);
+}
+
+void MachinePassInstrumentation::failure(std::string_view Name, const MachineFunction &Function,
+ const Error &ErrorValue, std::uint64_t Nanoseconds) const {
+ if (bufferFunctionEvent(Function, [this, Name = std::string(Name), ErrorValue,
+ Nanoseconds](const MachineFunction &Input) {
+ failure(Name, Input, ErrorValue, Nanoseconds);
+ })) {
+ return;
+ }
+ std::vector<FailureCallback> Callbacks;
+ {
+ std::scoped_lock Lock(Mutex);
+ InstrumentationStatistic &Record = statistic(Name, false);
+ ++Record.Invocations;
+ ++Record.Failures;
+ Record.TotalNanoseconds += Nanoseconds;
+ Callbacks = Failure;
+ }
+ for (const FailureCallback &Callback : Callbacks)
+ Callback(Name, Function, ErrorValue);
+}
+
+void MachinePassInstrumentation::beforeAnalysis(std::string_view Name,
+ const MachineFunction &Function) const {
+ if (bufferFunctionEvent(Function, [this, Name = std::string(Name)](const MachineFunction &Input) {
+ beforeAnalysis(Name, Input);
+ })) {
+ return;
+ }
+ std::vector<BeforeAnalysisCallback> Callbacks;
+ {
+ std::scoped_lock Lock(Mutex);
+ Callbacks = BeforeAnalysis;
+ }
+ for (const BeforeAnalysisCallback &Callback : Callbacks)
+ Callback(Name, Function);
+}
+
+void MachinePassInstrumentation::afterAnalysis(std::string_view Name,
+ const MachineFunction &Function, bool CacheHit,
+ std::uint64_t Nanoseconds) const {
+ if (bufferFunctionEvent(Function, [this, Name = std::string(Name), CacheHit,
+ Nanoseconds](const MachineFunction &Input) {
+ afterAnalysis(Name, Input, CacheHit, Nanoseconds);
+ })) {
+ return;
+ }
+ std::vector<AfterAnalysisCallback> Callbacks;
+ {
+ std::scoped_lock Lock(Mutex);
+ InstrumentationStatistic &Record = statistic(Name, true);
+ ++Record.Invocations;
+ Record.CacheHits += CacheHit ? 1U : 0U;
+ Record.TotalNanoseconds += Nanoseconds;
+ Callbacks = AfterAnalysis;
+ }
+ for (const AfterAnalysisCallback &Callback : Callbacks)
+ Callback(Name, Function, CacheHit, Nanoseconds);
+}
+
+void MachinePassInstrumentation::analysisFailure(std::string_view Name,
+ const MachineFunction &Function,
+ const Error &ErrorValue,
+ std::uint64_t Nanoseconds) const {
+ if (bufferFunctionEvent(Function, [this, Name = std::string(Name), ErrorValue,
+ Nanoseconds](const MachineFunction &Input) {
+ analysisFailure(Name, Input, ErrorValue, Nanoseconds);
+ })) {
+ return;
+ }
+ std::vector<AnalysisFailureCallback> Callbacks;
+ {
+ std::scoped_lock Lock(Mutex);
+ InstrumentationStatistic &Record = statistic(Name, true);
+ ++Record.Invocations;
+ ++Record.Failures;
+ Record.TotalNanoseconds += Nanoseconds;
+ Callbacks = AnalysisFailure;
+ }
+ for (const AnalysisFailureCallback &Callback : Callbacks)
+ Callback(Name, Function, ErrorValue);
+}
+
+void MachinePassInstrumentation::change(std::string_view Name, const MachineFunction &Function,
+ MachinePassChanges Changes, bool FingerprintChanged) const {
+ if (bufferFunctionEvent(Function, [this, Name = std::string(Name), Changes,
+ FingerprintChanged](const MachineFunction &Input) {
+ change(Name, Input, Changes, FingerprintChanged);
+ })) {
+ return;
+ }
+ std::vector<ChangeCallback> Callbacks;
+ {
+ std::scoped_lock Lock(Mutex);
+ Callbacks = Change;
+ }
+ for (const ChangeCallback &Callback : Callbacks)
+ Callback(Name, Function, Changes, FingerprintChanged);
+}
+
+Expected<bool> MachinePassInstrumentation::requestAction(std::string_view Action,
+ const MachineFunction &Function,
+ std::uint64_t Cost) {
+ std::scoped_lock ActionLock(ActionMutex);
+ ActionFilter CurrentFilter;
+ {
+ std::scoped_lock Lock(Mutex);
+ CurrentFilter = Filter;
+ }
+ if (CurrentFilter && !CurrentFilter(Action, Function, Cost))
+ return false;
+
+ std::scoped_lock Lock(Mutex);
+ if (!PerFunctionFuel)
+ return true;
+ std::uint64_t &Consumed = ConsumedFuel[Function.stableID().value()];
+ if (Consumed > *PerFunctionFuel || Cost > *PerFunctionFuel - Consumed)
+ return false;
+ Consumed += Cost;
+ return true;
+}
+
+void MachinePassInstrumentation::beforeUnit(std::string_view Name,
+ const CompilationUnit &Unit) const {
+ std::vector<BeforeUnitCallback> Callbacks;
+ {
+ std::scoped_lock Lock(Mutex);
+ Callbacks = BeforeUnit;
+ }
+ for (const BeforeUnitCallback &Callback : Callbacks)
+ Callback(Name, Unit);
+}
+
+void MachinePassInstrumentation::afterUnit(std::string_view Name, const CompilationUnit &Unit,
+ const CompilationUnitPassResult &Result,
+ std::uint64_t Nanoseconds) const {
+ std::vector<AfterUnitCallback> Callbacks;
+ {
+ std::scoped_lock Lock(Mutex);
+ InstrumentationStatistic &Record = statistic("unit:" + std::string(Name), false);
+ ++Record.Invocations;
+ Record.Changes += Result.Changed ? 1U : 0U;
+ Record.TotalNanoseconds += Nanoseconds;
+ Callbacks = AfterUnit;
+ }
+ for (const AfterUnitCallback &Callback : Callbacks)
+ Callback(Name, Unit, Result);
+}
+
+void MachinePassInstrumentation::unitFailure(std::string_view Name, const CompilationUnit &Unit,
+ const Error &ErrorValue,
+ std::uint64_t Nanoseconds) const {
+ std::vector<UnitFailureCallback> Callbacks;
+ {
+ std::scoped_lock Lock(Mutex);
+ InstrumentationStatistic &Record = statistic("unit:" + std::string(Name), false);
+ ++Record.Invocations;
+ ++Record.Failures;
+ Record.TotalNanoseconds += Nanoseconds;
+ Callbacks = UnitFailure;
+ }
+ for (const UnitFailureCallback &Callback : Callbacks)
+ Callback(Name, Unit, ErrorValue);
+}
+
+void MachinePassInstrumentation::beforeUnitAnalysis(std::string_view Name,
+ const CompilationUnit &Unit) const {
+ std::vector<BeforeUnitAnalysisCallback> Callbacks;
+ {
+ std::scoped_lock Lock(Mutex);
+ Callbacks = BeforeUnitAnalysis;
+ }
+ for (const BeforeUnitAnalysisCallback &Callback : Callbacks)
+ Callback(Name, Unit);
+}
+
+void MachinePassInstrumentation::afterUnitAnalysis(std::string_view Name,
+ const CompilationUnit &Unit, bool CacheHit,
+ std::uint64_t Nanoseconds) const {
+ std::vector<AfterUnitAnalysisCallback> Callbacks;
+ {
+ std::scoped_lock Lock(Mutex);
+ InstrumentationStatistic &Record = statistic("unit:" + std::string(Name), true);
+ ++Record.Invocations;
+ Record.CacheHits += CacheHit ? 1U : 0U;
+ Record.TotalNanoseconds += Nanoseconds;
+ Callbacks = AfterUnitAnalysis;
+ }
+ for (const AfterUnitAnalysisCallback &Callback : Callbacks)
+ Callback(Name, Unit, CacheHit, Nanoseconds);
+}
+
+void MachinePassInstrumentation::unitAnalysisFailure(std::string_view Name,
+ const CompilationUnit &Unit,
+ const Error &ErrorValue,
+ std::uint64_t Nanoseconds) const {
+ std::vector<UnitAnalysisFailureCallback> Callbacks;
+ {
+ std::scoped_lock Lock(Mutex);
+ InstrumentationStatistic &Record = statistic("unit:" + std::string(Name), true);
+ ++Record.Invocations;
+ ++Record.Failures;
+ Record.TotalNanoseconds += Nanoseconds;
+ Callbacks = UnitAnalysisFailure;
+ }
+ for (const UnitAnalysisFailureCallback &Callback : Callbacks)
+ Callback(Name, Unit, ErrorValue);
+}
+
+std::vector<InstrumentationStatistic> MachinePassInstrumentation::statistics() const {
+ std::vector<InstrumentationStatistic> Result;
+ {
+ std::scoped_lock Lock(Mutex);
+ Result.reserve(Statistics.size());
+ for (const auto &[Key, Record] : Statistics) {
+ (void)Key;
+ Result.push_back(Record);
+ }
+ }
+ std::ranges::sort(
+ Result, [](const InstrumentationStatistic &Left, const InstrumentationStatistic &Right) {
+ if (Left.IsAnalysis != Right.IsAnalysis)
+ return Left.IsAnalysis < Right.IsAnalysis;
+ return Left.Name < Right.Name;
+ });
+ return Result;
+}
+
+void MachinePassInstrumentation::clearStatistics() {
+ std::scoped_lock Lock(Mutex);
+ Statistics.clear();
+ ConsumedFuel.clear();
+}
+
+Expected<void> MachinePassManager::validateContract(const MachinePassContract &Contract) const {
+ if (Contract.Name.empty())
+ return makeUnexpected(ErrorCode::InvalidArgument, "machine pass must have a non-empty name");
+ if (Contract.Sets.intersects(Contract.Clears)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "machine pass cannot set and clear the same property");
+ }
+ if (hasDuplicateAnalysisIDs(Contract.RequiredAnalyses)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "machine pass required analyses must be non-null and unique");
+ }
+ return validateParameters(Contract.Parameters);
+}
+
+Expected<void> MachinePassManager::addPass(std::unique_ptr<MachineFunctionPass> Pass) {
+ if (!Pass)
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "machine pass manager cannot add a null pass");
+ auto Validation = validateContract(Pass->contract());
+ if (!Validation)
+ return std::unexpected(Validation.error());
+ Passes.push_back(std::move(Pass));
+ return {};
+}
+
+Expected<void> MachinePassManager::run(MachineFunction &Function, const TargetDescription &Target,
+ MachineAnalysisManager &Analyses,
+ MachinePassInstrumentation *Instrumentation) const {
+ Analyses.setInstrumentation(Instrumentation);
+ for (const std::unique_ptr<MachineFunctionPass> &Pass : Passes) {
+ const MachinePassContract &Contract = Pass->contract();
+ const std::string_view Name = Contract.Name;
+
+ if (const MachineFunctionProperties Missing =
+ Contract.Requires.missingFrom(Function.properties());
+ !Missing.empty()) {
+ Error Failure(ErrorCode::VerificationError,
+ "pass '" + std::string(Name) + "' requires properties: " + Missing.toString());
+ if (Instrumentation != nullptr)
+ Instrumentation->failure(Name, Function, Failure);
+ Analyses.setInstrumentation(nullptr);
+ return std::unexpected(std::move(Failure));
+ }
+
+ if (Options.VerifyBefore) {
+ auto Verification = MachineVerifier::verify(Function, Target);
+ if (!Verification) {
+ Error Failure = prefixedError(Verification.error(), "pass '" + std::string(Name) +
+ "' received invalid MachineIR: ");
+ if (Instrumentation != nullptr)
+ Instrumentation->failure(Name, Function, Failure);
+ Analyses.setInstrumentation(nullptr);
+ return std::unexpected(std::move(Failure));
+ }
+ }
+
+ for (const AnalysisID Required : Contract.RequiredAnalyses) {
+ auto Result = Analyses.getResultImpl(Required, Function, Target);
+ if (!Result) {
+ Error Failure =
+ prefixedError(Result.error(), "pass '" + std::string(Name) +
+ "' could not materialize a required analysis: ");
+ if (Instrumentation != nullptr)
+ Instrumentation->failure(Name, Function, Failure);
+ Analyses.setInstrumentation(nullptr);
+ return std::unexpected(std::move(Failure));
+ }
+ }
+
+ const std::uint64_t FingerprintBefore =
+ Options.DetectUnreportedChanges ? machineFunctionFingerprint(Function) : 0;
+ if (Instrumentation != nullptr)
+ Instrumentation->before(Name, Function);
+ const Clock::time_point Start = Clock::now();
+ auto PassResult = Pass->run(Function, Target, Analyses);
+ const std::uint64_t Duration = elapsedNanoseconds(Start);
+ if (!PassResult) {
+ Error Failure =
+ prefixedError(PassResult.error(), "pass '" + std::string(Name) + "' failed: ");
+ if (Instrumentation != nullptr)
+ Instrumentation->failure(Name, Function, Failure, Duration);
+ Analyses.clear(Function);
+ Analyses.setInstrumentation(nullptr);
+ return std::unexpected(std::move(Failure));
+ }
+
+ if (PassResult->Changed != !PassResult->Changes.empty()) {
+ Error Failure(ErrorCode::VerificationError,
+ "pass '" + std::string(Name) +
+ "' reported inconsistent changed state and modification categories");
+ if (Instrumentation != nullptr)
+ Instrumentation->failure(Name, Function, Failure, Duration);
+ Analyses.clear(Function);
+ Analyses.setInstrumentation(nullptr);
+ return std::unexpected(std::move(Failure));
+ }
+ if (!Contract.DeclaredChanges.contains(PassResult->Changes)) {
+ Error Failure(ErrorCode::VerificationError,
+ "pass '" + std::string(Name) +
+ "' reported a modification category absent from its registration");
+ if (Instrumentation != nullptr)
+ Instrumentation->failure(Name, Function, Failure, Duration);
+ Analyses.clear(Function);
+ Analyses.setInstrumentation(nullptr);
+ return std::unexpected(std::move(Failure));
+ }
+
+ bool FingerprintChanged = PassResult->Changed;
+ if (Options.DetectUnreportedChanges) {
+ FingerprintChanged = FingerprintBefore != machineFunctionFingerprint(Function);
+ if (FingerprintChanged && !PassResult->Changed) {
+ Error Failure(ErrorCode::VerificationError,
+ "pass '" + std::string(Name) +
+ "' changed MachineIR without reporting a modification");
+ if (Instrumentation != nullptr)
+ Instrumentation->failure(Name, Function, Failure, Duration);
+ Analyses.clear(Function);
+ Analyses.setInstrumentation(nullptr);
+ return std::unexpected(std::move(Failure));
+ }
+ }
+ if (Instrumentation != nullptr)
+ Instrumentation->change(Name, Function, PassResult->Changes, FingerprintChanged);
+
+ auto Transition =
+ Function.properties().applyTransition(Contract.Requires, Contract.Sets, Contract.Clears);
+ if (!Transition) {
+ Error Failure =
+ prefixedError(Transition.error(), "pass '" + std::string(Name) +
+ "' produced an invalid property transition: ");
+ if (Instrumentation != nullptr)
+ Instrumentation->failure(Name, Function, Failure, Duration);
+ Analyses.clear(Function);
+ Analyses.setInstrumentation(nullptr);
+ return std::unexpected(std::move(Failure));
+ }
+ Analyses.invalidate(Function, PassResult->Preserved, PassResult->Changes);
+
+ if (Options.VerifyEach) {
+ MachineVerifierOptions VerifyOptions;
+ VerifyOptions.Stage = Contract.VerificationStage;
+ auto Verification = MachineVerifier::verify(Function, Target, VerifyOptions);
+ if (!Verification) {
+ Error Failure = prefixedError(Verification.error(),
+ "pass '" + std::string(Name) + "' left invalid MachineIR: ");
+ if (Instrumentation != nullptr)
+ Instrumentation->failure(Name, Function, Failure, Duration);
+ Analyses.clear(Function);
+ Analyses.setInstrumentation(nullptr);
+ return std::unexpected(std::move(Failure));
+ }
+ }
+ if (Instrumentation != nullptr)
+ Instrumentation->after(Name, Function, *PassResult, Duration);
+ }
+ Analyses.setInstrumentation(nullptr);
+ return {};
+}
+
+Expected<void> MachinePassManager::runFunctions(CompilationUnit &Unit,
+ const TargetDescription &Target,
+ const MachineAnalysisManager &AnalysisPrototype,
+ std::size_t ThreadCount,
+ MachinePassInstrumentation *Instrumentation) const {
+ if (ThreadCount == 0)
+ return makeUnexpected(ErrorCode::InvalidArgument, "machine pass thread count must be nonzero");
+ auto UnitVerification = verifyCompilationUnit(Unit, Target);
+ if (!UnitVerification)
+ return std::unexpected(UnitVerification.error());
+ const std::size_t FunctionCount = Unit.functionCount();
+ if (FunctionCount == 0)
+ return {};
+ const std::size_t WorkerCount = std::min(ThreadCount, FunctionCount);
+ if (WorkerCount > 1) {
+ for (const std::unique_ptr<MachineFunctionPass> &Pass : Passes) {
+ if (Pass->contract().ThreadSafety != PassThreadSafety::ThreadSafe) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "parallel machine pipeline contains a pass not marked thread-safe");
+ }
+ auto AnalysisSafety =
+ AnalysisPrototype.validateParallelRequirements(Pass->contract().RequiredAnalyses);
+ if (!AnalysisSafety)
+ return std::unexpected(AnalysisSafety.error());
+ }
+ }
+ std::vector<std::unique_ptr<MachineAnalysisManager>> Managers;
+ Managers.reserve(FunctionCount);
+ for (std::size_t Index = 0; Index < FunctionCount; ++Index) {
+ auto Manager = AnalysisPrototype.forkEmpty();
+ if (!Manager)
+ return std::unexpected(Manager.error());
+ Managers.push_back(std::move(*Manager));
+ }
+
+ std::vector<std::optional<Error>> Failures(FunctionCount);
+ std::vector<MachinePassInstrumentation::BufferedEvents> EventBuffers(FunctionCount);
+ std::atomic<std::size_t> NextFunction = 0;
+ const auto Worker = [&]() {
+ while (true) {
+ const std::size_t Index = NextFunction.fetch_add(1, std::memory_order_relaxed);
+ if (Index >= FunctionCount)
+ return;
+ MachineFunction *Function = Unit.functionAt(Index);
+ if (Function == nullptr) {
+ Failures[Index] = Error(ErrorCode::InternalError,
+ "compilation unit contains a null function during parallel run");
+ continue;
+ }
+ if (Instrumentation != nullptr)
+ Instrumentation->beginBuffering(EventBuffers[Index], Target);
+ auto Result = run(*Function, Target, *Managers[Index], Instrumentation);
+ if (Instrumentation != nullptr)
+ Instrumentation->endBuffering();
+ if (!Result)
+ Failures[Index] = Result.error();
+ }
+ };
+
+ std::vector<std::thread> Workers;
+ Workers.reserve(WorkerCount);
+ for (std::size_t Index = 0; Index < WorkerCount; ++Index)
+ Workers.emplace_back(Worker);
+ for (std::thread &WorkerThread : Workers)
+ WorkerThread.join();
+
+ for (std::size_t Index = 0; Index < FunctionCount; ++Index) {
+ if (Instrumentation != nullptr)
+ Instrumentation->replayBufferedEvents(EventBuffers[Index]);
+ if (Failures[Index]) {
+ return std::unexpected(
+ prefixedError(*Failures[Index],
+ "function '" + std::string(Unit.functionAt(Index)->name()) + "' failed: "));
+ }
+ }
+ return {};
+}
+
+Expected<void> CompilationUnitPassManager::addPass(std::unique_ptr<CompilationUnitPass> Pass) {
+ if (!Pass) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "compilation-unit pass manager cannot add a null pass");
+ }
+ const CompilationUnitPassContract &Contract = Pass->contract();
+ if (Contract.Name.empty()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "compilation-unit pass must have a non-empty name");
+ }
+ if (hasDuplicateAnalysisIDs(Contract.RequiredAnalyses)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "compilation-unit pass required analyses must be non-null and unique");
+ }
+ auto Parameters = validateParameters(Contract.Parameters);
+ if (!Parameters)
+ return std::unexpected(Parameters.error());
+ Passes.push_back(std::move(Pass));
+ return {};
+}
+
+Expected<void> CompilationUnitPassManager::run(CompilationUnit &Unit,
+ const TargetDescription &Target,
+ CompilationUnitAnalysisManager &UnitAnalyses,
+ MachineAnalysisManager &FunctionAnalyses,
+ MachinePassInstrumentation *Instrumentation) const {
+ UnitAnalyses.setInstrumentation(Instrumentation);
+ FunctionAnalyses.setInstrumentation(Instrumentation);
+ for (const std::unique_ptr<CompilationUnitPass> &Pass : Passes) {
+ const CompilationUnitPassContract &Contract = Pass->contract();
+ const std::string_view Name = Contract.Name;
+
+ if (Options.VerifyBefore) {
+ auto Verification = verifyCompilationUnit(Unit, Target);
+ if (!Verification) {
+ Error Failure =
+ prefixedError(Verification.error(), "compilation-unit pass '" + std::string(Name) +
+ "' received invalid MachineIR: ");
+ if (Instrumentation != nullptr)
+ Instrumentation->unitFailure(Name, Unit, Failure);
+ UnitAnalyses.setInstrumentation(nullptr);
+ FunctionAnalyses.setInstrumentation(nullptr);
+ return std::unexpected(std::move(Failure));
+ }
+ }
+
+ for (const AnalysisID Required : Contract.RequiredAnalyses) {
+ auto Result = UnitAnalyses.getResultImpl(Required, Unit, Target);
+ if (!Result) {
+ Error Failure =
+ prefixedError(Result.error(), "compilation-unit pass '" + std::string(Name) +
+ "' could not materialize a required analysis: ");
+ if (Instrumentation != nullptr)
+ Instrumentation->unitFailure(Name, Unit, Failure);
+ UnitAnalyses.setInstrumentation(nullptr);
+ FunctionAnalyses.setInstrumentation(nullptr);
+ return std::unexpected(std::move(Failure));
+ }
+ }
+
+ if (Instrumentation != nullptr)
+ Instrumentation->beforeUnit(Name, Unit);
+ const Clock::time_point Start = Clock::now();
+ auto Result = Pass->run(Unit, Target, UnitAnalyses, FunctionAnalyses);
+ const std::uint64_t Duration = elapsedNanoseconds(Start);
+ if (!Result) {
+ Error Failure = prefixedError(Result.error(),
+ "compilation-unit pass '" + std::string(Name) + "' failed: ");
+ if (Instrumentation != nullptr)
+ Instrumentation->unitFailure(Name, Unit, Failure, Duration);
+ UnitAnalyses.clearAll();
+ FunctionAnalyses.clearAll();
+ UnitAnalyses.setInstrumentation(nullptr);
+ FunctionAnalyses.setInstrumentation(nullptr);
+ return std::unexpected(std::move(Failure));
+ }
+ if (Result->Changed != !Result->Changes.empty() ||
+ !Contract.DeclaredChanges.contains(Result->Changes)) {
+ Error Failure(ErrorCode::VerificationError,
+ "compilation-unit pass '" + std::string(Name) +
+ "' reported inconsistent or undeclared modifications");
+ if (Instrumentation != nullptr)
+ Instrumentation->unitFailure(Name, Unit, Failure, Duration);
+ UnitAnalyses.clearAll();
+ FunctionAnalyses.clearAll();
+ UnitAnalyses.setInstrumentation(nullptr);
+ FunctionAnalyses.setInstrumentation(nullptr);
+ return std::unexpected(std::move(Failure));
+ }
+
+ UnitAnalyses.invalidate(Unit, Result->PreservedUnitAnalyses, Result->Changes);
+ for (std::size_t Index = 0; Index < Unit.functionCount(); ++Index) {
+ MachineFunction *Function = Unit.functionAt(Index);
+ if (Function != nullptr) {
+ FunctionAnalyses.invalidate(*Function, Result->PreservedFunctionAnalyses, Result->Changes);
+ }
+ }
+ if (Options.VerifyEach) {
+ auto Verification = verifyCompilationUnit(Unit, Target);
+ if (!Verification) {
+ Error Failure =
+ prefixedError(Verification.error(), "compilation-unit pass '" + std::string(Name) +
+ "' left invalid MachineIR: ");
+ if (Instrumentation != nullptr)
+ Instrumentation->unitFailure(Name, Unit, Failure, Duration);
+ UnitAnalyses.clearAll();
+ FunctionAnalyses.clearAll();
+ UnitAnalyses.setInstrumentation(nullptr);
+ FunctionAnalyses.setInstrumentation(nullptr);
+ return std::unexpected(std::move(Failure));
+ }
+ }
+ if (Instrumentation != nullptr)
+ Instrumentation->afterUnit(Name, Unit, *Result, Duration);
+ }
+ UnitAnalyses.setInstrumentation(nullptr);
+ FunctionAnalyses.setInstrumentation(nullptr);
+ return {};
+}
+
+} // namespace rcc
diff --git a/backend/lib/CodeGen/TargetCallLowering.cpp b/backend/lib/CodeGen/TargetCallLowering.cpp
new file mode 100644
index 0000000..d6a14b7
--- /dev/null
+++ b/backend/lib/CodeGen/TargetCallLowering.cpp
@@ -0,0 +1,353 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/TargetCallLowering.h"
+
+#include "rcc/CodeGen/MachineIRBuilder.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <string>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+namespace rcc {
+namespace {
+
+[[nodiscard]] bool isKnownExtension(ABIExtensionKind Extension) noexcept {
+ return Extension < ABIExtensionKind::Count;
+}
+
+[[nodiscard]] bool isKnownLocationKind(CCLocationKind Kind) noexcept {
+ return Kind <= CCLocationKind::Custom;
+}
+
+[[nodiscard]] bool isKnownTailCallKind(TailCallKind Kind) noexcept {
+ return Kind <= TailCallKind::MustTail;
+}
+
+[[nodiscard]] bool addOverflows(std::uint64_t Left, std::uint64_t Right) noexcept {
+ return Left > std::numeric_limits<std::uint64_t>::max() - Right;
+}
+
+[[nodiscard]] Expected<void> verifyAssignment(const CCAssignment &Assignment,
+ bool AllowNoRegister) {
+ if (Assignment.OriginalValue.isNoRegister() && !AllowNoRegister) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "call-lowering assignment requires a machine register value");
+ }
+ if (!Assignment.ValueType.isValid() || !Assignment.LocationType.isValid() ||
+ !Assignment.ValueType.isScalarValue() || !Assignment.LocationType.isScalarValue()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "call-lowering assignments require scalar value and location LLTs");
+ }
+ if (Assignment.FragmentCount == 0 || Assignment.FragmentIndex >= Assignment.FragmentCount) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "call-lowering assignment has invalid fragment coordinates");
+ }
+ if (Assignment.LocationSizeBytes == 0 || Assignment.StackSlotSizeBytes == 0 ||
+ Assignment.LocationSizeBytes > Assignment.StackSlotSizeBytes ||
+ !isKnownExtension(Assignment.Extension) || !isKnownLocationKind(Assignment.LocationKind)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "call-lowering assignment has invalid size or extension metadata");
+ }
+
+ const bool HasRegister = Assignment.LocationRegister.has_value();
+ const bool HasStack = Assignment.StackOffsetBytes.has_value();
+ if (HasRegister && !Assignment.LocationRegister->isValid()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "call-lowering assignment names an invalid physical register");
+ }
+ if (HasStack && *Assignment.StackOffsetBytes % Assignment.LocationAlignment.bytes() != 0) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "stack assignment offset violates its location alignment");
+ }
+ switch (Assignment.LocationKind) {
+ case CCLocationKind::Register:
+ if (!HasRegister || HasStack) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "register assignment must name exactly one physical register");
+ }
+ break;
+ case CCLocationKind::Stack:
+ if (HasRegister || !HasStack) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "stack assignment must name exactly one stack offset");
+ }
+ break;
+ case CCLocationKind::Indirect:
+ if (HasRegister == HasStack || !Assignment.CustomFlags.has(CCAssignmentFlag::Indirect)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "indirect assignment must have one transport location and its flag");
+ }
+ break;
+ case CCLocationKind::Custom:
+ if (HasRegister && HasStack) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "custom assignment cannot use register and stack simultaneously");
+ }
+ break;
+ }
+ return {};
+}
+
+[[nodiscard]] Expected<void> verifyAssignments(std::span<const CCAssignment> Assignments,
+ bool AllowNoRegister,
+ const StackArgumentArea &StackArea) {
+ std::unordered_set<PhysicalRegister> Registers;
+ std::vector<std::pair<std::uint64_t, std::uint64_t>> StackRanges;
+ StackRanges.reserve(Assignments.size());
+ for (const CCAssignment &Assignment : Assignments) {
+ auto Verification = verifyAssignment(Assignment, AllowNoRegister);
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ if (Assignment.LocationRegister && !Registers.insert(*Assignment.LocationRegister).second) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "call-lowering assignments reuse a physical location register");
+ }
+ if (!Assignment.StackOffsetBytes)
+ continue;
+ if (addOverflows(*Assignment.StackOffsetBytes, Assignment.StackSlotSizeBytes)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "call-lowering stack assignment range overflows uint64");
+ }
+ const std::uint64_t End = *Assignment.StackOffsetBytes + Assignment.StackSlotSizeBytes;
+ if (End > StackArea.SizeBytes) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "call-lowering stack assignment exceeds its argument area");
+ }
+ StackRanges.emplace_back(*Assignment.StackOffsetBytes, End);
+ }
+
+ std::ranges::sort(StackRanges);
+ for (std::size_t Index = 1; Index < StackRanges.size(); ++Index) {
+ if (StackRanges[Index].first < StackRanges[Index - 1].second) {
+ return makeUnexpected(ErrorCode::InvalidArgument, "call-lowering stack assignments overlap");
+ }
+ }
+ return {};
+}
+
+[[nodiscard]] Expected<void> verifyVarArgs(const VarArgLoweringState &State) {
+ if (!State.IsVariadic) {
+ if (State.FixedArgumentCount != 0 || State.FirstUnallocatedIntegerRegister != 0 ||
+ State.OverflowAreaOffsetBytes != 0 || State.RegisterSaveAreaOffsetBytes != 0 ||
+ State.RegisterSaveAreaSizeBytes != 0 || State.RegisterSavePaddingBytes != 0 ||
+ !State.RegistersToSave.empty()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "non-variadic plan contains variadic lowering state");
+ }
+ return {};
+ }
+
+ if (State.RegisterSaveAreaSizeBytes == 0 && !State.RegistersToSave.empty()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "variadic register saves require a nonempty save area");
+ }
+ if (State.RegisterSaveAreaSizeBytes % 8 != 0 || State.RegisterSavePaddingBytes % 8 != 0) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "variadic register-save size and padding must be XLEN aligned");
+ }
+ std::unordered_set<PhysicalRegister> SavedRegisters;
+ std::unordered_set<std::uint64_t> SavedOffsets;
+ for (const VarArgRegisterSave Save : State.RegistersToSave) {
+ if (!Save.Reg.isValid() || !SavedRegisters.insert(Save.Reg).second ||
+ Save.OffsetBytes % 8 != 0 || !SavedOffsets.insert(Save.OffsetBytes).second ||
+ addOverflows(Save.OffsetBytes, std::uint64_t{8}) ||
+ Save.OffsetBytes + 8 > State.RegisterSaveAreaSizeBytes) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "variadic register-save entry is invalid or outside its area");
+ }
+ }
+ return {};
+}
+
+} // namespace
+
+ABIValueFlags::ABIValueFlags(std::initializer_list<ABIValueFlag> Flags) noexcept {
+ for (const ABIValueFlag Flag : Flags)
+ set(Flag);
+}
+
+CCAssignmentFlags::CCAssignmentFlags(std::initializer_list<CCAssignmentFlag> Flags) noexcept {
+ for (const CCAssignmentFlag Flag : Flags)
+ set(Flag);
+}
+
+Expected<MachineCallTarget> MachineCallTarget::globalSymbol(std::string Name,
+ std::uint32_t TargetFlags) {
+ if (Name.empty())
+ return makeUnexpected(ErrorCode::InvalidArgument, "global call target name cannot be empty");
+ return MachineCallTarget(MachineCallTargetKind::GlobalSymbol, std::move(Name), TargetFlags);
+}
+
+Expected<MachineCallTarget> MachineCallTarget::externalSymbol(std::string Name,
+ std::uint32_t TargetFlags) {
+ if (Name.empty())
+ return makeUnexpected(ErrorCode::InvalidArgument, "external call target name cannot be empty");
+ return MachineCallTarget(MachineCallTargetKind::ExternalSymbol, std::move(Name), TargetFlags);
+}
+
+Expected<MachineCallTarget> MachineCallTarget::indirect(Register Callee,
+ std::uint32_t TargetFlags) {
+ if (Callee.isNoRegister()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "indirect call target requires a machine register");
+ }
+ return MachineCallTarget(MachineCallTargetKind::IndirectRegister, Callee, TargetFlags);
+}
+
+std::string_view MachineCallTarget::symbolName() const noexcept {
+ const auto *Name = std::get_if<std::string>(&Value);
+ return Name == nullptr ? std::string_view{} : std::string_view(*Name);
+}
+
+Register MachineCallTarget::indirectRegister() const noexcept {
+ const auto *Reg = std::get_if<Register>(&Value);
+ return Reg == nullptr ? Register{} : *Reg;
+}
+
+Expected<CallLoweringPlan> CallLoweringPlan::create(CallLoweringPlanDesc Description) {
+ if (Description.Target.empty() || Description.Convention.value() == 0) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "call-lowering plan requires target and calling-convention identities");
+ }
+ if (Description.Kind > CallLoweringPlanKind::Return ||
+ !isKnownTailCallKind(Description.TailDecision)) {
+ return makeUnexpected(ErrorCode::InvalidArgument, "call-lowering plan kind is invalid");
+ }
+ if (Description.StackArguments.ShadowSpaceBytes > Description.StackArguments.SizeBytes ||
+ Description.StackArguments.SizeBytes % Description.StackArguments.Align.bytes() != 0) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "call-lowering stack argument area is malformed");
+ }
+ if (Description.CallFrame.SizeBytes % Description.CallFrame.Align.bytes() != 0 ||
+ Description.CallFrame.EmitSetup != Description.CallFrame.EmitDestroy) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "call-lowering call-frame requirements are inconsistent");
+ }
+
+ auto ArgumentVerification =
+ verifyAssignments(Description.ArgumentAssignments, false, Description.StackArguments);
+ if (!ArgumentVerification)
+ return std::unexpected(ArgumentVerification.error());
+ const bool AllowNoReturnRegister = Description.Kind == CallLoweringPlanKind::Call &&
+ Description.TailDecision != TailCallKind::NotTail;
+ auto ReturnVerification = verifyAssignments(Description.ReturnAssignments, AllowNoReturnRegister,
+ StackArgumentArea{0, 0, Alignment{}});
+ if (!ReturnVerification)
+ return std::unexpected(ReturnVerification.error());
+ auto VarArgVerification = verifyVarArgs(Description.VarArgs);
+ if (!VarArgVerification)
+ return std::unexpected(VarArgVerification.error());
+
+ switch (Description.Kind) {
+ case CallLoweringPlanKind::FormalArguments:
+ if (!Description.ReturnAssignments.empty() || Description.Callee || Description.PreservedMask ||
+ Description.CallSiteID != 0 || Description.TailDecision != TailCallKind::NotTail ||
+ Description.CallFrame.EmitSetup || Description.CallFrame.ReusesIncomingArea ||
+ !Description.ByValCopies.empty() || !Description.TailRejectionReason.empty()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "formal-argument plan contains call or return state");
+ }
+ break;
+ case CallLoweringPlanKind::Return:
+ if (!Description.ArgumentAssignments.empty() || Description.Callee ||
+ Description.PreservedMask || Description.CallSiteID != 0 ||
+ Description.TailDecision != TailCallKind::NotTail || Description.CallFrame.EmitSetup ||
+ Description.CallFrame.ReusesIncomingArea || !Description.ByValCopies.empty() ||
+ Description.VarArgs.IsVariadic || !Description.TailRejectionReason.empty()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "return plan contains formal-argument or call state");
+ }
+ break;
+ case CallLoweringPlanKind::Call:
+ if (!Description.Callee || !Description.Callee->isValid() || !Description.PreservedMask ||
+ Description.CallSiteID == 0) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "call plan requires a callee, call-site ID, and preserved mask");
+ }
+ if (Description.TailDecision == TailCallKind::NotTail) {
+ if (!Description.CallFrame.EmitSetup || Description.CallFrame.ReusesIncomingArea) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "ordinary call plan requires a private call frame");
+ }
+ } else if (Description.CallFrame.EmitSetup || !Description.CallFrame.ReusesIncomingArea ||
+ !Description.TailRejectionReason.empty()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "tail-call plan must reuse the caller's incoming argument area");
+ }
+ if (Description.CallFrame.SizeBytes < Description.StackArguments.SizeBytes) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "call frame does not contain the stack-argument area");
+ }
+ break;
+ }
+
+ std::unordered_set<std::size_t> ByValAssignments;
+ std::vector<std::pair<std::uint64_t, std::uint64_t>> ByValRanges;
+ for (const ByValCopyAssignment &Copy : Description.ByValCopies) {
+ if (Copy.ArgumentAssignmentIndex >= Description.ArgumentAssignments.size() ||
+ !ByValAssignments.insert(Copy.ArgumentAssignmentIndex).second ||
+ Copy.Source.isNoRegister() || !Copy.PointerType.isPointer() || Copy.SizeBytes == 0 ||
+ Copy.StackOffsetBytes % Copy.Align.bytes() != 0 ||
+ Copy.StackOffsetBytes < Description.StackArguments.SizeBytes ||
+ !Description.ArgumentAssignments[Copy.ArgumentAssignmentIndex].CustomFlags.has(
+ CCAssignmentFlag::ByVal) ||
+ addOverflows(Copy.StackOffsetBytes, Copy.SizeBytes) ||
+ Copy.StackOffsetBytes + Copy.SizeBytes > Description.CallFrame.SizeBytes) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "call-lowering byval copy is malformed or outside the call frame");
+ }
+ ByValRanges.emplace_back(Copy.StackOffsetBytes, Copy.StackOffsetBytes + Copy.SizeBytes);
+ }
+ std::ranges::sort(ByValRanges);
+ for (std::size_t Index = 1; Index < ByValRanges.size(); ++Index) {
+ if (ByValRanges[Index].first < ByValRanges[Index - 1].second) {
+ return makeUnexpected(ErrorCode::InvalidArgument, "call-lowering byval copy ranges overlap");
+ }
+ }
+
+ return CallLoweringPlan(std::move(Description));
+}
+
+Expected<void>
+TargetCallLowering::lowerFormalArguments(MachineIRBuilder &Builder,
+ const FormalArgumentRequest &Request) const {
+ auto Plan = planFormalArguments(Request);
+ if (!Plan)
+ return std::unexpected(Plan.error());
+ if (Plan->kind() != CallLoweringPlanKind::FormalArguments) {
+ return makeUnexpected(ErrorCode::InternalError,
+ "target returned the wrong call-lowering plan kind for formal arguments");
+ }
+ return emitPlan(Builder, *Plan);
+}
+
+Expected<void> TargetCallLowering::lowerCall(MachineIRBuilder &Builder,
+ const CallLoweringRequest &Request) const {
+ auto Plan = planCall(Request);
+ if (!Plan)
+ return std::unexpected(Plan.error());
+ if (Plan->kind() != CallLoweringPlanKind::Call) {
+ return makeUnexpected(ErrorCode::InternalError,
+ "target returned the wrong call-lowering plan kind for a call");
+ }
+ return emitPlan(Builder, *Plan);
+}
+
+Expected<void> TargetCallLowering::lowerReturn(MachineIRBuilder &Builder,
+ const ReturnLoweringRequest &Request) const {
+ auto Plan = planReturn(Request);
+ if (!Plan)
+ return std::unexpected(Plan.error());
+ if (Plan->kind() != CallLoweringPlanKind::Return) {
+ return makeUnexpected(ErrorCode::InternalError,
+ "target returned the wrong call-lowering plan kind for a return");
+ }
+ return emitPlan(Builder, *Plan);
+}
+
+} // namespace rcc
diff --git a/backend/lib/Target/RISCV/CMakeLists.txt b/backend/lib/Target/RISCV/CMakeLists.txt
index a77494a..ff3f334 100644
--- a/backend/lib/Target/RISCV/CMakeLists.txt
+++ b/backend/lib/Target/RISCV/CMakeLists.txt
@@ -47,6 +47,7 @@ set_source_files_properties(
add_library(
RCCBackendRISCV
+ RISCVCallLowering.cpp
RISCVTarget.cpp
"${RCCBackendRISCVGeneratedRegisterHeader}"
"${RCCBackendRISCVGeneratedInstructionHeader}"
@@ -89,9 +90,12 @@ install(
)
rcc_backend_register_format_files(
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVCallLowering.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVTarget.cpp"
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVCallLowering.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVTarget.h"
)
rcc_backend_register_tidy_files(
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVCallLowering.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVTarget.cpp"
)
diff --git a/backend/lib/Target/RISCV/RISCVCallLowering.cpp b/backend/lib/Target/RISCV/RISCVCallLowering.cpp
new file mode 100644
index 0000000..13cd348
--- /dev/null
+++ b/backend/lib/Target/RISCV/RISCVCallLowering.cpp
@@ -0,0 +1,1473 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/Target/RISCV/RISCVCallLowering.h"
+
+#include "rcc/CodeGen/MachineBasicBlock.h"
+#include "rcc/CodeGen/MachineFrameInfo.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineIRBuilder.h"
+#include "rcc/CodeGen/MachineInstr.h"
+#include "rcc/CodeGen/MachineMemOperand.h"
+#include "rcc/CodeGen/MachineRegisterInfo.h"
+#include "rcc/CodeGen/TargetDescription.h"
+#include "rcc/Target/RISCV/RISCVTarget.h"
+
+#include <algorithm>
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <optional>
+#include <span>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+namespace rcc::riscv {
+namespace {
+
+constexpr std::string_view PlanTarget = "riscv64-unknown-linux-gnu:lp64d";
+constexpr std::uint64_t XLenBytes = 8;
+constexpr std::uint64_t LP64DStackAlignmentBytes = 16;
+constexpr std::uint32_t AllowedDirectCallFlags =
+ static_cast<std::uint32_t>(CallTargetFlag::DirectCall);
+
+[[nodiscard]] Opcode targetOpcode(InstructionOpcode Value) noexcept {
+ return Opcode::fromValue(static_cast<std::uint32_t>(Value));
+}
+
+[[nodiscard]] std::uint64_t stackObjectFlag(CallStackObjectFlag Flag) noexcept {
+ return static_cast<std::uint64_t>(Flag);
+}
+
+[[nodiscard]] Expected<Alignment> alignment(std::uint64_t Bytes) {
+ return Alignment::create(Bytes);
+}
+
+[[nodiscard]] Expected<std::uint64_t> alignTo(std::uint64_t Value, std::uint64_t AlignmentBytes) {
+ const std::uint64_t Remainder = Value % AlignmentBytes;
+ if (Remainder == 0)
+ return Value;
+ const std::uint64_t Increment = AlignmentBytes - Remainder;
+ if (Value > std::numeric_limits<std::uint64_t>::max() - Increment) {
+ return makeUnexpected(ErrorCode::Unsupported, "LP64D stack-area size overflows uint64");
+ }
+ return Value + Increment;
+}
+
+[[nodiscard]] std::uint64_t valueSizeBytes(LLT Type) noexcept {
+ return (static_cast<std::uint64_t>(Type.scalarBitWidth()) + 7U) / 8U;
+}
+
+[[nodiscard]] bool isSupportedFloat(LLT Type) noexcept {
+ if (!Type.isFloat())
+ return false;
+ const auto Semantics = Type.floatSemantics();
+ return Semantics &&
+ (*Semantics == FloatSemantics::IEEESingle || *Semantics == FloatSemantics::IEEEDouble);
+}
+
+[[nodiscard]] bool requiresIntegerTransport(const ABIValue &Value) noexcept {
+ return !isSupportedFloat(Value.Type) || Value.Flags.has(ABIValueFlag::ByVal) ||
+ Value.Flags.has(ABIValueFlag::StructReturn) || Value.Flags.has(ABIValueFlag::Indirect);
+}
+
+[[nodiscard]] Expected<void> verifyABIValue(const ABIValue &Value, bool RequireRegister) {
+ if (RequireRegister && Value.Value.isNoRegister()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "LP64D ABI value requires a source or destination register");
+ }
+ if (!Value.Type.isValid() || !Value.Type.isScalarValue() || Value.Type.isAnyScalar() ||
+ Value.Type.scalarBitWidth() > 64) {
+ return makeUnexpected(
+ ErrorCode::Unsupported,
+ "LP64D accepts explicitly classified integer, pointer, f32, and f64 scalar fragments");
+ }
+ if (Value.Type.isFloat() && !isSupportedFloat(Value.Type)) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RV64GC LP64D only supports f32 and f64 ABI fragments");
+ }
+ if (Value.Type.isPointer() && Value.Type.scalarBitWidth() != 64) {
+ return makeUnexpected(ErrorCode::Unsupported, "RV64GC LP64D requires 64-bit machine pointers");
+ }
+ if (Value.Type.isInteger() && Value.Type.scalarBitWidth() < 64) {
+ if (Value.Extension == ABIExtensionKind::None) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "narrow LP64D integer fragments require an explicit extension kind");
+ }
+ } else if (Value.Extension != ABIExtensionKind::None) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "only narrow integer ABI fragments may carry an extension kind");
+ }
+ if (Value.Extension >= ABIExtensionKind::Count || Value.FragmentCount == 0 ||
+ Value.FragmentIndex >= Value.FragmentCount || Value.FragmentCount > 2) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "LP64D ABI fragment coordinates or extension are invalid");
+ }
+
+ const bool IsByVal = Value.Flags.has(ABIValueFlag::ByVal);
+ const bool IsSRet = Value.Flags.has(ABIValueFlag::StructReturn);
+ const bool IsIndirect = Value.Flags.has(ABIValueFlag::Indirect);
+ const unsigned SpecialFlagCount = static_cast<unsigned>(IsByVal) + static_cast<unsigned>(IsSRet) +
+ static_cast<unsigned>(IsIndirect);
+ if (SpecialFlagCount > 1) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "byval, sret, and indirect ABI flags are mutually exclusive");
+ }
+ if ((IsByVal || IsSRet || IsIndirect) &&
+ (!Value.Type.isPointer() || Value.FragmentCount != 1 || Value.FragmentIndex != 0 ||
+ Value.FragmentOffsetBytes != 0)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "byval, sret, and indirect values require one explicit pointer fragment");
+ }
+ if (IsByVal) {
+ if (Value.ByValSizeBytes == 0) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "byval arguments require a nonzero copy size");
+ }
+ } else if (Value.ByValSizeBytes != 0) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "non-byval values cannot carry a byval copy size");
+ }
+ return {};
+}
+
+[[nodiscard]] Expected<std::size_t> verifyValueSequence(std::span<const ABIValue> Values,
+ bool RequireRegisters) {
+ std::size_t ExpectedOriginal = 0;
+ std::size_t Index = 0;
+ while (Index < Values.size()) {
+ const ABIValue &First = Values[Index];
+ auto ValueVerification = verifyABIValue(First, RequireRegisters);
+ if (!ValueVerification)
+ return std::unexpected(ValueVerification.error());
+ if (First.OriginalValue != ExpectedOriginal || First.FragmentIndex != 0 ||
+ Index + First.FragmentCount > Values.size()) {
+ return makeUnexpected(
+ ErrorCode::InvalidArgument,
+ "LP64D ABI values must form contiguous, zero-based original-value groups");
+ }
+
+ std::uint64_t PreviousEnd = 0;
+ for (std::size_t Fragment = 0; Fragment < First.FragmentCount; ++Fragment) {
+ const ABIValue &Value = Values[Index + Fragment];
+ auto FragmentVerification = verifyABIValue(Value, RequireRegisters);
+ if (!FragmentVerification)
+ return std::unexpected(FragmentVerification.error());
+ if (Value.OriginalValue != First.OriginalValue || Value.FragmentIndex != Fragment ||
+ Value.FragmentCount != First.FragmentCount ||
+ Value.OriginalAlignment != First.OriginalAlignment || Value.Flags != First.Flags ||
+ Value.ByValSizeBytes != First.ByValSizeBytes ||
+ Value.ByValAlignment != First.ByValAlignment || Value.FragmentOffsetBytes < PreviousEnd) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "LP64D fragments of one value have inconsistent metadata");
+ }
+ PreviousEnd =
+ static_cast<std::uint64_t>(Value.FragmentOffsetBytes) + valueSizeBytes(Value.Type);
+ }
+ Index += First.FragmentCount;
+ ++ExpectedOriginal;
+ }
+ return ExpectedOriginal;
+}
+
+[[nodiscard]] CCAssignmentFlags assignmentFlags(const ABIValue &Value, bool IsVariadic,
+ bool IsBitcast) {
+ CCAssignmentFlags Result;
+ if (IsVariadic)
+ Result.set(CCAssignmentFlag::Variadic);
+ if (Value.Flags.has(ABIValueFlag::ByVal))
+ Result.set(CCAssignmentFlag::ByVal);
+ if (Value.Flags.has(ABIValueFlag::StructReturn))
+ Result.set(CCAssignmentFlag::StructReturn);
+ if (Value.Flags.has(ABIValueFlag::Indirect))
+ Result.set(CCAssignmentFlag::Indirect);
+ if (Value.Flags.has(ABIValueFlag::InRegister))
+ Result.set(CCAssignmentFlag::InRegister);
+ if (IsBitcast)
+ Result.set(CCAssignmentFlag::Bitcast);
+ return Result;
+}
+
+[[nodiscard]] CCLocationKind locationKind(const ABIValue &Value, bool IsRegister) noexcept {
+ if (Value.Flags.has(ABIValueFlag::Indirect))
+ return CCLocationKind::Indirect;
+ return IsRegister ? CCLocationKind::Register : CCLocationKind::Stack;
+}
+
+struct ArgumentAssignmentResult final {
+ std::vector<CCAssignment> Assignments;
+ StackArgumentArea StackArea;
+ std::size_t FirstUnallocatedGPR = 0;
+};
+
+struct AssignmentState final {
+ std::size_t GPR = 0;
+ std::size_t FPR = 0;
+ std::uint64_t StackOffset = 0;
+ bool VariadicStackOnly = false;
+};
+
+[[nodiscard]] Expected<CCAssignment> registerAssignment(const ABIValue &Value,
+ std::size_t ValueIndex,
+ PhysicalRegister Location, LLT LocationType,
+ bool IsVariadic, bool IsBitcast) {
+ auto Align =
+ alignment(std::min<std::uint64_t>(Value.OriginalAlignment.bytes(), LP64DStackAlignmentBytes));
+ if (!Align)
+ return std::unexpected(Align.error());
+ return CCAssignment{
+ .ValueIndex = ValueIndex,
+ .OriginalValue = Value.Value,
+ .OriginalValueIndex = Value.OriginalValue,
+ .FragmentIndex = Value.FragmentIndex,
+ .FragmentCount = Value.FragmentCount,
+ .ValueType = Value.Type,
+ .LocationType = LocationType,
+ .LocationKind = locationKind(Value, true),
+ .LocationRegister = Location,
+ .StackOffsetBytes = std::nullopt,
+ .LocationSizeBytes = valueSizeBytes(LocationType),
+ .StackSlotSizeBytes = XLenBytes,
+ .LocationAlignment = *Align,
+ .Extension = Value.Extension,
+ .CustomFlags = assignmentFlags(Value, IsVariadic, IsBitcast),
+ };
+}
+
+[[nodiscard]] Expected<CCAssignment> stackAssignment(const ABIValue &Value, std::size_t ValueIndex,
+ std::uint64_t StackOffset, LLT LocationType,
+ bool IsVariadic,
+ std::uint64_t StackAlignment) {
+ auto Align = alignment(StackAlignment);
+ if (!Align)
+ return std::unexpected(Align.error());
+ return CCAssignment{
+ .ValueIndex = ValueIndex,
+ .OriginalValue = Value.Value,
+ .OriginalValueIndex = Value.OriginalValue,
+ .FragmentIndex = Value.FragmentIndex,
+ .FragmentCount = Value.FragmentCount,
+ .ValueType = Value.Type,
+ .LocationType = LocationType,
+ .LocationKind = locationKind(Value, false),
+ .LocationRegister = std::nullopt,
+ .StackOffsetBytes = StackOffset,
+ .LocationSizeBytes = valueSizeBytes(LocationType),
+ .StackSlotSizeBytes = XLenBytes,
+ .LocationAlignment = *Align,
+ .Extension = Value.Extension,
+ .CustomFlags = assignmentFlags(Value, IsVariadic, false),
+ };
+}
+
+[[nodiscard]] Expected<ArgumentAssignmentResult>
+assignArguments(std::span<const ABIValue> Values, std::size_t FixedArgumentCount, bool IsVariadic,
+ const CallingConventionInfo &Convention) {
+ auto I64 = LLT::makeInteger(64);
+ auto StackAlign = alignment(Convention.StackArgumentAlignmentBytes);
+ if (!I64 || !StackAlign)
+ return makeUnexpected(ErrorCode::InternalError, "LP64D primitive type facts are invalid");
+
+ ArgumentAssignmentResult Result;
+ AssignmentState State;
+ State.StackOffset = Convention.ShadowSpaceBytes;
+ Result.Assignments.reserve(Values.size());
+
+ std::size_t Index = 0;
+ while (Index < Values.size()) {
+ const ABIValue &First = Values[Index];
+ const std::size_t GroupSize = First.FragmentCount;
+ const bool VariadicArgument = IsVariadic && First.OriginalValue >= FixedArgumentCount;
+ bool HasPreferredFloat = false;
+ std::size_t RequiredGPRs = 0;
+ std::size_t RequiredFPRs = 0;
+ for (std::size_t Fragment = 0; Fragment < GroupSize; ++Fragment) {
+ const ABIValue &Value = Values[Index + Fragment];
+ if (!VariadicArgument && !requiresIntegerTransport(Value)) {
+ HasPreferredFloat = true;
+ ++RequiredFPRs;
+ } else {
+ ++RequiredGPRs;
+ }
+ }
+
+ const bool CanUsePreferredAggregate =
+ HasPreferredFloat &&
+ State.GPR + RequiredGPRs <= Convention.IntegerArgumentRegisters.size() &&
+ State.FPR + RequiredFPRs <= Convention.FloatingArgumentRegisters.size();
+ if (CanUsePreferredAggregate) {
+ for (std::size_t Fragment = 0; Fragment < GroupSize; ++Fragment) {
+ const ABIValue &Value = Values[Index + Fragment];
+ Expected<CCAssignment> Assignment =
+ requiresIntegerTransport(Value)
+ ? registerAssignment(Value, Index + Fragment,
+ Convention.IntegerArgumentRegisters[State.GPR++], *I64, false,
+ false)
+ : registerAssignment(Value, Index + Fragment,
+ Convention.FloatingArgumentRegisters[State.FPR++], Value.Type,
+ false, false);
+ if (!Assignment)
+ return std::unexpected(Assignment.error());
+ Result.Assignments.push_back(*Assignment);
+ }
+ Index += GroupSize;
+ continue;
+ }
+
+ const bool RequiresAlignedPair =
+ VariadicArgument && GroupSize == 2 && First.OriginalAlignment.bytes() >= 16;
+ if (RequiresAlignedPair && State.GPR % 2 != 0 &&
+ State.GPR < Convention.IntegerArgumentRegisters.size()) {
+ ++State.GPR;
+ }
+ bool ForceEntireGroupToStack = State.VariadicStackOnly;
+ if (RequiresAlignedPair && State.GPR + GroupSize > Convention.IntegerArgumentRegisters.size()) {
+ ForceEntireGroupToStack = true;
+ State.GPR = Convention.IntegerArgumentRegisters.size();
+ }
+
+ bool FirstStackFragment = true;
+ for (std::size_t Fragment = 0; Fragment < GroupSize; ++Fragment) {
+ const ABIValue &Value = Values[Index + Fragment];
+ const bool HasGPR =
+ !ForceEntireGroupToStack && State.GPR < Convention.IntegerArgumentRegisters.size();
+ if (HasGPR) {
+ const bool Bitcast = isSupportedFloat(Value.Type);
+ auto Assignment = registerAssignment(Value, Index + Fragment,
+ Convention.IntegerArgumentRegisters[State.GPR++], *I64,
+ VariadicArgument, Bitcast);
+ if (!Assignment)
+ return std::unexpected(Assignment.error());
+ Result.Assignments.push_back(*Assignment);
+ continue;
+ }
+
+ if (Value.Flags.has(ABIValueFlag::InRegister)) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "LP64D inreg argument cannot be assigned to the stack");
+ }
+ std::uint64_t RequiredAlignment = XLenBytes;
+ if (FirstStackFragment && ForceEntireGroupToStack) {
+ RequiredAlignment = std::min<std::uint64_t>(
+ std::max<std::uint64_t>(XLenBytes, First.OriginalAlignment.bytes()),
+ LP64DStackAlignmentBytes);
+ }
+ auto AlignedOffset = alignTo(State.StackOffset, RequiredAlignment);
+ if (!AlignedOffset)
+ return std::unexpected(AlignedOffset.error());
+ State.StackOffset = *AlignedOffset;
+ const LLT LocationType = isSupportedFloat(Value.Type) ? Value.Type : *I64;
+ auto Assignment = stackAssignment(Value, Index + Fragment, State.StackOffset, LocationType,
+ VariadicArgument, RequiredAlignment);
+ if (!Assignment)
+ return std::unexpected(Assignment.error());
+ Result.Assignments.push_back(*Assignment);
+ if (State.StackOffset > std::numeric_limits<std::uint64_t>::max() - XLenBytes) {
+ return makeUnexpected(ErrorCode::Unsupported, "LP64D argument area overflows uint64");
+ }
+ State.StackOffset += XLenBytes;
+ FirstStackFragment = false;
+ if (VariadicArgument)
+ State.VariadicStackOnly = true;
+ }
+ Index += GroupSize;
+ }
+
+ auto FinalSize = alignTo(State.StackOffset, Convention.StackArgumentAlignmentBytes);
+ if (!FinalSize)
+ return std::unexpected(FinalSize.error());
+ Result.StackArea = StackArgumentArea{*FinalSize, Convention.ShadowSpaceBytes, *StackAlign};
+ Result.FirstUnallocatedGPR = State.GPR;
+ return Result;
+}
+
+[[nodiscard]] Expected<std::vector<CCAssignment>>
+assignReturns(std::span<const ABIValue> Values, bool RequireRegisters,
+ const CallingConventionInfo &Convention) {
+ auto Sequence = verifyValueSequence(Values, RequireRegisters);
+ if (!Sequence)
+ return std::unexpected(Sequence.error());
+ if (Values.size() > 2) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "LP64D direct returns are limited to two explicit fragments");
+ }
+ auto I64 = LLT::makeInteger(64);
+ if (!I64)
+ return std::unexpected(I64.error());
+
+ std::vector<CCAssignment> Result;
+ Result.reserve(Values.size());
+ std::size_t GPR = 0;
+ std::size_t FPR = 0;
+ for (std::size_t Index = 0; Index < Values.size(); ++Index) {
+ const ABIValue &Value = Values[Index];
+ if (Value.Flags.rawValue() != 0) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "return fragments cannot carry argument-only ABI flags");
+ }
+ Expected<CCAssignment> Assignment =
+ isSupportedFloat(Value.Type)
+ ? registerAssignment(Value, Index, Convention.FloatingReturnRegisters[FPR++],
+ Value.Type, false, false)
+ : registerAssignment(Value, Index, Convention.IntegerReturnRegisters[GPR++], *I64,
+ false, false);
+ if (!Assignment)
+ return std::unexpected(Assignment.error());
+ Result.push_back(*Assignment);
+ }
+ return Result;
+}
+
+[[nodiscard]] bool hasStructReturn(std::span<const ABIValue> Arguments) noexcept {
+ return std::ranges::any_of(
+ Arguments, [](const ABIValue &Value) { return Value.Flags.has(ABIValueFlag::StructReturn); });
+}
+
+[[nodiscard]] bool equivalentReturnLocations(std::span<const CCAssignment> Left,
+ std::span<const CCAssignment> Right) noexcept {
+ if (Left.size() != Right.size())
+ return false;
+ for (std::size_t Index = 0; Index < Left.size(); ++Index) {
+ if (Left[Index].ValueType != Right[Index].ValueType ||
+ Left[Index].LocationType != Right[Index].LocationType ||
+ Left[Index].LocationKind != Right[Index].LocationKind ||
+ Left[Index].LocationRegister != Right[Index].LocationRegister ||
+ Left[Index].Extension != Right[Index].Extension ||
+ Left[Index].CustomFlags != Right[Index].CustomFlags) {
+ return false;
+ }
+ }
+ return true;
+}
+
+[[nodiscard]] Expected<std::string>
+tailCallRejection(const CallLoweringRequest &Request, const ArgumentAssignmentResult &Arguments,
+ std::span<const CCAssignment> Results,
+ std::span<const ByValCopyAssignment> ByValCopies,
+ const CallingConventionInfo &Convention) {
+ if (!Convention.SupportsTailCalls)
+ return std::string("the calling convention does not support tail calls");
+ if (!Request.TailContext)
+ return std::string("tail-call context is missing");
+ const TailCallContext &Context = *Request.TailContext;
+ if (Context.CallerConvention != Request.Convention)
+ return std::string("caller and callee calling conventions differ");
+ if (Context.CallerIsVariadic != Request.IsVariadic ||
+ (Request.IsVariadic && Context.CallerFixedArgumentCount != Request.FixedArgumentCount)) {
+ return std::string("caller and callee variadic signatures differ");
+ }
+ if (Context.CallerHasDynamicStackAllocation)
+ return std::string("the caller has a dynamic stack allocation");
+ if (!ByValCopies.empty())
+ return std::string("tail calls cannot create byval copies");
+ if (Context.CallerUsesStructReturn != hasStructReturn(Request.Arguments))
+ return std::string("caller and callee sret conventions differ");
+ if (Arguments.StackArea.SizeBytes > Context.ReusableIncomingArgumentBytes)
+ return std::string("outgoing stack arguments exceed the reusable incoming area");
+ if (std::ranges::any_of(Request.Results,
+ [](const ABIValue &Value) { return !Value.Value.isNoRegister(); })) {
+ return std::string("tail-call result signatures must not request local result copies");
+ }
+ auto CallerReturns = assignReturns(Context.CallerReturns, false, Convention);
+ if (!CallerReturns)
+ return std::unexpected(CallerReturns.error());
+ if (!equivalentReturnLocations(Results, *CallerReturns))
+ return std::string("caller and callee return locations differ");
+ return std::string{};
+}
+
+[[nodiscard]] Expected<const CallingConventionInfo *>
+lp64dConvention(CallingConventionID Convention) {
+ if (Convention != callingConventionID(CallingConvention::LP64D)) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V backend only supports the LP64D calling convention");
+ }
+ const CallingConventionInfo *Info = callingConvention(CallingConvention::LP64D);
+ if (Info == nullptr)
+ return makeUnexpected(ErrorCode::InternalError, "LP64D target facts are unavailable");
+ return Info;
+}
+
+class RISCVCallLowering final : public TargetCallLowering {
+public:
+ [[nodiscard]] Expected<CallLoweringPlan>
+ planFormalArguments(const FormalArgumentRequest &Request) const override;
+ [[nodiscard]] Expected<CallLoweringPlan>
+ planCall(const CallLoweringRequest &Request) const override;
+ [[nodiscard]] Expected<CallLoweringPlan>
+ planReturn(const ReturnLoweringRequest &Request) const override;
+ [[nodiscard]] Expected<void> emitPlan(MachineIRBuilder &Builder,
+ const CallLoweringPlan &Plan) const override;
+};
+
+Expected<CallLoweringPlan>
+RISCVCallLowering::planFormalArguments(const FormalArgumentRequest &Request) const {
+ auto Convention = lp64dConvention(Request.Convention);
+ if (!Convention)
+ return std::unexpected(Convention.error());
+ auto OriginalCount = verifyValueSequence(Request.Arguments, true);
+ if (!OriginalCount)
+ return std::unexpected(OriginalCount.error());
+ if (Request.FixedArgumentCount != *OriginalCount) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "formal-argument request must identify every declared argument as fixed");
+ }
+ if (!Request.IsVariadic && Request.FixedArgumentCount != *OriginalCount) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "non-variadic formal request has an invalid fixed-argument boundary");
+ }
+ if (hasStructReturn(Request.Arguments) &&
+ (Request.Arguments.empty() || Request.Arguments.front().OriginalValue != 0 ||
+ !Request.Arguments.front().Flags.has(ABIValueFlag::StructReturn))) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "LP64D sret must be the first formal argument");
+ }
+
+ auto Assignments = assignArguments(Request.Arguments, Request.FixedArgumentCount,
+ Request.IsVariadic, **Convention);
+ if (!Assignments)
+ return std::unexpected(Assignments.error());
+
+ VarArgLoweringState VarArgs;
+ if (Request.IsVariadic) {
+ VarArgs.IsVariadic = true;
+ VarArgs.FixedArgumentCount = Request.FixedArgumentCount;
+ VarArgs.FirstUnallocatedIntegerRegister = Assignments->FirstUnallocatedGPR;
+ VarArgs.OverflowAreaOffsetBytes = Assignments->StackArea.SizeBytes;
+ const std::size_t First = Assignments->FirstUnallocatedGPR;
+ const std::size_t RegisterCount = (*Convention)->IntegerArgumentRegisters.size();
+ VarArgs.RegisterSaveAreaSizeBytes = (RegisterCount - First) * XLenBytes;
+ VarArgs.RegisterSaveAreaOffsetBytes =
+ VarArgs.RegisterSaveAreaSizeBytes == 0
+ ? static_cast<std::int64_t>(VarArgs.OverflowAreaOffsetBytes)
+ : -static_cast<std::int64_t>(VarArgs.RegisterSaveAreaSizeBytes);
+ if (VarArgs.RegisterSaveAreaSizeBytes != 0 && First % 2 != 0)
+ VarArgs.RegisterSavePaddingBytes = XLenBytes;
+ for (std::size_t Index = First; Index < RegisterCount; ++Index) {
+ VarArgs.RegistersToSave.push_back(
+ {(*Convention)->IntegerArgumentRegisters[Index], (Index - First) * XLenBytes});
+ }
+ }
+
+ CallLoweringPlanDesc Plan;
+ Plan.Kind = CallLoweringPlanKind::FormalArguments;
+ Plan.Target = PlanTarget;
+ Plan.Convention = Request.Convention;
+ Plan.ArgumentAssignments = std::move(Assignments->Assignments);
+ Plan.StackArguments = Assignments->StackArea;
+ Plan.VarArgs = std::move(VarArgs);
+ return CallLoweringPlan::create(std::move(Plan));
+}
+
+Expected<CallLoweringPlan> RISCVCallLowering::planCall(const CallLoweringRequest &Request) const {
+ auto Convention = lp64dConvention(Request.Convention);
+ if (!Convention)
+ return std::unexpected(Convention.error());
+ auto OriginalCount = verifyValueSequence(Request.Arguments, true);
+ if (!OriginalCount)
+ return std::unexpected(OriginalCount.error());
+ if ((!Request.IsVariadic && Request.FixedArgumentCount != *OriginalCount) ||
+ (Request.IsVariadic && Request.FixedArgumentCount > *OriginalCount)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "call request has an invalid fixed/variadic argument boundary");
+ }
+ if (Request.CallSiteID == 0 || !Request.Callee.isValid()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "call request requires a valid callee and nonzero call-site ID");
+ }
+ if (hasStructReturn(Request.Arguments) &&
+ (Request.Arguments.empty() ||
+ !Request.Arguments.front().Flags.has(ABIValueFlag::StructReturn))) {
+ return makeUnexpected(ErrorCode::InvalidArgument, "LP64D sret must be the first call argument");
+ }
+ if (hasStructReturn(Request.Arguments) && !Request.Results.empty()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "LP64D sret calls cannot also request direct return values");
+ }
+
+ MachineCallTarget EffectiveCallee = Request.Callee;
+ switch (Request.Callee.kind()) {
+ case MachineCallTargetKind::GlobalSymbol: {
+ if ((Request.Callee.targetFlags() & ~AllowedDirectCallFlags) != 0) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "direct RISC-V call uses unknown target flags");
+ }
+ auto Callee = MachineCallTarget::globalSymbol(
+ std::string(Request.Callee.symbolName()),
+ Request.Callee.targetFlags() | static_cast<std::uint32_t>(CallTargetFlag::DirectCall));
+ if (!Callee)
+ return std::unexpected(Callee.error());
+ EffectiveCallee = std::move(*Callee);
+ break;
+ }
+ case MachineCallTargetKind::ExternalSymbol: {
+ if ((Request.Callee.targetFlags() & ~AllowedDirectCallFlags) != 0) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "direct RISC-V call uses unknown target flags");
+ }
+ auto Callee = MachineCallTarget::externalSymbol(
+ std::string(Request.Callee.symbolName()),
+ Request.Callee.targetFlags() | static_cast<std::uint32_t>(CallTargetFlag::DirectCall));
+ if (!Callee)
+ return std::unexpected(Callee.error());
+ EffectiveCallee = std::move(*Callee);
+ break;
+ }
+ case MachineCallTargetKind::IndirectRegister:
+ if (Request.Callee.targetFlags() != 0) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "indirect RISC-V calls cannot carry direct-call target flags");
+ }
+ break;
+ case MachineCallTargetKind::Invalid:
+ return makeUnexpected(ErrorCode::InvalidArgument, "call target is invalid");
+ }
+
+ const bool RequireResultRegisters = Request.TailKind == TailCallKind::NotTail;
+ auto ResultSequence = verifyValueSequence(Request.Results, RequireResultRegisters);
+ if (!ResultSequence)
+ return std::unexpected(ResultSequence.error());
+ auto Arguments = assignArguments(Request.Arguments, Request.FixedArgumentCount,
+ Request.IsVariadic, **Convention);
+ if (!Arguments)
+ return std::unexpected(Arguments.error());
+ auto Results = assignReturns(Request.Results, RequireResultRegisters, **Convention);
+ if (!Results)
+ return std::unexpected(Results.error());
+
+ auto Align16 = alignment(LP64DStackAlignmentBytes);
+ if (!Align16)
+ return std::unexpected(Align16.error());
+ std::uint64_t CallFrameCursor = Arguments->StackArea.SizeBytes;
+ std::vector<ByValCopyAssignment> ByValCopies;
+ for (std::size_t Index = 0; Index < Arguments->Assignments.size(); ++Index) {
+ const CCAssignment &Assignment = Arguments->Assignments[Index];
+ if (!Assignment.CustomFlags.has(CCAssignmentFlag::ByVal))
+ continue;
+ const ABIValue &Value = Request.Arguments[Assignment.ValueIndex];
+ const std::uint64_t CopyAlignment =
+ std::min<std::uint64_t>(Value.ByValAlignment.bytes(), LP64DStackAlignmentBytes);
+ auto CopyOffset = alignTo(CallFrameCursor, CopyAlignment);
+ if (!CopyOffset)
+ return std::unexpected(CopyOffset.error());
+ if (*CopyOffset > std::numeric_limits<std::uint64_t>::max() - Value.ByValSizeBytes) {
+ return makeUnexpected(ErrorCode::Unsupported, "LP64D byval area overflows uint64");
+ }
+ auto CopyAlign = alignment(CopyAlignment);
+ if (!CopyAlign)
+ return std::unexpected(CopyAlign.error());
+ ByValCopies.push_back(
+ {Index, Value.Value, Value.Type, *CopyOffset, Value.ByValSizeBytes, *CopyAlign});
+ CallFrameCursor = *CopyOffset + Value.ByValSizeBytes;
+ }
+ auto CallFrameSize = alignTo(CallFrameCursor, LP64DStackAlignmentBytes);
+ if (!CallFrameSize)
+ return std::unexpected(CallFrameSize.error());
+
+ TailCallKind Decision = TailCallKind::NotTail;
+ std::string RejectionReason;
+ if (Request.TailKind != TailCallKind::NotTail) {
+ auto Rejection = tailCallRejection(Request, *Arguments, *Results, ByValCopies, **Convention);
+ if (!Rejection)
+ return std::unexpected(Rejection.error());
+ RejectionReason = std::move(*Rejection);
+ if (RejectionReason.empty()) {
+ Decision = Request.TailKind;
+ } else if (Request.TailKind == TailCallKind::MustTail) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "musttail call is not ABI-compatible: " + RejectionReason);
+ } else {
+ auto NormalResults = verifyValueSequence(Request.Results, true);
+ if (!NormalResults)
+ return std::unexpected(NormalResults.error());
+ }
+ }
+
+ CallLoweringPlanDesc Plan;
+ Plan.Kind = CallLoweringPlanKind::Call;
+ Plan.Target = PlanTarget;
+ Plan.Convention = Request.Convention;
+ Plan.ArgumentAssignments = std::move(Arguments->Assignments);
+ Plan.ReturnAssignments = std::move(*Results);
+ Plan.StackArguments = Arguments->StackArea;
+ Plan.CallFrame = CallFrameRequirements{
+ Decision == TailCallKind::NotTail ? *CallFrameSize : Arguments->StackArea.SizeBytes, *Align16,
+ Decision == TailCallKind::NotTail, Decision == TailCallKind::NotTail,
+ Decision != TailCallKind::NotTail};
+ if (Request.IsVariadic) {
+ Plan.VarArgs.IsVariadic = true;
+ Plan.VarArgs.FixedArgumentCount = Request.FixedArgumentCount;
+ }
+ Plan.ByValCopies = std::move(ByValCopies);
+ Plan.PreservedMask = createCallPreservedMask();
+ Plan.Callee = std::move(EffectiveCallee);
+ Plan.CallSiteID = Request.CallSiteID;
+ Plan.TailDecision = Decision;
+ Plan.TailRejectionReason = std::move(RejectionReason);
+ return CallLoweringPlan::create(std::move(Plan));
+}
+
+Expected<CallLoweringPlan>
+RISCVCallLowering::planReturn(const ReturnLoweringRequest &Request) const {
+ auto Convention = lp64dConvention(Request.Convention);
+ if (!Convention)
+ return std::unexpected(Convention.error());
+ auto Values = assignReturns(Request.Values, true, **Convention);
+ if (!Values)
+ return std::unexpected(Values.error());
+
+ CallLoweringPlanDesc Plan;
+ Plan.Kind = CallLoweringPlanKind::Return;
+ Plan.Target = PlanTarget;
+ Plan.Convention = Request.Convention;
+ Plan.ReturnAssignments = std::move(*Values);
+ return CallLoweringPlan::create(std::move(Plan));
+}
+
+[[nodiscard]] CallValueTransform outgoingTransform(const CCAssignment &Assignment) noexcept {
+ if (Assignment.CustomFlags.has(CCAssignmentFlag::Bitcast))
+ return CallValueTransform::Bitcast;
+ switch (Assignment.Extension) {
+ case ABIExtensionKind::None:
+ return CallValueTransform::Full;
+ case ABIExtensionKind::AnyExtend:
+ return CallValueTransform::AnyExtend;
+ case ABIExtensionKind::SignExtend:
+ return CallValueTransform::SignExtend;
+ case ABIExtensionKind::ZeroExtend:
+ return CallValueTransform::ZeroExtend;
+ case ABIExtensionKind::Count:
+ break;
+ }
+ return CallValueTransform::Full;
+}
+
+[[nodiscard]] CallValueTransform incomingTransform(const CCAssignment &Assignment) noexcept {
+ if (Assignment.CustomFlags.has(CCAssignmentFlag::Bitcast))
+ return CallValueTransform::Bitcast;
+ if (Assignment.ValueType.scalarBitWidth() < Assignment.LocationType.scalarBitWidth())
+ return CallValueTransform::Truncate;
+ return CallValueTransform::Full;
+}
+
+[[nodiscard]] Expected<void> addUnsignedImmediate(MachineInstrBuilder &Builder,
+ std::uint64_t Value) {
+ auto Immediate = IntegerImmediate::fromUnsigned(64, Value);
+ if (!Immediate)
+ return std::unexpected(Immediate.error());
+ return Builder.addIntegerImmediate(std::move(*Immediate));
+}
+
+[[nodiscard]] Expected<const MachineMemOperand *>
+createMemoryOperand(MachineFunction &Function, MemoryAccessFlags Accesses, std::uint64_t SizeBytes,
+ Alignment Align, MemoryBase Base, std::int64_t Offset = 0) {
+ auto Size = MemorySize::fixed(SizeBytes);
+ if (!Size)
+ return std::unexpected(Size.error());
+ MachineMemOperandDesc Description;
+ Description.Accesses = Accesses;
+ Description.Size = *Size;
+ Description.Align = Align;
+ Description.Base = std::move(Base);
+ Description.Offset = Offset;
+ return Function.createMemOperand(std::move(Description));
+}
+
+[[nodiscard]] Expected<void> emitCCCopy(MachineIRBuilder &Builder, Register Destination,
+ Register Source, CallValueTransform Transform) {
+ auto Instruction = Builder.buildInstr(targetOpcode(InstructionOpcode::PseudoCC_COPY));
+ if (!Instruction)
+ return std::unexpected(Instruction.error());
+ auto Def = Instruction->addDef(Destination);
+ if (!Def)
+ return std::unexpected(Def.error());
+ auto Use = Instruction->addUse(Source);
+ if (!Use)
+ return std::unexpected(Use.error());
+ auto Action = Instruction->addImmediate(static_cast<std::int64_t>(Transform));
+ if (!Action)
+ return std::unexpected(Action.error());
+ auto Built = Instruction->build();
+ if (!Built)
+ return std::unexpected(Built.error());
+ return {};
+}
+
+[[nodiscard]] Expected<void> emitStackLoad(MachineIRBuilder &Builder,
+ const CCAssignment &Assignment, FrameIndex Object,
+ std::uint64_t ObjectOffset,
+ const MachineMemOperand &Memory) {
+ auto Instruction = Builder.buildInstr(targetOpcode(InstructionOpcode::PseudoLOAD_STACK_ARG));
+ if (!Instruction)
+ return std::unexpected(Instruction.error());
+ auto Def = Instruction->addDef(Assignment.OriginalValue);
+ if (!Def)
+ return std::unexpected(Def.error());
+ auto Frame = Instruction->addFrameIndex(Object);
+ if (!Frame)
+ return std::unexpected(Frame.error());
+ auto Offset = addUnsignedImmediate(*Instruction, ObjectOffset);
+ if (!Offset)
+ return std::unexpected(Offset.error());
+ auto Transform =
+ Instruction->addImmediate(static_cast<std::int64_t>(incomingTransform(Assignment)));
+ if (!Transform)
+ return std::unexpected(Transform.error());
+ auto Mem = Instruction->addMemOperand(Memory);
+ if (!Mem)
+ return std::unexpected(Mem.error());
+ auto Built = Instruction->build();
+ if (!Built)
+ return std::unexpected(Built.error());
+ return {};
+}
+
+[[nodiscard]] Expected<void> emitStackStore(MachineIRBuilder &Builder, Register Source,
+ FrameIndex Object, std::uint64_t ObjectOffset,
+ const MachineMemOperand &Memory,
+ CallValueTransform Transform) {
+ auto Instruction = Builder.buildInstr(targetOpcode(InstructionOpcode::PseudoSTORE_STACK_ARG));
+ if (!Instruction)
+ return std::unexpected(Instruction.error());
+ auto Use = Instruction->addUse(Source);
+ if (!Use)
+ return std::unexpected(Use.error());
+ auto Frame = Instruction->addFrameIndex(Object);
+ if (!Frame)
+ return std::unexpected(Frame.error());
+ auto Offset = addUnsignedImmediate(*Instruction, ObjectOffset);
+ if (!Offset)
+ return std::unexpected(Offset.error());
+ auto Action = Instruction->addImmediate(static_cast<std::int64_t>(Transform));
+ if (!Action)
+ return std::unexpected(Action.error());
+ auto Mem = Instruction->addMemOperand(Memory);
+ if (!Mem)
+ return std::unexpected(Mem.error());
+ auto Built = Instruction->build();
+ if (!Built)
+ return std::unexpected(Built.error());
+ return {};
+}
+
+[[nodiscard]] Expected<void> ensureFunctionLiveIn(MachineRegisterInfo &Registers,
+ PhysicalRegister Reg,
+ std::optional<Register> InitialValue) {
+ const auto Existing =
+ std::ranges::find(Registers.liveIns(), Reg, &PhysicalRegisterLiveIn::PhysReg);
+ if (Existing == Registers.liveIns().end())
+ return Registers.addLiveIn(Reg, InitialValue);
+ if (InitialValue && Existing->InitialVirtualRegister &&
+ Existing->InitialVirtualRegister != InitialValue) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "physical formal register already maps to another virtual live-in");
+ }
+ return {};
+}
+
+[[nodiscard]] Expected<void> ensureBlockLiveIn(MachineBasicBlock &Block, PhysicalRegister Reg) {
+ return Block.addLiveIn(Reg, LaneMask::all());
+}
+
+[[nodiscard]] Expected<void> verifyPlanRegisters(const CallLoweringPlan &Plan,
+ const MachineFunction &Function,
+ const TargetDescription &Target) {
+ const MachineRegisterInfo &Registers = Function.registerInfo();
+ const auto Verify = [&Registers, &Target](const CCAssignment &Assignment) -> Expected<void> {
+ if (Assignment.LocationRegister &&
+ !Target.registers().isKnownPhysicalRegister(*Assignment.LocationRegister)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "call-lowering plan names an unknown ABI location register");
+ }
+ const Register Reg = Assignment.OriginalValue;
+ if (Reg.isNoRegister())
+ return {};
+ if (Reg.isPhysical()) {
+ if (!Target.registers().isKnownPhysicalRegister(*Reg.physicalRegister())) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "call-lowering plan names an unknown physical value register");
+ }
+ return {};
+ }
+ auto Info = Registers.virtualRegisterInfo(Reg);
+ if (!Info)
+ return std::unexpected(Info.error());
+ if (!Info->get().Type || *Info->get().Type != Assignment.ValueType) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "call-lowering plan register LLT differs from MachineRegisterInfo");
+ }
+ return {};
+ };
+ for (const CCAssignment &Assignment : Plan.argumentAssignments()) {
+ auto Result = Verify(Assignment);
+ if (!Result)
+ return std::unexpected(Result.error());
+ }
+ for (const CCAssignment &Assignment : Plan.returnAssignments()) {
+ auto Result = Verify(Assignment);
+ if (!Result)
+ return std::unexpected(Result.error());
+ }
+ if (const MachineCallTarget *Callee = Plan.callee();
+ Callee != nullptr && Callee->kind() == MachineCallTargetKind::IndirectRegister) {
+ const Register CalleeReg = Callee->indirectRegister();
+ if (CalleeReg.isPhysical()) {
+ const std::optional<PhysicalRegister> Physical = CalleeReg.physicalRegister();
+ if (!Physical || !Target.registers().isKnownPhysicalRegister(*Physical)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "indirect LP64D callee names an unknown physical register");
+ }
+ } else {
+ auto Info = Registers.virtualRegisterInfo(CalleeReg);
+ if (!Info)
+ return std::unexpected(Info.error());
+ const std::optional<LLT> Type = Info->get().Type;
+ if (!Type || ((!Type->isPointer() && !Type->isInteger()) || Type->scalarBitWidth() != 64)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "indirect LP64D callee requires a 64-bit pointer or integer LLT");
+ }
+ }
+ }
+ return {};
+}
+
+[[nodiscard]] Expected<void> verifyRequiredOpcodes(const MachineIRBuilder &Builder) {
+ static constexpr std::array Required{
+ InstructionOpcode::PseudoCC_COPY,
+ InstructionOpcode::PseudoLOAD_STACK_ARG,
+ InstructionOpcode::PseudoSTORE_STACK_ARG,
+ InstructionOpcode::PseudoBYVAL_COPY,
+ InstructionOpcode::ADJCALLSTACKDOWN,
+ InstructionOpcode::ADJCALLSTACKUP,
+ InstructionOpcode::PseudoCALL,
+ InstructionOpcode::PseudoTAIL,
+ InstructionOpcode::PseudoRET,
+ };
+ for (const InstructionOpcode OpcodeValue : Required) {
+ if (Builder.target().instructionDescriptor(targetOpcode(OpcodeValue)) == nullptr) {
+ return makeUnexpected(ErrorCode::InternalError,
+ "RISC-V target description lacks a call-lowering pseudo");
+ }
+ }
+ return {};
+}
+
+[[nodiscard]] Expected<void> verifyFormalLiveIns(const CallLoweringPlan &Plan,
+ const MachineFunction &Function) {
+ if (Plan.kind() != CallLoweringPlanKind::FormalArguments)
+ return {};
+
+ const auto LiveIns = Function.registerInfo().liveIns();
+ for (const CCAssignment &Assignment : Plan.argumentAssignments()) {
+ if (!Assignment.LocationRegister || !Assignment.OriginalValue.isVirtual())
+ continue;
+ const auto Existing =
+ std::ranges::find(LiveIns, *Assignment.LocationRegister, &PhysicalRegisterLiveIn::PhysReg);
+ if (Existing != LiveIns.end() && Existing->InitialVirtualRegister &&
+ *Existing->InitialVirtualRegister != Assignment.OriginalValue) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "physical formal register already maps to another virtual live-in");
+ }
+ }
+ return {};
+}
+
+[[nodiscard]] Expected<void> preflightPlan(MachineIRBuilder &Builder,
+ const CallLoweringPlan &Plan) {
+ if (Plan.target() != PlanTarget ||
+ Plan.convention() != callingConventionID(CallingConvention::LP64D)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "call-lowering plan belongs to another target or ABI");
+ }
+ auto InsertPoint = Builder.verifyInsertPoint();
+ if (!InsertPoint)
+ return std::unexpected(InsertPoint.error());
+ auto Opcodes = verifyRequiredOpcodes(Builder);
+ if (!Opcodes)
+ return std::unexpected(Opcodes.error());
+ auto Registers = verifyPlanRegisters(Plan, Builder.function(), Builder.target());
+ if (!Registers)
+ return std::unexpected(Registers.error());
+ auto LiveIns = verifyFormalLiveIns(Plan, Builder.function());
+ if (!LiveIns)
+ return std::unexpected(LiveIns.error());
+ for (const CCAssignment &Assignment : Plan.argumentAssignments()) {
+ if (Assignment.StackOffsetBytes &&
+ *Assignment.StackOffsetBytes >
+ static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "LP64D stack assignment offset exceeds the frame-index range");
+ }
+ }
+ for (const ByValCopyAssignment &Copy : Plan.byValCopies()) {
+ if (Copy.StackOffsetBytes >
+ static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "LP64D byval offset exceeds the frame-index range");
+ }
+ }
+ if (Plan.kind() != CallLoweringPlanKind::Return && Builder.function().frameInfo().isFrozen()) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "call lowering cannot modify a frozen machine frame");
+ }
+ if (Plan.kind() == CallLoweringPlanKind::FormalArguments &&
+ (Builder.function().firstBlock() == nullptr ||
+ Builder.insertionBlock() != Builder.function().firstBlock())) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "formal arguments must be lowered in the entry machine block");
+ }
+ return {};
+}
+
+[[nodiscard]] Expected<void> updateMaximumFrameAlignment(MachineFrameState &State,
+ Alignment Required) {
+ if (!State.MaxAlignment || State.MaxAlignment->bytes() < Required.bytes())
+ State.MaxAlignment = Required;
+ return {};
+}
+
+[[nodiscard]] Expected<void> emitFormalArguments(MachineIRBuilder &Builder,
+ const CallLoweringPlan &Plan) {
+ MachineFunction &Function = Builder.function();
+ MachineBasicBlock &Entry = *Function.firstBlock();
+ MachineRegisterInfo &Registers = Function.registerInfo();
+ MachineFrameInfo &Frame = Function.frameInfo();
+ Alignment RequiredFrameAlignment;
+
+ for (const CCAssignment &Assignment : Plan.argumentAssignments()) {
+ if (Assignment.LocationRegister) {
+ const PhysicalRegister Location = *Assignment.LocationRegister;
+ const std::optional<Register> Initial =
+ Assignment.OriginalValue.isVirtual() ? std::optional<Register>(Assignment.OriginalValue)
+ : std::nullopt;
+ auto FunctionLiveIn = ensureFunctionLiveIn(Registers, Location, Initial);
+ if (!FunctionLiveIn)
+ return std::unexpected(FunctionLiveIn.error());
+ auto BlockLiveIn = ensureBlockLiveIn(Entry, Location);
+ if (!BlockLiveIn)
+ return std::unexpected(BlockLiveIn.error());
+ auto Copy = emitCCCopy(Builder, Assignment.OriginalValue, Register::fromPhysical(Location),
+ incomingTransform(Assignment));
+ if (!Copy)
+ return std::unexpected(Copy.error());
+ continue;
+ }
+
+ if (!Assignment.StackOffsetBytes) {
+ return makeUnexpected(ErrorCode::InternalError,
+ "verified formal stack assignment has no stack offset");
+ }
+ StackObjectDesc Object;
+ Object.Kind = StackObjectKind::FixedABI;
+ Object.Size = Assignment.StackSlotSizeBytes;
+ Object.Align = Assignment.LocationAlignment;
+ Object.Offset = static_cast<std::int64_t>(*Assignment.StackOffsetBytes);
+ Object.IsImmutable = true;
+ Object.TargetFlags = stackObjectFlag(CallStackObjectFlag::IncomingArgument);
+ auto FrameObject = Frame.createObject(std::move(Object));
+ if (!FrameObject)
+ return std::unexpected(FrameObject.error());
+ auto Memory =
+ createMemoryOperand(Function, {MemoryAccessFlag::Load}, Assignment.LocationSizeBytes,
+ Assignment.LocationAlignment, MemoryBase::frameIndex(*FrameObject));
+ if (!Memory)
+ return std::unexpected(Memory.error());
+ auto Load = emitStackLoad(Builder, Assignment, *FrameObject, 0, **Memory);
+ if (!Load)
+ return std::unexpected(Load.error());
+ if (RequiredFrameAlignment.bytes() < Assignment.LocationAlignment.bytes())
+ RequiredFrameAlignment = Assignment.LocationAlignment;
+ }
+
+ const VarArgLoweringState &VarArgs = Plan.varArgs();
+ if (!VarArgs.IsVariadic) {
+ if (RequiredFrameAlignment.bytes() == 1)
+ return {};
+ MachineFrameState State = Frame.state();
+ auto AlignmentUpdate = updateMaximumFrameAlignment(State, RequiredFrameAlignment);
+ if (!AlignmentUpdate)
+ return std::unexpected(AlignmentUpdate.error());
+ return Frame.setState(std::move(State));
+ }
+
+ auto Align8 = alignment(XLenBytes);
+ auto Align16 = alignment(LP64DStackAlignmentBytes);
+ if (!Align8 || !Align16)
+ return makeUnexpected(ErrorCode::InternalError, "LP64D primitive type facts are invalid");
+
+ FrameIndex SaveObject;
+ if (VarArgs.RegisterSaveAreaSizeBytes == 0) {
+ StackObjectDesc Object;
+ Object.Kind = StackObjectKind::FixedABI;
+ Object.Size = XLenBytes;
+ Object.Align = *Align8;
+ Object.Offset = VarArgs.RegisterSaveAreaOffsetBytes;
+ Object.IsImmutable = true;
+ Object.TargetFlags = stackObjectFlag(CallStackObjectFlag::VarArgSaveArea);
+ auto Created = Frame.createObject(std::move(Object));
+ if (!Created)
+ return std::unexpected(Created.error());
+ SaveObject = *Created;
+ } else {
+ StackObjectDesc Object;
+ Object.Kind = StackObjectKind::FixedABI;
+ Object.Size = VarArgs.RegisterSaveAreaSizeBytes;
+ Object.Align = VarArgs.FirstUnallocatedIntegerRegister % 2 == 0 ? *Align16 : *Align8;
+ Object.Offset = VarArgs.RegisterSaveAreaOffsetBytes;
+ Object.TargetFlags = stackObjectFlag(CallStackObjectFlag::VarArgSaveArea);
+ auto Created = Frame.createObject(std::move(Object));
+ if (!Created)
+ return std::unexpected(Created.error());
+ SaveObject = *Created;
+
+ if (VarArgs.RegisterSavePaddingBytes != 0) {
+ StackObjectDesc Padding;
+ Padding.Kind = StackObjectKind::FixedABI;
+ Padding.Size = VarArgs.RegisterSavePaddingBytes;
+ Padding.Align = *Align8;
+ Padding.Offset = VarArgs.RegisterSaveAreaOffsetBytes -
+ static_cast<std::int64_t>(VarArgs.RegisterSavePaddingBytes);
+ Padding.IsImmutable = true;
+ Padding.TargetFlags = stackObjectFlag(CallStackObjectFlag::VarArgAlignmentPadding);
+ auto CreatedPadding = Frame.createObject(std::move(Padding));
+ if (!CreatedPadding)
+ return std::unexpected(CreatedPadding.error());
+ }
+ }
+
+ for (const VarArgRegisterSave Save : VarArgs.RegistersToSave) {
+ auto FunctionLiveIn = ensureFunctionLiveIn(Registers, Save.Reg, std::nullopt);
+ if (!FunctionLiveIn)
+ return std::unexpected(FunctionLiveIn.error());
+ auto BlockLiveIn = ensureBlockLiveIn(Entry, Save.Reg);
+ if (!BlockLiveIn)
+ return std::unexpected(BlockLiveIn.error());
+ auto Memory = createMemoryOperand(Function, {MemoryAccessFlag::Store}, XLenBytes, *Align8,
+ MemoryBase::frameIndex(SaveObject),
+ static_cast<std::int64_t>(Save.OffsetBytes));
+ if (!Memory)
+ return std::unexpected(Memory.error());
+ auto Store = emitStackStore(Builder, Register::fromPhysical(Save.Reg), SaveObject,
+ Save.OffsetBytes, **Memory, CallValueTransform::Full);
+ if (!Store)
+ return std::unexpected(Store.error());
+ }
+
+ MachineFrameState State = Frame.state();
+ const Alignment VarArgAlignment =
+ RequiredFrameAlignment.bytes() > Align16->bytes() ? RequiredFrameAlignment : *Align16;
+ auto AlignmentUpdate = updateMaximumFrameAlignment(State, VarArgAlignment);
+ if (!AlignmentUpdate)
+ return std::unexpected(AlignmentUpdate.error());
+ return Frame.setState(std::move(State));
+}
+
+[[nodiscard]] Expected<void> emitCallFrameAdjustment(MachineIRBuilder &Builder,
+ InstructionOpcode OpcodeValue,
+ const CallFrameRequirements &Frame,
+ MachineInstrFlag Flag) {
+ auto Instruction = Builder.buildInstr(targetOpcode(OpcodeValue));
+ if (!Instruction)
+ return std::unexpected(Instruction.error());
+ auto Size = addUnsignedImmediate(*Instruction, Frame.SizeBytes);
+ if (!Size)
+ return std::unexpected(Size.error());
+ auto Align = addUnsignedImmediate(*Instruction, Frame.Align.bytes());
+ if (!Align)
+ return std::unexpected(Align.error());
+ auto Flags = Instruction->setFlags({Flag});
+ if (!Flags)
+ return std::unexpected(Flags.error());
+ auto Built = Instruction->build();
+ if (!Built)
+ return std::unexpected(Built.error());
+ return {};
+}
+
+[[nodiscard]] Expected<std::unordered_map<std::size_t, Register>>
+emitByValCopies(MachineIRBuilder &Builder, const CallLoweringPlan &Plan) {
+ MachineFunction &Function = Builder.function();
+ MachineFrameInfo &Frame = Function.frameInfo();
+ std::unordered_map<std::size_t, Register> Replacements;
+ for (const ByValCopyAssignment Copy : Plan.byValCopies()) {
+ StackObjectDesc Object;
+ Object.Kind = StackObjectKind::Target;
+ Object.Size = Copy.SizeBytes;
+ Object.Align = Copy.Align;
+ Object.LocalOffset = static_cast<std::int64_t>(Copy.StackOffsetBytes);
+ Object.TargetFlags = stackObjectFlag(CallStackObjectFlag::ByValCopy);
+ auto FrameObject = Frame.createObject(std::move(Object));
+ if (!FrameObject)
+ return std::unexpected(FrameObject.error());
+ auto Temporary = Function.registerInfo().createVirtualRegister(Copy.PointerType);
+ if (!Temporary)
+ return std::unexpected(Temporary.error());
+
+ auto SourceMemory = createMemoryOperand(Function, {MemoryAccessFlag::Load}, Copy.SizeBytes,
+ Copy.Align, MemoryBase{});
+ if (!SourceMemory)
+ return std::unexpected(SourceMemory.error());
+ auto DestinationMemory =
+ createMemoryOperand(Function, {MemoryAccessFlag::Store}, Copy.SizeBytes, Copy.Align,
+ MemoryBase::frameIndex(*FrameObject));
+ if (!DestinationMemory)
+ return std::unexpected(DestinationMemory.error());
+
+ auto Instruction = Builder.buildInstr(targetOpcode(InstructionOpcode::PseudoBYVAL_COPY));
+ if (!Instruction)
+ return std::unexpected(Instruction.error());
+ auto Def = Instruction->addDef(*Temporary);
+ if (!Def)
+ return std::unexpected(Def.error());
+ auto Use = Instruction->addUse(Copy.Source);
+ if (!Use)
+ return std::unexpected(Use.error());
+ auto FrameOperand = Instruction->addFrameIndex(*FrameObject);
+ if (!FrameOperand)
+ return std::unexpected(FrameOperand.error());
+ auto Size = addUnsignedImmediate(*Instruction, Copy.SizeBytes);
+ if (!Size)
+ return std::unexpected(Size.error());
+ auto Align = addUnsignedImmediate(*Instruction, Copy.Align.bytes());
+ if (!Align)
+ return std::unexpected(Align.error());
+ auto SourceMem = Instruction->addMemOperand(**SourceMemory);
+ if (!SourceMem)
+ return std::unexpected(SourceMem.error());
+ auto DestinationMem = Instruction->addMemOperand(**DestinationMemory);
+ if (!DestinationMem)
+ return std::unexpected(DestinationMem.error());
+ auto Built = Instruction->build();
+ if (!Built)
+ return std::unexpected(Built.error());
+ Replacements.emplace(Copy.ArgumentAssignmentIndex, *Temporary);
+ }
+ return Replacements;
+}
+
+[[nodiscard]] Expected<void>
+emitOutgoingArguments(MachineIRBuilder &Builder, const CallLoweringPlan &Plan,
+ const std::unordered_map<std::size_t, Register> &Replacements) {
+ MachineFunction &Function = Builder.function();
+ MachineFrameInfo &Frame = Function.frameInfo();
+ const auto Assignments = Plan.argumentAssignments();
+ for (std::size_t Index = 0; Index < Assignments.size(); ++Index) {
+ const CCAssignment &Assignment = Assignments[Index];
+ const auto Replacement = Replacements.find(Index);
+ const Register Source =
+ Replacement == Replacements.end() ? Assignment.OriginalValue : Replacement->second;
+ if (Assignment.LocationRegister) {
+ auto Copy = emitCCCopy(Builder, Register::fromPhysical(*Assignment.LocationRegister), Source,
+ outgoingTransform(Assignment));
+ if (!Copy)
+ return std::unexpected(Copy.error());
+ continue;
+ }
+
+ if (!Assignment.StackOffsetBytes) {
+ return makeUnexpected(ErrorCode::InternalError,
+ "verified outgoing stack assignment has no stack offset");
+ }
+ StackObjectDesc Object;
+ Object.Size = Assignment.StackSlotSizeBytes;
+ Object.Align = Assignment.LocationAlignment;
+ Object.TargetFlags = stackObjectFlag(CallStackObjectFlag::OutgoingArgument);
+ if (Plan.tailDecision() == TailCallKind::NotTail) {
+ Object.Kind = StackObjectKind::Target;
+ Object.LocalOffset = static_cast<std::int64_t>(*Assignment.StackOffsetBytes);
+ } else {
+ Object.Kind = StackObjectKind::FixedABI;
+ Object.Offset = static_cast<std::int64_t>(*Assignment.StackOffsetBytes);
+ }
+ auto FrameObject = Frame.createObject(std::move(Object));
+ if (!FrameObject)
+ return std::unexpected(FrameObject.error());
+ auto Memory =
+ createMemoryOperand(Function, {MemoryAccessFlag::Store}, Assignment.LocationSizeBytes,
+ Assignment.LocationAlignment, MemoryBase::frameIndex(*FrameObject));
+ if (!Memory)
+ return std::unexpected(Memory.error());
+ auto Store =
+ emitStackStore(Builder, Source, *FrameObject, 0, **Memory, outgoingTransform(Assignment));
+ if (!Store)
+ return std::unexpected(Store.error());
+ }
+ return {};
+}
+
+[[nodiscard]] Expected<void> addCallTarget(MachineInstrBuilder &Instruction,
+ const MachineCallTarget &Callee) {
+ switch (Callee.kind()) {
+ case MachineCallTargetKind::GlobalSymbol:
+ return Instruction.addGlobalSymbol(std::string(Callee.symbolName()), 0, Callee.targetFlags());
+ case MachineCallTargetKind::ExternalSymbol:
+ return Instruction.addExternalSymbol(std::string(Callee.symbolName()), 0, Callee.targetFlags());
+ case MachineCallTargetKind::IndirectRegister:
+ return Instruction.addUse(Callee.indirectRegister());
+ case MachineCallTargetKind::Invalid:
+ break;
+ }
+ return makeUnexpected(ErrorCode::InternalError,
+ "verified call-lowering plan has an invalid callee");
+}
+
+[[nodiscard]] Expected<PhysicalRegister> requiredLocationRegister(const CCAssignment &Assignment) {
+ if (!Assignment.LocationRegister) {
+ return makeUnexpected(ErrorCode::InternalError,
+ "verified return assignment has no location register");
+ }
+ return *Assignment.LocationRegister;
+}
+
+[[nodiscard]] Expected<void> emitCallInstruction(MachineIRBuilder &Builder,
+ const CallLoweringPlan &Plan) {
+ const bool IsTail = Plan.tailDecision() != TailCallKind::NotTail;
+ auto Instruction = Builder.buildInstr(
+ targetOpcode(IsTail ? InstructionOpcode::PseudoTAIL : InstructionOpcode::PseudoCALL));
+ if (!Instruction)
+ return std::unexpected(Instruction.error());
+ auto Callee = addCallTarget(*Instruction, *Plan.callee());
+ if (!Callee)
+ return std::unexpected(Callee.error());
+ auto CallSite = addUnsignedImmediate(*Instruction, Plan.callSiteID());
+ if (!CallSite)
+ return std::unexpected(CallSite.error());
+ auto Mask = Instruction->addRegisterMask(Plan.preservedMask());
+ if (!Mask)
+ return std::unexpected(Mask.error());
+
+ std::unordered_set<PhysicalRegister> DynamicDefs;
+ if (!IsTail) {
+ for (const CCAssignment &Assignment : Plan.returnAssignments()) {
+ if (Assignment.LocationRegister && DynamicDefs.insert(*Assignment.LocationRegister).second) {
+ auto Def =
+ Instruction->addImplicitDef(Register::fromPhysical(*Assignment.LocationRegister));
+ if (!Def)
+ return std::unexpected(Def.error());
+ }
+ }
+ }
+ std::unordered_set<PhysicalRegister> DynamicUses;
+ for (const CCAssignment &Assignment : Plan.argumentAssignments()) {
+ if (Assignment.LocationRegister && DynamicUses.insert(*Assignment.LocationRegister).second) {
+ auto Use = Instruction->addImplicitUse(Register::fromPhysical(*Assignment.LocationRegister));
+ if (!Use)
+ return std::unexpected(Use.error());
+ }
+ }
+ auto Built = Instruction->build();
+ if (!Built)
+ return std::unexpected(Built.error());
+ return {};
+}
+
+[[nodiscard]] Expected<void> emitCall(MachineIRBuilder &Builder, const CallLoweringPlan &Plan) {
+ if (Plan.callFrame().EmitSetup) {
+ auto Down = emitCallFrameAdjustment(Builder, InstructionOpcode::ADJCALLSTACKDOWN,
+ Plan.callFrame(), MachineInstrFlag::FrameSetup);
+ if (!Down)
+ return std::unexpected(Down.error());
+ }
+
+ auto Replacements = emitByValCopies(Builder, Plan);
+ if (!Replacements)
+ return std::unexpected(Replacements.error());
+ auto Arguments = emitOutgoingArguments(Builder, Plan, *Replacements);
+ if (!Arguments)
+ return std::unexpected(Arguments.error());
+ auto Call = emitCallInstruction(Builder, Plan);
+ if (!Call)
+ return std::unexpected(Call.error());
+
+ if (Plan.callFrame().EmitDestroy) {
+ auto Up = emitCallFrameAdjustment(Builder, InstructionOpcode::ADJCALLSTACKUP, Plan.callFrame(),
+ MachineInstrFlag::FrameDestroy);
+ if (!Up)
+ return std::unexpected(Up.error());
+ }
+ if (Plan.tailDecision() == TailCallKind::NotTail) {
+ for (const CCAssignment &Assignment : Plan.returnAssignments()) {
+ auto Location = requiredLocationRegister(Assignment);
+ if (!Location)
+ return std::unexpected(Location.error());
+ auto Copy = emitCCCopy(Builder, Assignment.OriginalValue, Register::fromPhysical(*Location),
+ incomingTransform(Assignment));
+ if (!Copy)
+ return std::unexpected(Copy.error());
+ }
+ }
+
+ MachineFrameInfo &Frame = Builder.function().frameInfo();
+ MachineFrameState State = Frame.state();
+ State.HasCalls = true;
+ State.HasTailCall = State.HasTailCall || Plan.tailDecision() != TailCallKind::NotTail;
+ State.AdjustsStack =
+ State.AdjustsStack || (Plan.callFrame().EmitSetup && Plan.callFrame().SizeBytes != 0);
+ State.HasMustTailInVarArgFunc =
+ State.HasMustTailInVarArgFunc ||
+ (Plan.tailDecision() == TailCallKind::MustTail && Plan.varArgs().IsVariadic);
+ if (Plan.tailDecision() == TailCallKind::NotTail) {
+ State.MaxCallFrameSize =
+ std::max(State.MaxCallFrameSize.value_or(0), Plan.callFrame().SizeBytes);
+ }
+ auto AlignmentUpdate = updateMaximumFrameAlignment(State, Plan.callFrame().Align);
+ if (!AlignmentUpdate)
+ return std::unexpected(AlignmentUpdate.error());
+ return Frame.setState(std::move(State));
+}
+
+[[nodiscard]] Expected<void> emitReturn(MachineIRBuilder &Builder, const CallLoweringPlan &Plan) {
+ for (const CCAssignment &Assignment : Plan.returnAssignments()) {
+ auto Location = requiredLocationRegister(Assignment);
+ if (!Location)
+ return std::unexpected(Location.error());
+ auto Copy = emitCCCopy(Builder, Register::fromPhysical(*Location), Assignment.OriginalValue,
+ outgoingTransform(Assignment));
+ if (!Copy)
+ return std::unexpected(Copy.error());
+ }
+
+ auto Return = Builder.buildInstr(targetOpcode(InstructionOpcode::PseudoRET));
+ if (!Return)
+ return std::unexpected(Return.error());
+ for (const CCAssignment &Assignment : Plan.returnAssignments()) {
+ auto Location = requiredLocationRegister(Assignment);
+ if (!Location)
+ return std::unexpected(Location.error());
+ auto Use = Return->addImplicitUse(Register::fromPhysical(*Location));
+ if (!Use)
+ return std::unexpected(Use.error());
+ }
+ auto Built = Return->build();
+ if (!Built)
+ return std::unexpected(Built.error());
+ return {};
+}
+
+Expected<void> RISCVCallLowering::emitPlan(MachineIRBuilder &Builder,
+ const CallLoweringPlan &Plan) const {
+ auto Preflight = preflightPlan(Builder, Plan);
+ if (!Preflight)
+ return std::unexpected(Preflight.error());
+ switch (Plan.kind()) {
+ case CallLoweringPlanKind::FormalArguments:
+ return emitFormalArguments(Builder, Plan);
+ case CallLoweringPlanKind::Call:
+ return emitCall(Builder, Plan);
+ case CallLoweringPlanKind::Return:
+ return emitReturn(Builder, Plan);
+ }
+ return makeUnexpected(ErrorCode::InternalError, "call-lowering plan kind is invalid");
+}
+
+} // namespace
+
+const TargetCallLowering &callLowering() noexcept {
+ static const RISCVCallLowering Lowering;
+ return Lowering;
+}
+
+} // namespace rcc::riscv
diff --git a/backend/unittests/CodeGen/CMakeLists.txt b/backend/unittests/CodeGen/CMakeLists.txt
index ef96358..deb7a75 100644
--- a/backend/unittests/CodeGen/CMakeLists.txt
+++ b/backend/unittests/CodeGen/CMakeLists.txt
@@ -4,6 +4,7 @@ set(
RCCBackendCodeGenTestSources
CompilationUnitTest.cpp
CoreTypesTest.cpp
+ GenericOpcodesTest.cpp
InstructionDescriptorTest.cpp
MachineCompilationStatusTest.cpp
MachineConstantPoolTest.cpp
@@ -14,13 +15,16 @@ set(
MachineIRAttributesTest.cpp
MachineIRBuilderTest.cpp
MachineIRCoreTest.cpp
+ MachineAnalysesTest.cpp
MachineJumpTableInfoTest.cpp
MachineMetadataTest.cpp
MachineOperandTest.cpp
+ MachinePassTest.cpp
MachineVerifierTest.cpp
MachineVerifierTargetTest.cpp
MachineValueTypesTest.cpp
TargetConstantTest.cpp
+ TargetCallLoweringTest.cpp
TargetFunctionStateTest.cpp
TargetMIRTextInfoTest.cpp
TargetRegisterInfoTest.cpp
@@ -41,6 +45,12 @@ target_link_libraries(RCCBackendCodeGenCoreTypesTests PRIVATE RCCBackend::CodeGe
add_test(NAME RCCBackend.CodeGen.CoreTypes COMMAND RCCBackendCodeGenCoreTypesTests)
+add_executable(RCCBackendCodeGenGenericOpcodesTests GenericOpcodesTest.cpp)
+rcc_backend_configure_target(RCCBackendCodeGenGenericOpcodesTests)
+target_link_libraries(RCCBackendCodeGenGenericOpcodesTests PRIVATE RCCBackend::CodeGen)
+
+add_test(NAME RCCBackend.CodeGen.GenericOpcodes COMMAND RCCBackendCodeGenGenericOpcodesTests)
+
add_executable(
RCCBackendCodeGenInstructionDescriptorTests
InstructionDescriptorTest.cpp
@@ -233,6 +243,24 @@ add_test(
COMMAND RCCBackendCodeGenMachineOperandTests
)
+add_executable(RCCBackendCodeGenMachinePassTests MachinePassTest.cpp)
+rcc_backend_configure_target(RCCBackendCodeGenMachinePassTests)
+target_link_libraries(
+ RCCBackendCodeGenMachinePassTests
+ PRIVATE RCCBackend::CodeGen RCCBackend::TestTarget
+)
+
+add_test(NAME RCCBackend.CodeGen.MachinePass COMMAND RCCBackendCodeGenMachinePassTests)
+
+add_executable(RCCBackendCodeGenMachineAnalysesTests MachineAnalysesTest.cpp)
+rcc_backend_configure_target(RCCBackendCodeGenMachineAnalysesTests)
+target_link_libraries(
+ RCCBackendCodeGenMachineAnalysesTests
+ PRIVATE RCCBackend::CodeGen RCCBackend::TestTarget
+)
+
+add_test(NAME RCCBackend.CodeGen.MachineAnalyses COMMAND RCCBackendCodeGenMachineAnalysesTests)
+
add_executable(RCCBackendCodeGenTargetConstantTests TargetConstantTest.cpp)
rcc_backend_configure_target(RCCBackendCodeGenTargetConstantTests)
target_link_libraries(RCCBackendCodeGenTargetConstantTests PRIVATE RCCBackend::CodeGen)
@@ -242,6 +270,18 @@ add_test(
COMMAND RCCBackendCodeGenTargetConstantTests
)
+add_executable(RCCBackendCodeGenTargetCallLoweringTests TargetCallLoweringTest.cpp)
+rcc_backend_configure_target(RCCBackendCodeGenTargetCallLoweringTests)
+target_link_libraries(
+ RCCBackendCodeGenTargetCallLoweringTests
+ PRIVATE RCCBackend::CodeGen RCCBackend::TestTarget
+)
+
+add_test(
+ NAME RCCBackend.CodeGen.TargetCallLowering
+ COMMAND RCCBackendCodeGenTargetCallLoweringTests
+)
+
add_executable(RCCBackendCodeGenTargetFunctionStateTests TargetFunctionStateTest.cpp)
rcc_backend_configure_target(RCCBackendCodeGenTargetFunctionStateTests)
target_link_libraries(
@@ -281,6 +321,7 @@ add_test(
rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/CompilationUnitTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/CoreTypesTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/GenericOpcodesTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineCompilationStatusTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineConstantPoolTest.cpp"
@@ -291,13 +332,16 @@ rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/MachineIRAttributesTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineIRBuilderTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineIRCoreTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MachineAnalysesTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineJumpTableInfoTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineMetadataTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineOperandTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MachinePassTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifierTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifierTargetTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineValueTypesTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetConstantTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/TargetCallLoweringTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetFunctionStateTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetMIRTextInfoTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetRegisterInfoTest.cpp"
@@ -305,6 +349,7 @@ rcc_backend_register_format_files(
rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/CompilationUnitTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/CoreTypesTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/GenericOpcodesTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineCompilationStatusTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineConstantPoolTest.cpp"
@@ -315,13 +360,16 @@ rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/MachineIRAttributesTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineIRBuilderTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineIRCoreTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MachineAnalysesTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineJumpTableInfoTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineMetadataTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineOperandTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MachinePassTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifierTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifierTargetTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineValueTypesTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetConstantTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/TargetCallLoweringTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetFunctionStateTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetMIRTextInfoTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetRegisterInfoTest.cpp"
diff --git a/backend/unittests/CodeGen/GenericOpcodesTest.cpp b/backend/unittests/CodeGen/GenericOpcodesTest.cpp
new file mode 100644
index 0000000..36cc6ca
--- /dev/null
+++ b/backend/unittests/CodeGen/GenericOpcodesTest.cpp
@@ -0,0 +1,701 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/GenericOpcodes.h"
+
+#include "rcc/CodeGen/MachineBasicBlock.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineInstr.h"
+#include "rcc/CodeGen/MachineMemOperand.h"
+
+#include <array>
+#include <bit>
+#include <cstddef>
+#include <cstdint>
+#include <iostream>
+#include <ranges>
+#include <span>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+namespace {
+
+class TestContext final {
+public:
+ void expect(bool Condition, std::string_view Message) {
+ if (Condition)
+ return;
+ std::cerr << Message << '\n';
+ ++Failures;
+ }
+
+ [[nodiscard]] int result() const noexcept {
+ return Failures == 0 ? 0 : 1;
+ }
+
+private:
+ int Failures = 0;
+};
+
+[[nodiscard]] std::vector<rcc::InstructionDescriptor> copyDescriptors() {
+ const auto Source = rcc::genericInstructionDescriptors();
+ return {Source.begin(), Source.end()};
+}
+
+[[nodiscard]] rcc::MachineOperand registerOperand(rcc::Register Reg, bool IsDef) {
+ rcc::RegisterOperandFlags Flags;
+ Flags.set(IsDef ? rcc::RegisterOperandFlag::Define : rcc::RegisterOperandFlag::Use);
+ auto Operand = rcc::MachineOperand::registerOperand(Reg, Flags);
+ if (!Operand)
+ std::terminate();
+ return std::move(*Operand);
+}
+
+[[nodiscard]] rcc::Register createVirtualRegister(rcc::MachineFunction &Function, rcc::LLT Type) {
+ auto Reg = Function.registerInfo().createVirtualRegister(Type);
+ if (!Reg)
+ std::terminate();
+ return *Reg;
+}
+
+[[nodiscard]] rcc::MachineInstr &createInstruction(rcc::MachineFunction &Function,
+ rcc::GenericOpcode OpcodeValue,
+ std::vector<rcc::MachineOperand> Operands) {
+ auto Instruction = Function.createInstruction(rcc::opcode(OpcodeValue));
+ if (!Instruction)
+ std::terminate();
+ for (rcc::MachineOperand &Operand : Operands) {
+ if (!(*Instruction)->addOperand(std::move(Operand)))
+ std::terminate();
+ }
+ return **Instruction;
+}
+
+[[nodiscard]] rcc::MachineMemOperandDesc
+memoryDescription(rcc::MemoryAccessFlags Accesses, std::uint64_t Bytes,
+ std::uint32_t AddressSpace = 0,
+ rcc::AtomicOperation AtomicKind = rcc::AtomicOperation::None) {
+ rcc::MachineMemOperandDesc Description;
+ Description.Accesses = Accesses;
+ Description.AtomicKind = AtomicKind;
+ if (AtomicKind == rcc::AtomicOperation::ReadModifyWrite) {
+ Description.SuccessOrdering = rcc::AtomicOrdering::AcquireRelease;
+ } else if (AtomicKind == rcc::AtomicOperation::CompareExchange) {
+ Description.SuccessOrdering = rcc::AtomicOrdering::AcquireRelease;
+ Description.FailureOrdering = rcc::AtomicOrdering::Acquire;
+ }
+ Description.Size = *rcc::MemorySize::fixed(Bytes);
+ Description.Align = *rcc::Alignment::create(std::bit_floor(Bytes));
+ Description.AddressSpace = AddressSpace;
+ Description.Base = rcc::MemoryBase::stack();
+ return Description;
+}
+
+void addMemOperand(rcc::MachineFunction &Function, rcc::MachineInstr &Instruction,
+ rcc::MachineMemOperandDesc Description) {
+ auto Memory = Function.createMemOperand(std::move(Description));
+ if (!Memory || !Instruction.addMemOperand(**Memory))
+ std::terminate();
+}
+
+void testCompleteRegistry(TestContext &Context) {
+ static_assert(rcc::CorePseudoOpcodeCount == 52);
+ static_assert(rcc::GenericOpcodeCount == 278);
+
+ const auto Descriptors = rcc::genericInstructionDescriptors();
+ Context.expect(Descriptors.size() == 330,
+ "the target-independent registry must contain 52 core and 278 generic opcodes");
+
+ std::unordered_set<std::string> Names;
+ std::unordered_set<rcc::Opcode> Opcodes;
+ bool ClassesAreCorrect = true;
+ for (const rcc::InstructionDescriptor &Descriptor : Descriptors) {
+ ClassesAreCorrect &= Descriptor.InstructionOpcode.isValid() && !Descriptor.Name.empty() &&
+ Names.insert(Descriptor.Name).second &&
+ Opcodes.insert(Descriptor.InstructionOpcode).second;
+ if (rcc::isGenericOpcode(Descriptor.InstructionOpcode)) {
+ ClassesAreCorrect &= Descriptor.Flags.has(rcc::InstructionFlag::Generic) &&
+ !Descriptor.Flags.has(rcc::InstructionFlag::Pseudo);
+ } else {
+ ClassesAreCorrect &= rcc::isCorePseudoOpcode(Descriptor.InstructionOpcode) &&
+ Descriptor.Flags.has(rcc::InstructionFlag::Pseudo) &&
+ !Descriptor.Flags.has(rcc::InstructionFlag::Generic);
+ }
+ }
+ Context.expect(ClassesAreCorrect,
+ "every target-independent opcode must have one valid name, value, and class");
+ Context.expect(rcc::getGenericOpcodeName(rcc::GenericOpcode::PHI) == "PHI" &&
+ rcc::getGenericOpcodeName(rcc::GenericOpcode::G_ASSERT_SEXT) ==
+ "G_ASSERT_SEXT" &&
+ rcc::getGenericOpcodeName(rcc::GenericOpcode::G_UBFX) == "G_UBFX" &&
+ rcc::getGenericOpcodeName(rcc::GenericOpcode::Invalid).empty(),
+ "opcode names must cover both ranges without assigning marker names");
+
+ auto Set = rcc::InstructionSet::create(copyDescriptors());
+ Context.expect(Set.has_value(), "the complete target-independent descriptor set must validate");
+}
+
+void testCoreOpcodeContracts(TestContext &Context) {
+ struct Contract final {
+ rcc::GenericOpcode OpcodeValue;
+ std::size_t DescriptorOperands;
+ std::size_t ExplicitDefs;
+ bool Variadic;
+ std::size_t MinimumRepetitions;
+ bool Meta;
+ bool TerminalEmission;
+ rcc::PseudoOwner Owner;
+ rcc::PipelineStage Deadline;
+ };
+
+#define RCC_META(Name, Operands, Defs, Variadic, Minimum) \
+ Contract{rcc::GenericOpcode::Name, \
+ Operands, \
+ Defs, \
+ Variadic, \
+ Minimum, \
+ true, \
+ false, \
+ rcc::PseudoOwner::None, \
+ rcc::PipelineStage::Emission}
+#define RCC_PSEUDO(Name, Operands, Defs, Variadic, Minimum, Owner, Deadline) \
+ Contract{rcc::GenericOpcode::Name, \
+ Operands, \
+ Defs, \
+ Variadic, \
+ Minimum, \
+ false, \
+ false, \
+ rcc::PseudoOwner::Owner, \
+ rcc::PipelineStage::Deadline}
+#define RCC_TERMINAL(Name, Operands, Defs, Variadic, Minimum) \
+ Contract { \
+ rcc::GenericOpcode::Name, Operands, Defs, Variadic, Minimum, false, true, \
+ rcc::PseudoOwner::AsmPrinter, rcc::PipelineStage::Emission \
+ }
+
+ const std::array Contracts{
+ RCC_PSEUDO(PHI, 3, 1, true, 1, PhiElimination, SSAEliminated),
+ RCC_TERMINAL(INLINEASM, 1, 0, true, 0),
+ RCC_TERMINAL(INLINEASM_BR, 1, 0, true, 0),
+ RCC_META(CFI_INSTRUCTION, 1, 0, false, 0),
+ RCC_META(EH_LABEL, 1, 0, false, 0),
+ RCC_META(GC_LABEL, 1, 0, false, 0),
+ RCC_META(ANNOTATION_LABEL, 1, 0, false, 0),
+ RCC_META(KILL, 1, 0, true, 0),
+ RCC_PSEUDO(EXTRACT_SUBREG, 3, 1, false, 0, PostRAExpansion, PostRAFinalized),
+ RCC_PSEUDO(INSERT_SUBREG, 4, 1, false, 0, TwoAddressRewrite, TiedOpsRewritten),
+ RCC_META(IMPLICIT_DEF, 1, 1, false, 0),
+ RCC_PSEUDO(INIT_UNDEF, 1, 1, false, 0, RegisterAllocator, RegisterAllocated),
+ RCC_PSEUDO(SUBREG_TO_REG, 3, 1, false, 0, PostRAExpansion, PostRAFinalized),
+ RCC_PSEUDO(COPY_TO_REGCLASS, 3, 1, false, 0, RegisterAllocator, RegisterAllocated),
+ RCC_META(DBG_VALUE, 1, 0, true, 0),
+ RCC_META(DBG_VALUE_LIST, 1, 0, true, 0),
+ RCC_META(DBG_INSTR_REF, 1, 0, true, 0),
+ RCC_META(DBG_PHI, 1, 0, true, 0),
+ RCC_META(DBG_LABEL, 1, 0, false, 0),
+ RCC_PSEUDO(REG_SEQUENCE, 3, 1, true, 1, TwoAddressRewrite, TiedOpsRewritten),
+ RCC_PSEUDO(COPY, 2, 1, false, 0, PostRAExpansion, PostRAFinalized),
+ RCC_PSEUDO(COPY_LANEMASK, 3, 1, false, 0, PostRAExpansion, PostRAFinalized),
+ RCC_META(BUNDLE, 1, 0, true, 0),
+ RCC_META(LIFETIME_START, 1, 0, false, 0),
+ RCC_META(LIFETIME_END, 1, 0, false, 0),
+ RCC_META(PSEUDO_PROBE, 4, 0, false, 0),
+ RCC_META(ARITH_FENCE, 2, 1, false, 0),
+ RCC_TERMINAL(STACKMAP, 3, 0, true, 0),
+ RCC_TERMINAL(FENTRY_CALL, 0, 0, false, 0),
+ RCC_TERMINAL(PATCHPOINT, 7, 1, true, 0),
+ RCC_PSEUDO(LOAD_STACK_GUARD, 1, 1, false, 0, InstructionSelect, Selected),
+ RCC_PSEUDO(PREALLOCATED_SETUP, 1, 0, false, 0, InstructionSelect, Selected),
+ RCC_PSEUDO(PREALLOCATED_ARG, 3, 1, false, 0, InstructionSelect, Selected),
+ RCC_TERMINAL(STATEPOINT, 1, 0, true, 0),
+ RCC_META(LOCAL_ESCAPE, 2, 0, false, 0),
+ RCC_TERMINAL(FAULTING_OP, 2, 1, true, 0),
+ RCC_TERMINAL(PATCHABLE_OP, 1, 0, true, 0),
+ RCC_TERMINAL(PATCHABLE_FUNCTION_ENTER, 0, 0, false, 0),
+ RCC_TERMINAL(PATCHABLE_RET, 1, 0, true, 0),
+ RCC_TERMINAL(PATCHABLE_FUNCTION_EXIT, 0, 0, false, 0),
+ RCC_TERMINAL(PATCHABLE_TAIL_CALL, 1, 0, true, 0),
+ RCC_TERMINAL(PATCHABLE_EVENT_CALL, 2, 0, false, 0),
+ RCC_TERMINAL(PATCHABLE_TYPED_EVENT_CALL, 3, 0, false, 0),
+ RCC_PSEUDO(ICALL_BRANCH_FUNNEL, 1, 0, true, 0, PostRAExpansion, PostRAFinalized),
+ RCC_META(FAKE_USE, 1, 0, true, 0),
+ RCC_META(MEMBARRIER, 0, 0, false, 0),
+ RCC_META(JUMP_TABLE_DEBUG_INFO, 1, 0, false, 0),
+ RCC_META(RELOC_NONE, 1, 0, false, 0),
+ RCC_META(CONVERGENCECTRL_ENTRY, 1, 1, false, 0),
+ RCC_META(CONVERGENCECTRL_ANCHOR, 1, 1, false, 0),
+ RCC_META(CONVERGENCECTRL_LOOP, 2, 1, false, 0),
+ RCC_META(CONVERGENCECTRL_GLUE, 1, 0, false, 0),
+ };
+#undef RCC_TERMINAL
+#undef RCC_PSEUDO
+#undef RCC_META
+
+ static_assert(Contracts.size() == rcc::CorePseudoOpcodeCount);
+ const auto Descriptors = rcc::genericInstructionDescriptors();
+ bool Matches = true;
+ for (const Contract &Expected : Contracts) {
+ const auto Found = std::ranges::find_if(
+ Descriptors, [&Expected](const rcc::InstructionDescriptor &Descriptor) {
+ return Descriptor.InstructionOpcode == rcc::opcode(Expected.OpcodeValue);
+ });
+ if (Found == Descriptors.end()) {
+ Matches = false;
+ continue;
+ }
+ Matches &=
+ Found->Operands.size() == Expected.DescriptorOperands &&
+ Found->NumExplicitDefs == Expected.ExplicitDefs && Found->Variadic == Expected.Variadic &&
+ Found->MinimumVariadicRepetitions == Expected.MinimumRepetitions &&
+ Found->Flags.has(rcc::InstructionFlag::Meta) == Expected.Meta &&
+ Found->Flags.has(rcc::InstructionFlag::TerminalEmission) == Expected.TerminalEmission &&
+ Found->Owner == Expected.Owner && Found->Deadline == Expected.Deadline;
+ }
+ Context.expect(Matches,
+ "all 52 core opcodes must retain their reviewed shape and lifecycle contracts");
+
+ const auto InlineAsm = std::ranges::find_if(Descriptors, [](const auto &Descriptor) {
+ return Descriptor.InstructionOpcode == rcc::opcode(rcc::GenericOpcode::INLINEASM);
+ });
+ const auto InlineAsmBr = std::ranges::find_if(Descriptors, [](const auto &Descriptor) {
+ return Descriptor.InstructionOpcode == rcc::opcode(rcc::GenericOpcode::INLINEASM_BR);
+ });
+ Context.expect(InlineAsm != Descriptors.end() && InlineAsmBr != Descriptors.end() &&
+ !InlineAsm->Flags.has(rcc::InstructionFlag::HasSideEffects) &&
+ InlineAsmBr->Flags.has(rcc::InstructionFlag::HasSideEffects),
+ "INLINEASM_BR must be intrinsically side-effecting while INLINEASM uses its "
+ "operand flags");
+}
+
+void testShapesAndTypes(TestContext &Context) {
+ auto Set = rcc::InstructionSet::create(copyDescriptors());
+ auto Function =
+ rcc::MachineFunction::create("generic", rcc::MachineFunctionStableID::fromValue(1));
+ Context.expect(Set.has_value() && Function.has_value(),
+ "generic opcode tests require a descriptor set and machine function");
+ if (!Set || !Function)
+ return;
+
+ auto I32 = rcc::LLT::makeInteger(32);
+ auto I64 = rcc::LLT::makeInteger(64);
+ auto I1 = rcc::LLT::makeInteger(1);
+ auto F32 = rcc::LLT::makeFloat(rcc::FloatSemantics::IEEESingle);
+ auto F64 = rcc::LLT::makeFloat(rcc::FloatSemantics::IEEEDouble);
+ auto P64 = rcc::LLT::makePointer(0, 64);
+ auto V2I32 = I32 ? rcc::LLT::makeFixedVector(2, *I32) : rcc::Expected<rcc::LLT>{};
+ auto V4I32 = I32 ? rcc::LLT::makeFixedVector(4, *I32) : rcc::Expected<rcc::LLT>{};
+ auto V2P64 = P64 ? rcc::LLT::makeFixedVector(2, *P64) : rcc::Expected<rcc::LLT>{};
+ if (!I1 || !I32 || !I64 || !F32 || !F64 || !P64 || !V2I32 || !V4I32 || !V2P64)
+ std::terminate();
+ auto Dst32 = (*Function)->registerInfo().createVirtualRegister(*I32);
+ auto Src32A = (*Function)->registerInfo().createVirtualRegister(*I32);
+ auto Src32B = (*Function)->registerInfo().createVirtualRegister(*I32);
+ auto Wide = (*Function)->registerInfo().createVirtualRegister(*I64);
+ auto Address = (*Function)->registerInfo().createVirtualRegister(*P64);
+ auto NewAddress = (*Function)->registerInfo().createVirtualRegister(*P64);
+ const rcc::Register Cond = createVirtualRegister(**Function, *I1);
+ const rcc::Register Float32 = createVirtualRegister(**Function, *F32);
+ const rcc::Register Float64 = createVirtualRegister(**Function, *F64);
+ const rcc::Register Vec2A = createVirtualRegister(**Function, *V2I32);
+ const rcc::Register Vec2B = createVirtualRegister(**Function, *V2I32);
+ const rcc::Register Vec4 = createVirtualRegister(**Function, *V4I32);
+ const rcc::Register PointerVector = createVirtualRegister(**Function, *V2P64);
+ if (!Dst32 || !Src32A || !Src32B || !Wide || !Address || !NewAddress)
+ std::terminate();
+
+ std::vector<rcc::MachineOperand> AddOperands;
+ AddOperands.push_back(registerOperand(*Dst32, true));
+ AddOperands.push_back(registerOperand(*Src32A, false));
+ AddOperands.push_back(registerOperand(*Src32B, false));
+ Context.expect(Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_ADD), AddOperands,
+ (*Function)->registerInfo())
+ .has_value(),
+ "same-typed integer operands must satisfy G_ADD");
+ AddOperands.back() = registerOperand(*Wide, false);
+ Context.expect(!Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_ADD), AddOperands,
+ (*Function)->registerInfo()),
+ "G_ADD must reject a source from a different LLT equality group");
+
+ auto Block0 = (*Function)->createBasicBlock();
+ auto Block1 = (*Function)->createBasicBlock();
+ if (!Block0 || !Block1 || !(*Function)->appendBlock(**Block0) ||
+ !(*Function)->appendBlock(**Block1)) {
+ std::terminate();
+ }
+ std::vector<rcc::MachineOperand> PhiOperands;
+ PhiOperands.push_back(registerOperand(*Dst32, true));
+ Context.expect(!Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_PHI), PhiOperands,
+ (*Function)->registerInfo()),
+ "G_PHI must require at least one value and predecessor pair");
+ PhiOperands.push_back(registerOperand(*Src32A, false));
+ PhiOperands.push_back(rcc::MachineOperand::basicBlock(**Block0));
+ PhiOperands.push_back(registerOperand(*Src32B, false));
+ PhiOperands.push_back(rcc::MachineOperand::basicBlock(**Block1));
+ Context.expect(Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_PHI), PhiOperands,
+ (*Function)->registerInfo())
+ .has_value(),
+ "G_PHI must accept repeated value and predecessor pairs");
+ PhiOperands.pop_back();
+ Context.expect(!Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_PHI), PhiOperands,
+ (*Function)->registerInfo()),
+ "G_PHI must reject an incomplete variadic operand pair");
+
+ std::vector<rcc::MachineOperand> IndexedLoad;
+ IndexedLoad.push_back(registerOperand(*Dst32, true));
+ IndexedLoad.push_back(registerOperand(*NewAddress, true));
+ IndexedLoad.push_back(registerOperand(*Address, false));
+ IndexedLoad.push_back(registerOperand(*Wide, false));
+ IndexedLoad.push_back(rcc::MachineOperand::immediate(1));
+ Context.expect(Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_INDEXED_LOAD),
+ IndexedLoad, (*Function)->registerInfo())
+ .has_value(),
+ "indexed loads must keep address and integer offset type groups independent");
+
+ std::vector<rcc::MachineOperand> UnmergeOperands;
+ UnmergeOperands.push_back(registerOperand(*Dst32, true));
+ UnmergeOperands.push_back(registerOperand(*Wide, false));
+ Context.expect(!Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_UNMERGE_VALUES),
+ UnmergeOperands, (*Function)->registerInfo()),
+ "G_UNMERGE_VALUES must require at least two definitions");
+ UnmergeOperands.insert(UnmergeOperands.begin() + 1, registerOperand(*Src32B, true));
+ Context.expect(Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_UNMERGE_VALUES),
+ UnmergeOperands, (*Function)->registerInfo())
+ .has_value(),
+ "two homogeneous halves must exactly cover a G_UNMERGE_VALUES source");
+
+ std::vector<rcc::MachineOperand> MergeOperands;
+ MergeOperands.push_back(registerOperand(*Wide, true));
+ MergeOperands.push_back(registerOperand(*Src32A, false));
+ Context.expect(!Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_MERGE_VALUES),
+ MergeOperands, (*Function)->registerInfo()),
+ "G_MERGE_VALUES must require at least two sources");
+ MergeOperands.push_back(registerOperand(*Src32B, false));
+ Context.expect(Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_MERGE_VALUES),
+ MergeOperands, (*Function)->registerInfo())
+ .has_value(),
+ "two homogeneous sources must exactly fill a G_MERGE_VALUES result");
+
+ std::vector<rcc::MachineOperand> ConcatOperands;
+ ConcatOperands.push_back(registerOperand(Vec4, true));
+ ConcatOperands.push_back(registerOperand(Vec2A, false));
+ Context.expect(!Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_CONCAT_VECTORS),
+ ConcatOperands, (*Function)->registerInfo()),
+ "G_CONCAT_VECTORS must require at least two source vectors");
+ ConcatOperands.push_back(registerOperand(Vec2B, false));
+ Context.expect(Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_CONCAT_VECTORS),
+ ConcatOperands, (*Function)->registerInfo())
+ .has_value(),
+ "two vector sources must exactly tile a G_CONCAT_VECTORS result");
+
+ std::vector<rcc::MachineOperand> FrameIndexOperands;
+ FrameIndexOperands.push_back(registerOperand(PointerVector, true));
+ FrameIndexOperands.push_back(rcc::MachineOperand::frameIndex(rcc::FrameIndex::fromValue(0)));
+ Context.expect(!Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_FRAME_INDEX),
+ FrameIndexOperands, (*Function)->registerInfo()),
+ "G_FRAME_INDEX must reject a vector-of-pointers result");
+
+ std::vector<rcc::MachineOperand> PtrAuthOperands;
+ PtrAuthOperands.push_back(registerOperand(*NewAddress, true));
+ PtrAuthOperands.push_back(registerOperand(*Address, false));
+ PtrAuthOperands.push_back(rcc::MachineOperand::immediate(0));
+ PtrAuthOperands.push_back(registerOperand(*Wide, false));
+ PtrAuthOperands.push_back(rcc::MachineOperand::immediate(7));
+ Context.expect(
+ Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_PTRAUTH_GLOBAL_VALUE),
+ PtrAuthOperands, (*Function)->registerInfo())
+ .has_value(),
+ "G_PTRAUTH_GLOBAL_VALUE must accept pointer and discriminator registers");
+ auto Symbol = rcc::MachineOperand::globalSymbol("global");
+ if (!Symbol)
+ std::terminate();
+ PtrAuthOperands[1] = std::move(*Symbol);
+ Context.expect(
+ !Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_PTRAUTH_GLOBAL_VALUE),
+ PtrAuthOperands, (*Function)->registerInfo()),
+ "G_PTRAUTH_GLOBAL_VALUE must reject a symbol in place of its address register");
+
+ std::vector<rcc::MachineOperand> ExtensionOperands;
+ ExtensionOperands.push_back(registerOperand(*Wide, true));
+ ExtensionOperands.push_back(registerOperand(*Src32A, false));
+ Context.expect(Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_ZEXT),
+ ExtensionOperands, (*Function)->registerInfo())
+ .has_value(),
+ "G_ZEXT must accept a wider destination with matching shape");
+ ExtensionOperands[0] = registerOperand(*Dst32, true);
+ ExtensionOperands[1] = registerOperand(*Wide, false);
+ Context.expect(!Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_ZEXT),
+ ExtensionOperands, (*Function)->registerInfo()),
+ "G_ZEXT must reject a narrower destination");
+
+ auto Predicate = rcc::MachineOperand::integerPredicate(rcc::IntegerPredicate::Equal);
+ if (!Predicate)
+ std::terminate();
+ std::vector<rcc::MachineOperand> CompareOperands;
+ CompareOperands.push_back(registerOperand(Cond, true));
+ CompareOperands.push_back(std::move(*Predicate));
+ CompareOperands.push_back(registerOperand(*Src32A, false));
+ CompareOperands.push_back(registerOperand(*Src32B, false));
+ Context.expect(Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_ICMP),
+ CompareOperands, (*Function)->registerInfo())
+ .has_value(),
+ "G_ICMP must accept an i1 result and homogeneous integer sources");
+ CompareOperands[0] = registerOperand(*Dst32, true);
+ Context.expect(!Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_ICMP),
+ CompareOperands, (*Function)->registerInfo()),
+ "G_ICMP must reject a non-i1 result");
+
+ std::vector<rcc::MachineOperand> FloatingLoad;
+ FloatingLoad.push_back(registerOperand(Float64, true));
+ FloatingLoad.push_back(registerOperand(*Address, false));
+ Context.expect(Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_FPEXTLOAD),
+ FloatingLoad, (*Function)->registerInfo())
+ .has_value(),
+ "G_FPEXTLOAD must accept a scalar floating result and pointer address");
+ FloatingLoad[0] = registerOperand(*Wide, true);
+ Context.expect(!Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_FPEXTLOAD),
+ FloatingLoad, (*Function)->registerInfo()),
+ "G_FPEXTLOAD must reject an integer result");
+
+ const std::array<std::uint64_t, 1> FloatWords{0};
+ auto FloatBits = rcc::FloatingPointBits::create(rcc::FloatSemantics::IEEESingle, FloatWords);
+ if (!FloatBits)
+ std::terminate();
+ std::vector<rcc::MachineOperand> FConstantOperands;
+ FConstantOperands.push_back(registerOperand(Float32, true));
+ FConstantOperands.push_back(rcc::MachineOperand::floatingImmediate(*FloatBits));
+ Context.expect(Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_FCONSTANT),
+ FConstantOperands, (*Function)->registerInfo())
+ .has_value(),
+ "G_FCONSTANT payload semantics must match its scalar destination");
+ FConstantOperands[0] = registerOperand(Float64, true);
+ Context.expect(!Set->validateExplicitOperands(rcc::opcode(rcc::GenericOpcode::G_FCONSTANT),
+ FConstantOperands, (*Function)->registerInfo()),
+ "G_FCONSTANT must reject mismatched floating semantics");
+}
+
+void testInstructionMemorySemantics(TestContext &Context) {
+ auto Set = rcc::InstructionSet::create(copyDescriptors());
+ auto Function =
+ rcc::MachineFunction::create("generic_memory", rcc::MachineFunctionStableID::fromValue(2));
+ Context.expect(Set && Function, "generic memory tests require an instruction set and function");
+ if (!Set || !Function)
+ return;
+
+ const rcc::LLT I1 = *rcc::LLT::makeInteger(1);
+ const rcc::LLT I32 = *rcc::LLT::makeInteger(32);
+ const rcc::LLT I64 = *rcc::LLT::makeInteger(64);
+ const rcc::LLT F64 = *rcc::LLT::makeFloat(rcc::FloatSemantics::IEEEDouble);
+ const rcc::LLT P0 = *rcc::LLT::makePointer(0, 64);
+ const rcc::LLT P1 = *rcc::LLT::makePointer(1, 64);
+ const rcc::Register Dst32 = createVirtualRegister(**Function, I32);
+ const rcc::Register Dst64 = createVirtualRegister(**Function, I64);
+ const rcc::Register Src64 = createVirtualRegister(**Function, I64);
+ const rcc::Register Float64 = createVirtualRegister(**Function, F64);
+ const rcc::Register Status = createVirtualRegister(**Function, I1);
+ const rcc::Register Address0 = createVirtualRegister(**Function, P0);
+ const rcc::Register Address1 = createVirtualRegister(**Function, P1);
+
+ std::vector<rcc::MachineOperand> LoadOperands;
+ LoadOperands.push_back(registerOperand(Dst64, true));
+ LoadOperands.push_back(registerOperand(Address0, false));
+ rcc::MachineInstr &Load =
+ createInstruction(**Function, rcc::GenericOpcode::G_LOAD, std::move(LoadOperands));
+ addMemOperand(**Function, Load, memoryDescription({rcc::MemoryAccessFlag::Load}, 8));
+ Context.expect(Set->validateInstruction(Load, (*Function)->registerInfo()).has_value(),
+ "G_LOAD must accept one matching load MMO");
+
+ std::vector<rcc::MachineOperand> OversizedLoadOperands;
+ OversizedLoadOperands.push_back(registerOperand(Dst32, true));
+ OversizedLoadOperands.push_back(registerOperand(Address0, false));
+ rcc::MachineInstr &OversizedLoad =
+ createInstruction(**Function, rcc::GenericOpcode::G_LOAD, std::move(OversizedLoadOperands));
+ addMemOperand(**Function, OversizedLoad, memoryDescription({rcc::MemoryAccessFlag::Load}, 8));
+ Context.expect(!Set->validateInstruction(OversizedLoad, (*Function)->registerInfo()),
+ "G_LOAD must reject an MMO larger than its result LLT");
+
+ std::vector<rcc::MachineOperand> WrongAddressSpaceOperands;
+ WrongAddressSpaceOperands.push_back(registerOperand(Dst64, true));
+ WrongAddressSpaceOperands.push_back(registerOperand(Address1, false));
+ rcc::MachineInstr &WrongAddressSpace = createInstruction(**Function, rcc::GenericOpcode::G_LOAD,
+ std::move(WrongAddressSpaceOperands));
+ addMemOperand(**Function, WrongAddressSpace,
+ memoryDescription({rcc::MemoryAccessFlag::Load}, 8, 0));
+ Context.expect(!Set->validateInstruction(WrongAddressSpace, (*Function)->registerInfo()),
+ "generic memory access must match its pointer and MMO address spaces");
+
+ std::vector<rcc::MachineOperand> ExtLoadOperands;
+ ExtLoadOperands.push_back(registerOperand(Dst64, true));
+ ExtLoadOperands.push_back(registerOperand(Address0, false));
+ rcc::MachineInstr &ExtLoad =
+ createInstruction(**Function, rcc::GenericOpcode::G_ZEXTLOAD, std::move(ExtLoadOperands));
+ addMemOperand(**Function, ExtLoad, memoryDescription({rcc::MemoryAccessFlag::Load}, 4));
+ Context.expect(Set->validateInstruction(ExtLoad, (*Function)->registerInfo()).has_value(),
+ "G_ZEXTLOAD must accept a strictly narrower memory access");
+
+ std::vector<rcc::MachineOperand> NonExtendingLoadOperands;
+ NonExtendingLoadOperands.push_back(registerOperand(Dst64, true));
+ NonExtendingLoadOperands.push_back(registerOperand(Address0, false));
+ rcc::MachineInstr &NonExtendingLoad = createInstruction(
+ **Function, rcc::GenericOpcode::G_ZEXTLOAD, std::move(NonExtendingLoadOperands));
+ addMemOperand(**Function, NonExtendingLoad, memoryDescription({rcc::MemoryAccessFlag::Load}, 8));
+ Context.expect(!Set->validateInstruction(NonExtendingLoad, (*Function)->registerInfo()),
+ "G_ZEXTLOAD must reject a non-narrowing MMO");
+
+ std::vector<rcc::MachineOperand> CompareExchangeOperands;
+ CompareExchangeOperands.push_back(registerOperand(Dst64, true));
+ CompareExchangeOperands.push_back(registerOperand(Status, true));
+ CompareExchangeOperands.push_back(registerOperand(Address0, false));
+ CompareExchangeOperands.push_back(registerOperand(Src64, false));
+ CompareExchangeOperands.push_back(registerOperand(Src64, false));
+ rcc::MachineInstr &CompareExchange =
+ createInstruction(**Function, rcc::GenericOpcode::G_ATOMIC_CMPXCHG_WITH_SUCCESS,
+ std::move(CompareExchangeOperands));
+ addMemOperand(**Function, CompareExchange,
+ memoryDescription({rcc::MemoryAccessFlag::Load, rcc::MemoryAccessFlag::Store}, 8, 0,
+ rcc::AtomicOperation::CompareExchange));
+ Context.expect(Set->validateInstruction(CompareExchange, (*Function)->registerInfo()).has_value(),
+ "generic compare-exchange must accept a matching atomic MMO");
+
+ std::vector<rcc::MachineOperand> WrongAtomicOperands;
+ WrongAtomicOperands.push_back(registerOperand(Dst64, true));
+ WrongAtomicOperands.push_back(registerOperand(Address0, false));
+ WrongAtomicOperands.push_back(registerOperand(Src64, false));
+ rcc::MachineInstr &WrongAtomic = createInstruction(
+ **Function, rcc::GenericOpcode::G_ATOMICRMW_ADD, std::move(WrongAtomicOperands));
+ addMemOperand(**Function, WrongAtomic,
+ memoryDescription({rcc::MemoryAccessFlag::Load, rcc::MemoryAccessFlag::Store}, 8, 0,
+ rcc::AtomicOperation::CompareExchange));
+ Context.expect(!Set->validateInstruction(WrongAtomic, (*Function)->registerInfo()),
+ "generic atomic RMW must reject a compare-exchange MMO");
+
+ std::vector<rcc::MachineOperand> FloatingAtomicOperands;
+ FloatingAtomicOperands.push_back(registerOperand(Float64, true));
+ FloatingAtomicOperands.push_back(registerOperand(Address0, false));
+ FloatingAtomicOperands.push_back(registerOperand(Float64, false));
+ rcc::MachineInstr &FloatingAtomic = createInstruction(
+ **Function, rcc::GenericOpcode::G_ATOMICRMW_FADD, std::move(FloatingAtomicOperands));
+ addMemOperand(**Function, FloatingAtomic,
+ memoryDescription({rcc::MemoryAccessFlag::Load, rcc::MemoryAccessFlag::Store}, 8, 0,
+ rcc::AtomicOperation::ReadModifyWrite));
+ Context.expect(Set->validateInstruction(FloatingAtomic, (*Function)->registerInfo()).has_value(),
+ "floating atomic RMW must accept a scalar floating value and RMW MMO");
+
+ std::vector<rcc::MachineOperand> WrongFloatingAtomicOperands;
+ WrongFloatingAtomicOperands.push_back(registerOperand(Dst64, true));
+ WrongFloatingAtomicOperands.push_back(registerOperand(Address0, false));
+ WrongFloatingAtomicOperands.push_back(registerOperand(Src64, false));
+ rcc::MachineInstr &WrongFloatingAtomic = createInstruction(
+ **Function, rcc::GenericOpcode::G_ATOMICRMW_FADD, std::move(WrongFloatingAtomicOperands));
+ addMemOperand(**Function, WrongFloatingAtomic,
+ memoryDescription({rcc::MemoryAccessFlag::Load, rcc::MemoryAccessFlag::Store}, 8, 0,
+ rcc::AtomicOperation::ReadModifyWrite));
+ Context.expect(!Set->validateInstruction(WrongFloatingAtomic, (*Function)->registerInfo()),
+ "floating atomic RMW must reject integer value registers");
+
+ std::vector<rcc::MachineOperand> PrefetchOperands;
+ PrefetchOperands.push_back(registerOperand(Address0, false));
+ PrefetchOperands.push_back(rcc::MachineOperand::immediate(0));
+ PrefetchOperands.push_back(rcc::MachineOperand::immediate(3));
+ PrefetchOperands.push_back(rcc::MachineOperand::immediate(1));
+ rcc::MachineInstr &Prefetch =
+ createInstruction(**Function, rcc::GenericOpcode::G_PREFETCH, std::move(PrefetchOperands));
+ Context.expect(!Set->validateInstruction(Prefetch, (*Function)->registerInfo()),
+ "G_PREFETCH must require an MMO");
+ addMemOperand(**Function, Prefetch, memoryDescription({rcc::MemoryAccessFlag::Load}, 1));
+ Context.expect(Set->validateInstruction(Prefetch, (*Function)->registerInfo()).has_value(),
+ "read-prefetch selector must match a load-only MMO");
+
+ std::vector<rcc::MachineOperand> VarArgOperands;
+ VarArgOperands.push_back(registerOperand(Dst64, true));
+ VarArgOperands.push_back(registerOperand(Address0, false));
+ VarArgOperands.push_back(rcc::MachineOperand::immediate(8));
+ rcc::MachineInstr &VarArg =
+ createInstruction(**Function, rcc::GenericOpcode::G_VAARG, std::move(VarArgOperands));
+ addMemOperand(**Function, VarArg, memoryDescription({rcc::MemoryAccessFlag::Load}, 8));
+ addMemOperand(**Function, VarArg, memoryDescription({rcc::MemoryAccessFlag::Store}, 8));
+ Context.expect(Set->validateInstruction(VarArg, (*Function)->registerInfo()).has_value(),
+ "G_VAARG must accept separate value-load and list-update MMOs");
+
+ std::vector<rcc::MachineOperand> CombinedVarArgOperands;
+ CombinedVarArgOperands.push_back(registerOperand(Dst64, true));
+ CombinedVarArgOperands.push_back(registerOperand(Address0, false));
+ CombinedVarArgOperands.push_back(rcc::MachineOperand::immediate(8));
+ rcc::MachineInstr &CombinedVarArg =
+ createInstruction(**Function, rcc::GenericOpcode::G_VAARG, std::move(CombinedVarArgOperands));
+ addMemOperand(**Function, CombinedVarArg,
+ memoryDescription({rcc::MemoryAccessFlag::Load, rcc::MemoryAccessFlag::Store}, 8));
+ Context.expect(!Set->validateInstruction(CombinedVarArg, (*Function)->registerInfo()),
+ "G_VAARG must reject a combined read-write MMO in place of two accesses");
+
+ std::vector<rcc::MachineOperand> MemoryTransferOperands;
+ MemoryTransferOperands.push_back(registerOperand(Address0, false));
+ MemoryTransferOperands.push_back(registerOperand(Address1, false));
+ MemoryTransferOperands.push_back(registerOperand(Src64, false));
+ MemoryTransferOperands.push_back(rcc::MachineOperand::immediate(0));
+ rcc::MachineInstr &MemoryTransfer = createInstruction(**Function, rcc::GenericOpcode::G_MEMCPY,
+ std::move(MemoryTransferOperands));
+ addMemOperand(**Function, MemoryTransfer,
+ memoryDescription({rcc::MemoryAccessFlag::Store}, 8, 0));
+ addMemOperand(**Function, MemoryTransfer, memoryDescription({rcc::MemoryAccessFlag::Load}, 8, 1));
+ Context.expect(Set->validateInstruction(MemoryTransfer, (*Function)->registerInfo()).has_value(),
+ "G_MEMCPY must accept equal-sized store/load MMOs in canonical order");
+
+ std::vector<rcc::MachineOperand> ReversedTransferOperands;
+ ReversedTransferOperands.push_back(registerOperand(Address0, false));
+ ReversedTransferOperands.push_back(registerOperand(Address1, false));
+ ReversedTransferOperands.push_back(registerOperand(Src64, false));
+ ReversedTransferOperands.push_back(rcc::MachineOperand::immediate(0));
+ rcc::MachineInstr &ReversedTransfer = createInstruction(**Function, rcc::GenericOpcode::G_MEMCPY,
+ std::move(ReversedTransferOperands));
+ addMemOperand(**Function, ReversedTransfer,
+ memoryDescription({rcc::MemoryAccessFlag::Load}, 8, 0));
+ addMemOperand(**Function, ReversedTransfer,
+ memoryDescription({rcc::MemoryAccessFlag::Store}, 8, 1));
+ Context.expect(!Set->validateInstruction(ReversedTransfer, (*Function)->registerInfo()),
+ "G_MEMCPY must reject reversed MMO access directions");
+}
+
+void testSemanticFlags(TestContext &Context) {
+ const auto Descriptors = rcc::genericInstructionDescriptors();
+ const auto Find = [Descriptors](rcc::GenericOpcode OpcodeValue) {
+ const rcc::Opcode Value = rcc::opcode(OpcodeValue);
+ const auto Position =
+ std::ranges::find_if(Descriptors, [Value](const rcc::InstructionDescriptor &Descriptor) {
+ return Descriptor.InstructionOpcode == Value;
+ });
+ return Position == Descriptors.end() ? nullptr : &*Position;
+ };
+
+ const auto *Load = Find(rcc::GenericOpcode::G_LOAD);
+ const auto *Atomic = Find(rcc::GenericOpcode::G_ATOMIC_CMPXCHG);
+ const auto *Branch = Find(rcc::GenericOpcode::G_BRINDIRECT);
+ const auto *Strict = Find(rcc::GenericOpcode::G_STRICT_FMA);
+ const auto *InlineAsm = Find(rcc::GenericOpcode::INLINEASM);
+ Context.expect(
+ Load != nullptr && Load->Flags.has(rcc::InstructionFlag::MayLoad) &&
+ Load->RequiresMemoryOperands && Atomic != nullptr &&
+ Atomic->Flags.has(rcc::InstructionFlag::MayLoad) &&
+ Atomic->Flags.has(rcc::InstructionFlag::MayStore) && Atomic->RequiresMemoryOperands &&
+ Branch != nullptr && Branch->Flags.has(rcc::InstructionFlag::Terminator) &&
+ Branch->Flags.has(rcc::InstructionFlag::IndirectBranch) &&
+ Branch->Flags.has(rcc::InstructionFlag::Barrier) && Strict != nullptr &&
+ Strict->Flags.has(rcc::InstructionFlag::HasSideEffects) && InlineAsm != nullptr &&
+ InlineAsm->Flags.has(rcc::InstructionFlag::TerminalEmission) &&
+ InlineAsm->Owner == rcc::PseudoOwner::AsmPrinter,
+ "memory, control-flow, constrained-FP, and terminal flags must be explicit");
+}
+
+} // namespace
+
+int main() {
+ TestContext Context;
+ testCompleteRegistry(Context);
+ testCoreOpcodeContracts(Context);
+ testShapesAndTypes(Context);
+ testInstructionMemorySemantics(Context);
+ testSemanticFlags(Context);
+ return Context.result();
+}
diff --git a/backend/unittests/CodeGen/MachineAnalysesTest.cpp b/backend/unittests/CodeGen/MachineAnalysesTest.cpp
new file mode 100644
index 0000000..23c5e19
--- /dev/null
+++ b/backend/unittests/CodeGen/MachineAnalysesTest.cpp
@@ -0,0 +1,477 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/MachineAnalyses.h"
+
+#include "Target/TestTarget.h"
+#include "rcc/CodeGen/GenericOpcodes.h"
+#include "rcc/CodeGen/MachineCFGUpdater.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineIRBuilder.h"
+#include "rcc/CodeGen/MachineInstr.h"
+#include "rcc/CodeGen/MachineMemOperand.h"
+#include "rcc/CodeGen/MachineVerifier.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <iostream>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace {
+
+class TestContext final {
+public:
+ void expect(bool Condition, std::string_view Message) {
+ if (Condition)
+ return;
+ std::cerr << Message << '\n';
+ ++Failures;
+ }
+
+ [[nodiscard]] int result() const noexcept {
+ return Failures == 0 ? 0 : 1;
+ }
+
+private:
+ int Failures = 0;
+};
+
+[[nodiscard]] rcc::Expected<rcc::TargetDescription> createTarget() {
+ std::vector<rcc::InstructionDescriptor> Descriptors;
+ std::vector<rcc::ImmediatePredicate> Predicates;
+ auto Appended = rcc::appendGenericInstructionInfo(Descriptors, Predicates);
+ if (!Appended)
+ return std::unexpected(Appended.error());
+ auto Instructions = rcc::InstructionSet::create(std::move(Descriptors), std::move(Predicates));
+ if (!Instructions)
+ return std::unexpected(Instructions.error());
+ return rcc::test::createTestTargetDescription(std::move(*Instructions));
+}
+
+[[nodiscard]] rcc::Expected<rcc::MachineBasicBlock *> appendBlock(rcc::MachineFunction &Function) {
+ auto Block = Function.createBasicBlock();
+ if (!Block)
+ return std::unexpected(Block.error());
+ auto Appended = Function.appendBlock(**Block);
+ if (!Appended)
+ return std::unexpected(Appended.error());
+ return *Block;
+}
+
+void testCFGAnalyses(TestContext &Context, const rcc::TargetDescription &Target) {
+ auto Function = rcc::MachineFunction::create("cfg", rcc::MachineFunctionStableID::fromValue(1));
+ Context.expect(Function.has_value(), "CFG analysis function creation must succeed");
+ if (!Function)
+ return;
+
+ auto Entry = appendBlock(**Function);
+ auto Header = appendBlock(**Function);
+ auto Body = appendBlock(**Function);
+ auto Exit = appendBlock(**Function);
+ auto Dead = appendBlock(**Function);
+ Context.expect(Entry && Header && Body && Exit && Dead,
+ "CFG analysis blocks must be appended successfully");
+ if (!Entry || !Header || !Body || !Exit || !Dead)
+ return;
+
+ auto ThreeQuarters = rcc::BranchProbability::fromFraction(3, 4);
+ auto OneQuarter = rcc::BranchProbability::fromFraction(1, 4);
+ auto Updater = (*Function)->cfgUpdater();
+ Context.expect(ThreeQuarters && OneQuarter &&
+ Updater.addSuccessor(**Entry, **Header, rcc::BranchProbability::one()) &&
+ Updater.addSuccessor(**Header, **Body, *ThreeQuarters) &&
+ Updater.addSuccessor(**Header, **Exit, *OneQuarter) &&
+ Updater.addSuccessor(**Body, **Header, rcc::BranchProbability::one()),
+ "reducible loop CFG construction must succeed");
+
+ rcc::MachineAnalysisManager Analyses;
+ Context.expect(rcc::registerStage8MachineAnalyses(Analyses).has_value(),
+ "standard Stage 8 analyses must register atomically");
+ auto Dominators = Analyses.getResult<rcc::MachineDominatorTreeAnalysis>(**Function, Target);
+ auto Loops = Analyses.getResult<rcc::MachineLoopAnalysis>(**Function, Target);
+ auto Probabilities =
+ Analyses.getResult<rcc::MachineBranchProbabilityAnalysis>(**Function, Target);
+ auto Frequencies = Analyses.getResult<rcc::MachineBlockFrequencyAnalysis>(**Function, Target);
+ Context.expect(Dominators && Loops && Probabilities && Frequencies,
+ "CFG analyses and their dependencies must materialize");
+ if (!Dominators || !Loops || !Probabilities || !Frequencies)
+ return;
+
+ Context.expect((*Dominators)->dominates(**Entry, **Exit) &&
+ (*Dominators)->dominates(**Header, **Body) &&
+ (*Dominators)->immediateDominator(**Body) == *Header &&
+ (*Dominators)->nearestCommonDominator(**Body, **Exit) == *Header &&
+ !(*Dominators)->isReachable(**Dead),
+ "dominator analysis must model reachability, IDom, and common dominators");
+ Context.expect((*Loops)->loops().size() == 1 && (*Loops)->loopFor(**Header) != nullptr &&
+ (*Loops)->loopFor(**Body) != nullptr && (*Loops)->loopDepth(**Body) == 1,
+ "loop analysis must recover the natural loop and loop depth");
+ Context.expect((*Probabilities)->probability(**Header, **Body) == *ThreeQuarters &&
+ (*Probabilities)->probability(**Header, **Exit) == *OneQuarter,
+ "branch-probability analysis must retain exact integer edge probabilities");
+ Context.expect((*Frequencies)->frequency(**Entry) != 0 &&
+ (*Frequencies)->frequency(**Header) > (*Frequencies)->frequency(**Entry) &&
+ (*Frequencies)->frequency(**Dead) == 0,
+ "block-frequency analysis must distinguish loop and unreachable blocks");
+
+ auto Alias = Analyses.getResult<rcc::MachineAliasAnalysis>(**Function, Target);
+ auto MemoryDependence =
+ Analyses.getResult<rcc::MachineMemoryDependenceAnalysis>(**Function, Target);
+ Context.expect(Alias && MemoryDependence && Analyses.cachedResultCount(**Function) == 6,
+ "the complete Stage 8 analysis set must materialize into one function cache");
+ Analyses.invalidate(**Function, rcc::PreservedAnalyses::none(),
+ {rcc::MachinePassChange::Registers});
+ Context.expect(Analyses.cachedResultCount(**Function) == 6,
+ "register-only changes must preserve CFG and memory analyses");
+ Analyses.invalidate(**Function, rcc::PreservedAnalyses::none(), {rcc::MachinePassChange::CFG});
+ Context.expect(
+ Analyses.cachedResultCount(**Function) == 1 &&
+ Analyses.hasCached(**Function,
+ rcc::MachineAnalysisManager::id<rcc::MachineAliasAnalysis>()),
+ "CFG changes must invalidate CFG dependents and memory dependence but preserve aliasing");
+
+ auto RefreshedDominators =
+ Analyses.getResult<rcc::MachineDominatorTreeAnalysis>(**Function, Target);
+ Context.expect(
+ RefreshedDominators &&
+ Updater.addSuccessor(**Entry, **Exit, rcc::BranchProbability::zero()).has_value() &&
+ (*RefreshedDominators)->updateAfterCFGChange(**Function).has_value() &&
+ (*RefreshedDominators)->immediateDominator(**Exit) == *Entry,
+ "dominator result must support exact updates after CFG edge changes");
+}
+
+void testIrreducibleCFG(TestContext &Context, const rcc::TargetDescription &Target) {
+ auto Function =
+ rcc::MachineFunction::create("irreducible", rcc::MachineFunctionStableID::fromValue(2));
+ if (!Function) {
+ Context.expect(false, "irreducible CFG function creation must succeed");
+ return;
+ }
+ auto Entry = appendBlock(**Function);
+ auto Left = appendBlock(**Function);
+ auto Right = appendBlock(**Function);
+ auto Exit = appendBlock(**Function);
+ if (!Entry || !Left || !Right || !Exit) {
+ Context.expect(false, "irreducible CFG blocks must be appended");
+ return;
+ }
+ auto Half = rcc::BranchProbability::fromFraction(1, 2);
+ auto Updater = (*Function)->cfgUpdater();
+ Context.expect(Half && Updater.addSuccessor(**Entry, **Left, *Half) &&
+ Updater.addSuccessor(**Entry, **Right, *Half) &&
+ Updater.addSuccessor(**Left, **Right, *Half) &&
+ Updater.addSuccessor(**Left, **Exit, *Half) &&
+ Updater.addSuccessor(**Right, **Left, *Half) &&
+ Updater.addSuccessor(**Right, **Exit, *Half),
+ "irreducible CFG construction must succeed");
+
+ rcc::MachineAnalysisManager Analyses;
+ auto Registered = rcc::registerStage8MachineAnalyses(Analyses);
+ auto Loops =
+ Registered
+ ? Analyses.getResult<rcc::MachineLoopAnalysis>(**Function, Target)
+ : rcc::Expected<rcc::MachineLoopAnalysis::Result *>{std::unexpected(Registered.error())};
+ Context.expect(Loops && (*Loops)->irreducibleRegions().size() == 1 &&
+ (*Loops)->irreducibleRegions().front().Entries.size() == 2 &&
+ (*Loops)->isIrreducible(**Left) && (*Loops)->isIrreducible(**Right),
+ "loop analysis must explicitly identify multi-entry irreducible regions");
+}
+
+[[nodiscard]] rcc::MachineMemOperandDesc
+memoryDescription(rcc::MemoryBase Base, std::int64_t Offset, rcc::MemoryAccessFlag Access) {
+ rcc::MachineMemOperandDesc Description;
+ Description.Accesses = {Access};
+ Description.Size = *rcc::MemorySize::fixed(8);
+ Description.Align = *rcc::Alignment::create(8);
+ Description.Base = std::move(Base);
+ Description.Offset = Offset;
+ return Description;
+}
+
+void testAliasAnalysis(TestContext &Context) {
+ auto First = rcc::MachineMemOperand::create(memoryDescription(
+ rcc::MemoryBase::frameIndex(rcc::FrameIndex::fromValue(0)), 0, rcc::MemoryAccessFlag::Load));
+ auto Same = rcc::MachineMemOperand::create(memoryDescription(
+ rcc::MemoryBase::frameIndex(rcc::FrameIndex::fromValue(0)), 0, rcc::MemoryAccessFlag::Store));
+ auto Partial = rcc::MachineMemOperand::create(memoryDescription(
+ rcc::MemoryBase::frameIndex(rcc::FrameIndex::fromValue(0)), 4, rcc::MemoryAccessFlag::Store));
+ auto DisjointRange = rcc::MachineMemOperand::create(
+ memoryDescription(rcc::MemoryBase::frameIndex(rcc::FrameIndex::fromValue(0)), 16,
+ rcc::MemoryAccessFlag::Store));
+ auto DistinctObject = rcc::MachineMemOperand::create(memoryDescription(
+ rcc::MemoryBase::frameIndex(rcc::FrameIndex::fromValue(1)), 0, rcc::MemoryAccessFlag::Store));
+ auto DifferentObjectKind = rcc::MachineMemOperand::create(
+ memoryDescription(rcc::MemoryBase::constantPool(rcc::ConstantPoolIndex::fromValue(0)), 0,
+ rcc::MemoryAccessFlag::Store));
+ Context.expect(First && Same && Partial && DisjointRange && DistinctObject && DifferentObjectKind,
+ "alias-analysis memory operands must be valid");
+ if (!First || !Same || !Partial || !DisjointRange || !DistinctObject || !DifferentObjectKind)
+ return;
+
+ rcc::MachineAliasAnalysis::Result Alias;
+ Context.expect(Alias.alias(*First, *Same) == rcc::MachineAliasResult::MustAlias &&
+ Alias.alias(*First, *Partial) == rcc::MachineAliasResult::PartialAlias &&
+ Alias.alias(*First, *DisjointRange) == rcc::MachineAliasResult::NoAlias &&
+ Alias.alias(*First, *DistinctObject) == rcc::MachineAliasResult::NoAlias &&
+ Alias.alias(*First, *DifferentObjectKind) == rcc::MachineAliasResult::NoAlias,
+ "alias analysis must distinguish exact, partial, range, and object aliasing");
+}
+
+void testMemoryDependence(TestContext &Context, const rcc::TargetDescription &Target) {
+ auto Function =
+ rcc::MachineFunction::create("memory", rcc::MachineFunctionStableID::fromValue(3));
+ if (!Function) {
+ Context.expect(false, "memory-dependence function creation must succeed");
+ return;
+ }
+ auto Block = appendBlock(**Function);
+ auto OtherBlock = appendBlock(**Function);
+ auto I64 = rcc::LLT::makeInteger(64);
+ auto Pointer = rcc::LLT::makePointer(0, 64);
+ auto Value = I64 ? (*Function)->registerInfo().createVirtualRegister(*I64)
+ : rcc::Expected<rcc::Register>{std::unexpected(I64.error())};
+ auto Address = Pointer ? (*Function)->registerInfo().createVirtualRegister(*Pointer)
+ : rcc::Expected<rcc::Register>{std::unexpected(Pointer.error())};
+ if (!Block || !OtherBlock || !Value || !Address) {
+ Context.expect(false, "memory-dependence MachineIR setup must succeed");
+ return;
+ }
+
+ auto StoreMemory = (*Function)->createMemOperand(
+ memoryDescription(rcc::MemoryBase::stack(), 0, rcc::MemoryAccessFlag::Store));
+ auto AliasingLoadMemory = (*Function)->createMemOperand(
+ memoryDescription(rcc::MemoryBase::stack(), 0, rcc::MemoryAccessFlag::Load));
+ auto DisjointLoadMemory = (*Function)->createMemOperand(
+ memoryDescription(rcc::MemoryBase::stack(), 32, rcc::MemoryAccessFlag::Load));
+ auto VolatileStoreDescription =
+ memoryDescription(rcc::MemoryBase::stack(), 64, rcc::MemoryAccessFlag::Store);
+ VolatileStoreDescription.Accesses.set(rcc::MemoryAccessFlag::Volatile);
+ auto VolatileLoadDescription =
+ memoryDescription(rcc::MemoryBase::stack(), 96, rcc::MemoryAccessFlag::Load);
+ VolatileLoadDescription.Accesses.set(rcc::MemoryAccessFlag::Volatile);
+ auto VolatileStoreMemory = (*Function)->createMemOperand(std::move(VolatileStoreDescription));
+ auto VolatileLoadMemory = (*Function)->createMemOperand(std::move(VolatileLoadDescription));
+ auto OtherBlockLoadMemory = (*Function)->createMemOperand(
+ memoryDescription(rcc::MemoryBase::stack(), 0, rcc::MemoryAccessFlag::Load));
+ rcc::MachineIRBuilder Builder(**Function, Target);
+ Context.expect(StoreMemory && AliasingLoadMemory && DisjointLoadMemory && VolatileStoreMemory &&
+ VolatileLoadMemory && OtherBlockLoadMemory && Builder.setInsertPoint(**Block),
+ "memory-dependence builder setup must succeed");
+ if (!StoreMemory || !AliasingLoadMemory || !DisjointLoadMemory || !VolatileStoreMemory ||
+ !VolatileLoadMemory || !OtherBlockLoadMemory)
+ return;
+
+ auto Store = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_STORE));
+ if (!Store || !Store->addUse(*Value) || !Store->addUse(*Address) ||
+ !Store->addMemOperand(**StoreMemory)) {
+ Context.expect(false, "generic store construction must succeed");
+ return;
+ }
+ auto StoreInstruction = Store->build();
+ auto Load = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_LOAD));
+ if (!Load || !Load->addDef(*Value) || !Load->addUse(*Address) ||
+ !Load->addMemOperand(**AliasingLoadMemory)) {
+ Context.expect(false, "aliasing generic load construction must succeed");
+ return;
+ }
+ auto LoadInstruction = Load->build();
+ auto DisjointLoad = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_LOAD));
+ if (!DisjointLoad || !DisjointLoad->addDef(*Value) || !DisjointLoad->addUse(*Address) ||
+ !DisjointLoad->addMemOperand(**DisjointLoadMemory)) {
+ Context.expect(false, "disjoint generic load construction must succeed");
+ return;
+ }
+ auto DisjointLoadInstruction = DisjointLoad->build();
+ auto VolatileStore = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_STORE));
+ if (!VolatileStore || !VolatileStore->addUse(*Value) || !VolatileStore->addUse(*Address) ||
+ !VolatileStore->addMemOperand(**VolatileStoreMemory)) {
+ Context.expect(false, "volatile store construction must succeed");
+ return;
+ }
+ auto VolatileStoreInstruction = VolatileStore->build();
+ auto VolatileLoad = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_LOAD));
+ if (!VolatileLoad || !VolatileLoad->addDef(*Value) || !VolatileLoad->addUse(*Address) ||
+ !VolatileLoad->addMemOperand(**VolatileLoadMemory)) {
+ Context.expect(false, "volatile load construction must succeed");
+ return;
+ }
+ auto VolatileLoadInstruction = VolatileLoad->build();
+ if (!Builder.setInsertPoint(**OtherBlock)) {
+ Context.expect(false, "second memory region insertion point must be valid");
+ return;
+ }
+ auto OtherBlockLoad = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_LOAD));
+ if (!OtherBlockLoad || !OtherBlockLoad->addDef(*Value) || !OtherBlockLoad->addUse(*Address) ||
+ !OtherBlockLoad->addMemOperand(**OtherBlockLoadMemory)) {
+ Context.expect(false, "second-region generic load construction must succeed");
+ return;
+ }
+ auto OtherBlockLoadInstruction = OtherBlockLoad->build();
+ Context.expect(StoreInstruction && LoadInstruction && DisjointLoadInstruction &&
+ VolatileStoreInstruction && VolatileLoadInstruction &&
+ OtherBlockLoadInstruction,
+ "memory instructions must be published transactionally");
+ if (!StoreInstruction || !LoadInstruction || !DisjointLoadInstruction ||
+ !VolatileStoreInstruction || !VolatileLoadInstruction || !OtherBlockLoadInstruction)
+ return;
+
+ rcc::MachineAnalysisManager Analyses;
+ auto Registered = rcc::registerStage8MachineAnalyses(Analyses);
+ auto Dependencies =
+ Registered ? Analyses.getResult<rcc::MachineMemoryDependenceAnalysis>(**Function, Target)
+ : rcc::Expected<rcc::MachineMemoryDependenceAnalysis::Result *>{
+ std::unexpected(Registered.error())};
+ Context.expect(Dependencies && (*Dependencies)->dependencies(**LoadInstruction).size() == 1 &&
+ (*Dependencies)->dependencies(**LoadInstruction).front().Source ==
+ (*StoreInstruction)->stableID() &&
+ (*Dependencies)->dependencies(**DisjointLoadInstruction).empty(),
+ "memory dependence must add flow edges only for potentially aliasing accesses");
+ Context.expect(
+ Dependencies && (*Dependencies)->regions().size() == 2 &&
+ (*Dependencies)->regionFor(**Block) != nullptr &&
+ (*Dependencies)->regionFor(**Block)->Instructions.size() == 5 &&
+ (*Dependencies)->regionFor(**OtherBlock) != nullptr &&
+ (*Dependencies)->regionFor(**OtherBlock)->Instructions.size() == 1 &&
+ (*Dependencies)->dependencies(**OtherBlockLoadInstruction).empty() &&
+ (*Dependencies)->dependencies(**OtherBlock).empty(),
+ "memory dependence must expose deterministic block regions without cross-CFG edges");
+ bool HasOrderedVolatileDependency = false;
+ if (Dependencies) {
+ HasOrderedVolatileDependency =
+ std::ranges::any_of((*Dependencies)->dependencies(**VolatileLoadInstruction),
+ [&](const rcc::MachineMemoryDependency &Dependency) {
+ return Dependency.Source == (*VolatileStoreInstruction)->stableID();
+ });
+ }
+ Context.expect(HasOrderedVolatileDependency,
+ "ordered memory accesses must retain dependencies across disjoint addresses");
+}
+
+class ConflictingAnalysis final {
+public:
+ struct Result final {};
+ [[nodiscard]] static rcc::Expected<Result>
+ run(rcc::MachineFunction &, const rcc::TargetDescription &, rcc::MachineAnalysisManager &) {
+ return Result{};
+ }
+};
+
+void testAtomicStandardRegistration(TestContext &Context) {
+ rcc::MachineAnalysisManager Analyses;
+ Context.expect(Analyses
+ .registerAnalysis<ConflictingAnalysis>({
+ .Name = "MachineLoopInfo",
+ .Dependencies = {},
+ })
+ .has_value(),
+ "analysis-name conflict setup must succeed");
+ Context.expect(!rcc::registerStage8MachineAnalyses(Analyses) &&
+ !Analyses.isRegistered(
+ rcc::MachineAnalysisManager::id<rcc::MachineDominatorTreeAnalysis>()),
+ "failed standard-analysis registration must not publish a partial set");
+}
+
+void testCFGUpdaterPHIs(TestContext &Context, const rcc::TargetDescription &Target) {
+ auto Function =
+ rcc::MachineFunction::create("cfg_phi", rcc::MachineFunctionStableID::fromValue(4));
+ if (!Function) {
+ Context.expect(false, "CFG PHI function creation must succeed");
+ return;
+ }
+ auto Source = appendBlock(**Function);
+ auto Other = appendBlock(**Function);
+ auto Merge = appendBlock(**Function);
+ auto Unconnected = appendBlock(**Function);
+ if (!Source || !Other || !Merge || !Unconnected) {
+ Context.expect(false, "CFG PHI blocks must be appended");
+ return;
+ }
+ auto Updater = (*Function)->cfgUpdater();
+ if (!Updater.addSuccessor(**Source, **Merge, rcc::BranchProbability::one()) ||
+ !Updater.addSuccessor(**Other, **Merge, rcc::BranchProbability::one())) {
+ Context.expect(false, "CFG PHI predecessor edges must be created");
+ return;
+ }
+
+ auto I64 = rcc::LLT::makeInteger(64);
+ auto Destination = I64 ? (*Function)->registerInfo().createVirtualRegister(*I64)
+ : rcc::Expected<rcc::Register>{std::unexpected(I64.error())};
+ auto SourceValue = I64 ? (*Function)->registerInfo().createVirtualRegister(*I64)
+ : rcc::Expected<rcc::Register>{std::unexpected(I64.error())};
+ auto OtherValue = I64 ? (*Function)->registerInfo().createVirtualRegister(*I64)
+ : rcc::Expected<rcc::Register>{std::unexpected(I64.error())};
+ if (!Destination || !SourceValue || !OtherValue) {
+ Context.expect(false, "CFG PHI registers must be created");
+ return;
+ }
+
+ rcc::MachineIRBuilder Builder(**Function, Target);
+ if (!Builder.setInsertPoint(**Source))
+ return;
+ auto SourceDefinition = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_IMPLICIT_DEF));
+ if (!SourceDefinition || !SourceDefinition->addDef(*SourceValue) || !SourceDefinition->build()) {
+ Context.expect(false, "CFG PHI source definition must build");
+ return;
+ }
+ auto Branch = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_BR));
+ if (!Branch || !Branch->addBasicBlock(**Merge)) {
+ Context.expect(false, "CFG PHI branch must be assembled");
+ return;
+ }
+ auto BranchInstruction = Branch->build();
+ if (!BranchInstruction || !Builder.setInsertPoint(**Merge)) {
+ Context.expect(false, "CFG PHI branch must publish");
+ return;
+ }
+ auto Phi = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_PHI));
+ if (!Phi || !Phi->addDef(*Destination) || !Phi->addUse(*SourceValue) ||
+ !Phi->addBasicBlock(**Source) || !Phi->addUse(*OtherValue) || !Phi->addBasicBlock(**Other)) {
+ Context.expect(false, "CFG PHI instruction must be assembled");
+ return;
+ }
+ auto PhiInstruction = Phi->build();
+ Context.expect(PhiInstruction && rcc::MachineVerifier::verify(**Function, Target),
+ "initial CFG PHI function must verify");
+ if (!PhiInstruction)
+ return;
+
+ auto Split = Updater.splitBlock(**Source, **BranchInstruction);
+ Context.expect(
+ Split && (*Split)->predecessors().size() == 1 &&
+ (*Split)->predecessors().front() == *Source && (*Split)->successors().size() == 1 &&
+ (*PhiInstruction)->operandAt(2)->getIf<rcc::BasicBlockOperand>()->Block == *Split &&
+ rcc::MachineVerifier::verify(**Function, Target),
+ "block split must redirect CFG edges and PHI predecessor identity");
+ if (!Split)
+ return;
+
+ Context.expect(Updater.removeSuccessor(**Other, **Merge) &&
+ (*PhiInstruction)->operandCount() == 3 &&
+ rcc::MachineVerifier::verify(**Function, Target),
+ "edge deletion must remove the corresponding PHI incoming pair");
+ Context.expect(!Updater.addSuccessor(**Unconnected, **Merge, rcc::BranchProbability::one()),
+ "new PHI predecessors without incoming values must be rejected");
+ Context.expect(Updater.deleteBlock(**Other) && !(*Other)->isLinked() &&
+ rcc::MachineVerifier::verify(**Function, Target),
+ "CFG block deletion must detach a disconnected block cleanly");
+}
+
+} // namespace
+
+int main() {
+ TestContext Context;
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "machine analysis tests require a valid target");
+ if (!Target)
+ return Context.result();
+
+ testCFGAnalyses(Context, *Target);
+ testIrreducibleCFG(Context, *Target);
+ testAliasAnalysis(Context);
+ testMemoryDependence(Context, *Target);
+ testCFGUpdaterPHIs(Context, *Target);
+ testAtomicStandardRegistration(Context);
+ return Context.result();
+}
diff --git a/backend/unittests/CodeGen/MachinePassTest.cpp b/backend/unittests/CodeGen/MachinePassTest.cpp
new file mode 100644
index 0000000..fb125a4
--- /dev/null
+++ b/backend/unittests/CodeGen/MachinePassTest.cpp
@@ -0,0 +1,918 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/MachinePass.h"
+
+#include "Target/TestTarget.h"
+#include "rcc/CodeGen/CompilationUnit.h"
+#include "rcc/CodeGen/GenericOpcodes.h"
+#include "rcc/CodeGen/MachineFunction.h"
+
+#include <algorithm>
+#include <atomic>
+#include <chrono>
+#include <cstddef>
+#include <cstdint>
+#include <iostream>
+#include <memory>
+#include <ranges>
+#include <string>
+#include <string_view>
+#include <thread>
+#include <utility>
+#include <vector>
+
+namespace {
+
+class TestContext final {
+public:
+ void expect(bool Condition, std::string_view Message) {
+ if (Condition)
+ return;
+ std::cerr << Message << '\n';
+ ++Failures;
+ }
+
+ [[nodiscard]] int result() const noexcept {
+ return Failures == 0 ? 0 : 1;
+ }
+
+private:
+ int Failures = 0;
+};
+
+[[nodiscard]] rcc::Expected<rcc::TargetDescription> createTarget() {
+ std::vector<rcc::InstructionDescriptor> Descriptors;
+ std::vector<rcc::ImmediatePredicate> Predicates;
+ auto Appended = rcc::appendGenericInstructionInfo(Descriptors, Predicates);
+ if (!Appended)
+ return std::unexpected(Appended.error());
+ auto Instructions = rcc::InstructionSet::create(std::move(Descriptors), std::move(Predicates));
+ if (!Instructions)
+ return std::unexpected(Instructions.error());
+ return rcc::test::createTestTargetDescription(std::move(*Instructions));
+}
+
+class BlockCountAnalysis final {
+public:
+ struct Result final {
+ std::size_t Count = 0;
+ };
+
+ [[nodiscard]] static constexpr std::string_view name() noexcept {
+ return "BlockCount";
+ }
+
+ [[nodiscard]] static rcc::Expected<Result> run(rcc::MachineFunction &Function,
+ const rcc::TargetDescription &,
+ rcc::MachineAnalysisManager &) {
+ ++Runs;
+ return Result{Function.blockCount()};
+ }
+
+ static inline std::atomic<std::uint32_t> Runs = 0;
+};
+
+class DependentAnalysis final {
+public:
+ struct Result final {
+ std::size_t Count = 0;
+ };
+
+ [[nodiscard]] static constexpr std::string_view name() noexcept {
+ return "Dependent";
+ }
+
+ [[nodiscard]] static rcc::Expected<Result> run(rcc::MachineFunction &Function,
+ const rcc::TargetDescription &Target,
+ rcc::MachineAnalysisManager &Analyses) {
+ auto Blocks = Analyses.getResult<BlockCountAnalysis>(Function, Target);
+ if (!Blocks)
+ return std::unexpected(Blocks.error());
+ return Result{(*Blocks)->Count};
+ }
+};
+
+class NonThreadSafeAnalysis final {
+public:
+ struct Result final {};
+
+ [[nodiscard]] static constexpr std::string_view name() noexcept {
+ return "NonThreadSafe";
+ }
+
+ [[nodiscard]] static rcc::Expected<Result>
+ run(rcc::MachineFunction &, const rcc::TargetDescription &, rcc::MachineAnalysisManager &) {
+ return Result{};
+ }
+};
+
+class FailingAnalysis final {
+public:
+ struct Result final {};
+
+ [[nodiscard]] static constexpr std::string_view name() noexcept {
+ return "Failing";
+ }
+
+ [[nodiscard]] static rcc::Expected<Result>
+ run(rcc::MachineFunction &, const rcc::TargetDescription &, rcc::MachineAnalysisManager &) {
+ return rcc::makeUnexpected(rcc::ErrorCode::VerificationError,
+ "intentional machine analysis failure");
+ }
+};
+
+class StickyAnalysis final {
+public:
+ struct Result final {
+ [[nodiscard]] bool invalidate(const rcc::MachineFunction &,
+ const rcc::PreservedAnalyses &) noexcept {
+ return false;
+ }
+ };
+
+ [[nodiscard]] static constexpr std::string_view name() noexcept {
+ return "Sticky";
+ }
+ [[nodiscard]] static rcc::Expected<Result>
+ run(rcc::MachineFunction &, const rcc::TargetDescription &, rcc::MachineAnalysisManager &) {
+ return Result{};
+ }
+};
+
+class ExpiringAnalysis final {
+public:
+ struct Result final {
+ [[nodiscard]] bool invalidate(const rcc::MachineFunction &,
+ const rcc::PreservedAnalyses &) noexcept {
+ return true;
+ }
+ };
+
+ [[nodiscard]] static constexpr std::string_view name() noexcept {
+ return "Expiring";
+ }
+ [[nodiscard]] static rcc::Expected<Result>
+ run(rcc::MachineFunction &, const rcc::TargetDescription &, rcc::MachineAnalysisManager &) {
+ return Result{};
+ }
+};
+
+class CycleA final {
+public:
+ struct Result final {};
+ [[nodiscard]] static constexpr std::string_view name() noexcept {
+ return "CycleA";
+ }
+ [[nodiscard]] static rcc::Expected<Result>
+ run(rcc::MachineFunction &, const rcc::TargetDescription &, rcc::MachineAnalysisManager &) {
+ return Result{};
+ }
+};
+
+class CycleB final {
+public:
+ struct Result final {};
+ [[nodiscard]] static constexpr std::string_view name() noexcept {
+ return "CycleB";
+ }
+ [[nodiscard]] static rcc::Expected<Result>
+ run(rcc::MachineFunction &, const rcc::TargetDescription &, rcc::MachineAnalysisManager &) {
+ return Result{};
+ }
+};
+
+class RequiredAnalysisPass final : public rcc::MachineFunctionPass {
+public:
+ [[nodiscard]] const rcc::MachinePassContract &contract() const noexcept override {
+ static const rcc::MachinePassContract Contract{
+ .Name = "RequiredAnalysis",
+ .Requires = {},
+ .Sets = {},
+ .Clears = {},
+ .RequiredAnalyses = {rcc::MachineAnalysisManager::id<DependentAnalysis>()},
+ .VerificationStage = std::nullopt,
+ };
+ return Contract;
+ }
+
+ [[nodiscard]] rcc::Expected<rcc::MachinePassResult>
+ run(rcc::MachineFunction &Function, const rcc::TargetDescription &Target,
+ rcc::MachineAnalysisManager &Analyses) override {
+ auto Dependent = Analyses.getResult<DependentAnalysis>(Function, Target);
+ if (!Dependent)
+ return std::unexpected(Dependent.error());
+ auto FirstAction = Analyses.requestAction("first", Function);
+ auto SecondAction = Analyses.requestAction("second", Function);
+ if (!FirstAction || !SecondAction || !*FirstAction || *SecondAction) {
+ return rcc::makeUnexpected(rcc::ErrorCode::VerificationError,
+ "per-function action fuel was not enforced");
+ }
+ rcc::MachinePassResult Result;
+ Result.Preserved = rcc::PreservedAnalyses::all();
+ return Result;
+ }
+};
+
+class UnreportedChangePass final : public rcc::MachineFunctionPass {
+public:
+ [[nodiscard]] const rcc::MachinePassContract &contract() const noexcept override {
+ static const rcc::MachinePassContract Contract{
+ .Name = "UnreportedChange",
+ .Requires = {},
+ .Sets = {},
+ .Clears = {},
+ .RequiredAnalyses = {},
+ .VerificationStage = std::nullopt,
+ };
+ return Contract;
+ }
+
+ [[nodiscard]] rcc::Expected<rcc::MachinePassResult> run(rcc::MachineFunction &Function,
+ const rcc::TargetDescription &,
+ rcc::MachineAnalysisManager &) override {
+ auto Block = Function.createBasicBlock();
+ if (!Block)
+ return std::unexpected(Block.error());
+ auto Appended = Function.appendBlock(**Block);
+ if (!Appended)
+ return std::unexpected(Appended.error());
+ return rcc::MachinePassResult{};
+ }
+};
+
+class RequiresFailingAnalysisPass final : public rcc::MachineFunctionPass {
+public:
+ [[nodiscard]] const rcc::MachinePassContract &contract() const noexcept override {
+ static const rcc::MachinePassContract Contract{
+ .Name = "RequiresFailingAnalysis",
+ .Requires = {},
+ .Sets = {},
+ .Clears = {},
+ .RequiredAnalyses = {rcc::MachineAnalysisManager::id<FailingAnalysis>()},
+ .VerificationStage = std::nullopt,
+ };
+ return Contract;
+ }
+
+ [[nodiscard]] rcc::Expected<rcc::MachinePassResult> run(rcc::MachineFunction &,
+ const rcc::TargetDescription &,
+ rcc::MachineAnalysisManager &) override {
+ return rcc::MachinePassResult{};
+ }
+};
+
+class MutatingFailurePass final : public rcc::MachineFunctionPass {
+public:
+ [[nodiscard]] const rcc::MachinePassContract &contract() const noexcept override {
+ static const rcc::MachinePassContract Contract{
+ .Name = "MutatingFailure",
+ .Requires = {},
+ .Sets = {},
+ .Clears = {},
+ .RequiredAnalyses = {rcc::MachineAnalysisManager::id<BlockCountAnalysis>()},
+ .VerificationStage = std::nullopt,
+ };
+ return Contract;
+ }
+
+ [[nodiscard]] rcc::Expected<rcc::MachinePassResult> run(rcc::MachineFunction &Function,
+ const rcc::TargetDescription &,
+ rcc::MachineAnalysisManager &) override {
+ auto Block = Function.createBasicBlock();
+ if (!Block)
+ return std::unexpected(Block.error());
+ auto Appended = Function.appendBlock(**Block);
+ if (!Appended)
+ return std::unexpected(Appended.error());
+ return rcc::makeUnexpected(rcc::ErrorCode::VerificationError,
+ "intentional mutating pass failure");
+ }
+};
+
+class ParallelCountingPass final : public rcc::MachineFunctionPass {
+public:
+ explicit ParallelCountingPass(std::atomic<std::uint32_t> &Count) : Count(&Count) {}
+
+ [[nodiscard]] const rcc::MachinePassContract &contract() const noexcept override {
+ static const rcc::MachinePassContract Contract = [] {
+ rcc::MachinePassContract Value{
+ .Name = "ParallelCounting",
+ .Requires = {},
+ .Sets = {},
+ .Clears = {},
+ .RequiredAnalyses = {},
+ .VerificationStage = std::nullopt,
+ };
+ Value.ThreadSafety = rcc::PassThreadSafety::ThreadSafe;
+ return Value;
+ }();
+ return Contract;
+ }
+
+ [[nodiscard]] rcc::Expected<rcc::MachinePassResult> run(rcc::MachineFunction &,
+ const rcc::TargetDescription &,
+ rcc::MachineAnalysisManager &) override {
+ Count->fetch_add(1, std::memory_order_relaxed);
+ return rcc::MachinePassResult{};
+ }
+
+private:
+ std::atomic<std::uint32_t> *Count;
+};
+
+class InstrumentedParallelPass final : public rcc::MachineFunctionPass {
+public:
+ InstrumentedParallelPass(std::atomic<std::uint32_t> &Runs, std::atomic<std::uint32_t> &Active,
+ std::atomic<std::uint32_t> &MaximumActive)
+ : Runs(&Runs), Active(&Active), MaximumActive(&MaximumActive) {}
+
+ [[nodiscard]] const rcc::MachinePassContract &contract() const noexcept override {
+ static const rcc::MachinePassContract Contract = [] {
+ rcc::MachinePassContract Value{
+ .Name = "InstrumentedParallel",
+ .Requires = {},
+ .Sets = {},
+ .Clears = {},
+ .RequiredAnalyses = {},
+ .VerificationStage = std::nullopt,
+ };
+ Value.ThreadSafety = rcc::PassThreadSafety::ThreadSafe;
+ Value.DeclaredChanges = {rcc::MachinePassChange::BlockLayout};
+ return Value;
+ }();
+ return Contract;
+ }
+
+ [[nodiscard]] rcc::Expected<rcc::MachinePassResult> run(rcc::MachineFunction &Function,
+ const rcc::TargetDescription &,
+ rcc::MachineAnalysisManager &) override {
+ const std::uint32_t Current = Active->fetch_add(1, std::memory_order_acq_rel) + 1U;
+ std::uint32_t Maximum = MaximumActive->load(std::memory_order_relaxed);
+ while (Maximum < Current &&
+ !MaximumActive->compare_exchange_weak(Maximum, Current, std::memory_order_relaxed)) {
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(2));
+ auto Block = Function.createBasicBlock();
+ if (!Block) {
+ Active->fetch_sub(1, std::memory_order_acq_rel);
+ return std::unexpected(Block.error());
+ }
+ auto Appended = Function.appendBlock(**Block);
+ Active->fetch_sub(1, std::memory_order_acq_rel);
+ if (!Appended)
+ return std::unexpected(Appended.error());
+ Runs->fetch_add(1, std::memory_order_relaxed);
+ rcc::MachinePassResult Result;
+ Result.Changed = true;
+ Result.Changes.add(rcc::MachinePassChange::BlockLayout);
+ return Result;
+ }
+
+private:
+ std::atomic<std::uint32_t> *Runs;
+ std::atomic<std::uint32_t> *Active;
+ std::atomic<std::uint32_t> *MaximumActive;
+};
+
+class RequiresNonThreadSafeAnalysisPass final : public rcc::MachineFunctionPass {
+public:
+ [[nodiscard]] const rcc::MachinePassContract &contract() const noexcept override {
+ static const rcc::MachinePassContract Contract = [] {
+ rcc::MachinePassContract Value{
+ .Name = "RequiresNonThreadSafe",
+ .Requires = {},
+ .Sets = {},
+ .Clears = {},
+ .RequiredAnalyses =
+ {
+ rcc::MachineAnalysisManager::id<NonThreadSafeAnalysis>(),
+ },
+ .VerificationStage = std::nullopt,
+ };
+ Value.ThreadSafety = rcc::PassThreadSafety::ThreadSafe;
+ return Value;
+ }();
+ return Contract;
+ }
+
+ [[nodiscard]] rcc::Expected<rcc::MachinePassResult> run(rcc::MachineFunction &,
+ const rcc::TargetDescription &,
+ rcc::MachineAnalysisManager &) override {
+ return rcc::MachinePassResult{};
+ }
+};
+
+class UnitFunctionCountAnalysis final {
+public:
+ struct Result final {
+ std::size_t Count = 0;
+ };
+ [[nodiscard]] static constexpr std::string_view name() noexcept {
+ return "UnitFunctionCount";
+ }
+ [[nodiscard]] static rcc::Expected<Result> run(rcc::CompilationUnit &Unit,
+ const rcc::TargetDescription &,
+ rcc::CompilationUnitAnalysisManager &) {
+ return Result{Unit.functionCount()};
+ }
+};
+
+class FailingUnitAnalysis final {
+public:
+ struct Result final {};
+
+ [[nodiscard]] static constexpr std::string_view name() noexcept {
+ return "FailingUnit";
+ }
+
+ [[nodiscard]] static rcc::Expected<Result> run(rcc::CompilationUnit &,
+ const rcc::TargetDescription &,
+ rcc::CompilationUnitAnalysisManager &) {
+ return rcc::makeUnexpected(rcc::ErrorCode::VerificationError,
+ "intentional compilation-unit analysis failure");
+ }
+};
+
+class RequiresFailingUnitAnalysisPass final : public rcc::CompilationUnitPass {
+public:
+ [[nodiscard]] const rcc::CompilationUnitPassContract &contract() const noexcept override {
+ static const rcc::CompilationUnitPassContract Contract{
+ .Name = "RequiresFailingUnitAnalysis",
+ .RequiredAnalyses =
+ {
+ rcc::CompilationUnitAnalysisManager::id<FailingUnitAnalysis>(),
+ },
+ .Parameters = {},
+ .DeclaredChanges = {},
+ };
+ return Contract;
+ }
+
+ [[nodiscard]] rcc::Expected<rcc::CompilationUnitPassResult>
+ run(rcc::CompilationUnit &, const rcc::TargetDescription &, rcc::CompilationUnitAnalysisManager &,
+ rcc::MachineAnalysisManager &) override {
+ return rcc::CompilationUnitPassResult{};
+ }
+};
+
+class ObserveUnitPass final : public rcc::CompilationUnitPass {
+public:
+ [[nodiscard]] const rcc::CompilationUnitPassContract &contract() const noexcept override {
+ static const rcc::CompilationUnitPassContract Contract{
+ .Name = "ObserveUnit",
+ .RequiredAnalyses = {rcc::CompilationUnitAnalysisManager::id<UnitFunctionCountAnalysis>()},
+ .Parameters = {},
+ .DeclaredChanges = {},
+ };
+ return Contract;
+ }
+
+ [[nodiscard]] rcc::Expected<rcc::CompilationUnitPassResult>
+ run(rcc::CompilationUnit &Unit, const rcc::TargetDescription &Target,
+ rcc::CompilationUnitAnalysisManager &Analyses, rcc::MachineAnalysisManager &) override {
+ auto Count = Analyses.getResult<UnitFunctionCountAnalysis>(Unit, Target);
+ if (!Count)
+ return std::unexpected(Count.error());
+ if ((*Count)->Count != Unit.functionCount()) {
+ return rcc::makeUnexpected(rcc::ErrorCode::VerificationError,
+ "unit analysis returned a stale function count");
+ }
+ return rcc::CompilationUnitPassResult{};
+ }
+};
+
+class MutatingUnitFailurePass final : public rcc::CompilationUnitPass {
+public:
+ [[nodiscard]] const rcc::CompilationUnitPassContract &contract() const noexcept override {
+ static const rcc::CompilationUnitPassContract Contract{
+ .Name = "MutatingUnitFailure",
+ .RequiredAnalyses = {rcc::CompilationUnitAnalysisManager::id<UnitFunctionCountAnalysis>()},
+ .Parameters = {},
+ .DeclaredChanges = {rcc::MachinePassChange::BlockLayout},
+ };
+ return Contract;
+ }
+
+ [[nodiscard]] rcc::Expected<rcc::CompilationUnitPassResult>
+ run(rcc::CompilationUnit &Unit, const rcc::TargetDescription &,
+ rcc::CompilationUnitAnalysisManager &, rcc::MachineAnalysisManager &) override {
+ auto Created = Unit.createMachineFunction("added-before-failure");
+ if (!Created)
+ return std::unexpected(Created.error());
+ return rcc::makeUnexpected(rcc::ErrorCode::VerificationError,
+ "intentional compilation-unit pass failure");
+ }
+};
+
+void testAnalysisInfrastructure(TestContext &Context, const rcc::TargetDescription &Target) {
+ auto Function =
+ rcc::MachineFunction::create("analysis", rcc::MachineFunctionStableID::fromValue(1));
+ Context.expect(Function.has_value(), "analysis function creation must succeed");
+ if (!Function)
+ return;
+
+ rcc::MachineAnalysisManager Analyses;
+ Context.expect(Analyses
+ .registerAnalysis<BlockCountAnalysis>({
+ .Name = "BlockCount",
+ .Dependencies = {},
+ .InvalidatedBy = {rcc::MachinePassChange::CFG},
+ })
+ .has_value(),
+ "base analysis registration must succeed");
+ Context.expect(Analyses
+ .registerAnalysis<DependentAnalysis>({
+ .Name = "Dependent",
+ .Dependencies = {rcc::MachineAnalysisManager::id<BlockCountAnalysis>()},
+ .InvalidatedBy = {rcc::MachinePassChange::Operands},
+ })
+ .has_value(),
+ "dependent analysis registration must succeed");
+
+ BlockCountAnalysis::Runs = 0;
+ auto First = Analyses.getResult<DependentAnalysis>(**Function, Target);
+ auto Second = Analyses.getResult<DependentAnalysis>(**Function, Target);
+ Context.expect(First && Second && *First == *Second && BlockCountAnalysis::Runs == 1,
+ "analysis dependencies must materialize once and then use the cache");
+
+ Analyses.invalidate(**Function, rcc::PreservedAnalyses::none(),
+ {rcc::MachinePassChange::Registers});
+ Context.expect(Analyses.cachedResultCount(**Function) == 2,
+ "unrelated changes must preserve cached analyses precisely");
+ Analyses.invalidate(**Function, rcc::PreservedAnalyses::none(), {rcc::MachinePassChange::CFG});
+ Context.expect(Analyses.cachedResultCount(**Function) == 0,
+ "invalidating a dependency must invalidate its dependent analysis");
+
+ rcc::MachineAnalysisManager Cyclic;
+ Context.expect(Cyclic
+ .registerAnalysis<CycleA>({
+ .Name = "CycleA",
+ .Dependencies = {rcc::MachineAnalysisManager::id<CycleB>()},
+ })
+ .has_value() &&
+ Cyclic
+ .registerAnalysis<CycleB>({
+ .Name = "CycleB",
+ .Dependencies = {rcc::MachineAnalysisManager::id<CycleA>()},
+ })
+ .has_value(),
+ "cyclic analysis registrations must be accepted until requested");
+ Context.expect(!Cyclic.getResult<CycleA>(**Function, Target),
+ "analysis dependency cycles must produce a structured error");
+
+ rcc::MachineAnalysisManager CustomInvalidation;
+ Context.expect(CustomInvalidation
+ .registerAnalysis<StickyAnalysis>({
+ .Name = "Sticky",
+ .Dependencies = {},
+ .InvalidatedBy = {rcc::MachinePassChange::CFG},
+ })
+ .has_value() &&
+ CustomInvalidation
+ .registerAnalysis<ExpiringAnalysis>({
+ .Name = "Expiring",
+ .Dependencies = {},
+ .InvalidatedBy = {rcc::MachinePassChange::CFG},
+ })
+ .has_value() &&
+ CustomInvalidation.getResult<StickyAnalysis>(**Function, Target) &&
+ CustomInvalidation.getResult<ExpiringAnalysis>(**Function, Target),
+ "custom invalidation analyses must materialize");
+ CustomInvalidation.invalidate(**Function, rcc::PreservedAnalyses::none(),
+ {rcc::MachinePassChange::CFG});
+ Context.expect(
+ CustomInvalidation.hasCached(**Function, rcc::MachineAnalysisManager::id<StickyAnalysis>()) &&
+ !CustomInvalidation.hasCached(**Function,
+ rcc::MachineAnalysisManager::id<ExpiringAnalysis>()),
+ "analysis invalidate() must return false to preserve and true to invalidate");
+}
+
+void testPassAndInstrumentation(TestContext &Context, const rcc::TargetDescription &Target) {
+ auto Function = rcc::MachineFunction::create("pass", rcc::MachineFunctionStableID::fromValue(2));
+ Context.expect(Function.has_value(), "pass function creation must succeed");
+ if (!Function)
+ return;
+
+ rcc::MachineAnalysisManager Analyses;
+ Context.expect(
+ Analyses
+ .registerAnalysis<BlockCountAnalysis>({
+ .Name = "BlockCount",
+ .Dependencies = {},
+ .InvalidatedBy = rcc::MachinePassChanges::all(),
+ .ThreadSafety = rcc::PassThreadSafety::ThreadSafe,
+ })
+ .has_value() &&
+ Analyses
+ .registerAnalysis<DependentAnalysis>({
+ .Name = "Dependent",
+ .Dependencies = {rcc::MachineAnalysisManager::id<BlockCountAnalysis>()},
+ })
+ .has_value(),
+ "pass analysis registrations must succeed");
+
+ rcc::MachinePassInstrumentation Instrumentation;
+ Instrumentation.setPerFunctionFuel(1);
+ std::uint32_t BeforePass = 0;
+ std::uint32_t AfterPass = 0;
+ std::uint32_t BeforeAnalysis = 0;
+ std::uint32_t AfterAnalysis = 0;
+ std::uint32_t AnalysisCacheHits = 0;
+ std::uint32_t Printed = 0;
+ std::uint32_t ChangeEvents = 0;
+ std::uint32_t FailureEvents = 0;
+ Instrumentation.addBeforeCallback(
+ [&BeforePass](std::string_view, const rcc::MachineFunction &) { ++BeforePass; });
+ Instrumentation.addAfterCallback([&AfterPass](std::string_view, const rcc::MachineFunction &,
+ const rcc::MachinePassResult &) { ++AfterPass; });
+ Instrumentation.addBeforeAnalysisCallback(
+ [&BeforeAnalysis](std::string_view, const rcc::MachineFunction &) { ++BeforeAnalysis; });
+ Instrumentation.addAfterAnalysisCallback(
+ [&AfterAnalysis, &AnalysisCacheHits](std::string_view, const rcc::MachineFunction &,
+ bool CacheHit, std::uint64_t) {
+ ++AfterAnalysis;
+ AnalysisCacheHits += CacheHit ? 1U : 0U;
+ });
+ Instrumentation.addIRPrintCallback([&Printed](rcc::InstrumentationPhase, std::string_view,
+ const rcc::MachineFunction &) { ++Printed; });
+ Instrumentation.addChangeCallback([&ChangeEvents](std::string_view, const rcc::MachineFunction &,
+ rcc::MachinePassChanges Changes,
+ bool FingerprintChanged) {
+ if (Changes.empty() && !FingerprintChanged)
+ ++ChangeEvents;
+ });
+ Instrumentation.addFailureCallback([&FailureEvents](std::string_view,
+ const rcc::MachineFunction &,
+ const rcc::Error &) { ++FailureEvents; });
+
+ rcc::MachinePassManager Pipeline;
+ Context.expect(Pipeline.addPass(std::make_unique<RequiredAnalysisPass>()).has_value(),
+ "a valid pass must be accepted");
+ auto Run = Pipeline.run(**Function, Target, Analyses, &Instrumentation);
+ Context.expect(Run.has_value(), "a pass with a materialized required analysis must run");
+ Context.expect(BeforePass == 1 && AfterPass == 1 && BeforeAnalysis == 2 && AfterAnalysis == 4 &&
+ AnalysisCacheHits == 2 && Printed == 2 && ChangeEvents == 1,
+ "pass, analysis, cache-hit, IR-print, and change callbacks must all run");
+ const auto Statistics = Instrumentation.statistics();
+ Context.expect(Statistics.size() == 3,
+ "instrumentation must collect one pass and two analysis statistics");
+
+ auto ChangedFunction =
+ rcc::MachineFunction::create("unreported", rcc::MachineFunctionStableID::fromValue(3));
+ rcc::MachineAnalysisManager EmptyAnalyses;
+ rcc::MachinePassManager Detecting(
+ {.VerifyBefore = true, .VerifyEach = true, .DetectUnreportedChanges = true});
+ Context.expect(ChangedFunction &&
+ Detecting.addPass(std::make_unique<UnreportedChangePass>()).has_value() &&
+ !Detecting.run(**ChangedFunction, Target, EmptyAnalyses, &Instrumentation) &&
+ FailureEvents == 1,
+ "change detection must reject an unreported MachineIR mutation");
+
+ auto FailedFunction =
+ rcc::MachineFunction::create("failed", rcc::MachineFunctionStableID::fromValue(4));
+ rcc::MachineAnalysisManager FailureAnalyses;
+ Context.expect(FailedFunction && FailureAnalyses
+ .registerAnalysis<BlockCountAnalysis>({
+ .Name = "BlockCount",
+ .Dependencies = {},
+ .InvalidatedBy = rcc::MachinePassChanges::all(),
+ })
+ .has_value(),
+ "mutating failure analysis fixtures must be valid");
+ rcc::MachinePassManager FailingPipeline;
+ Context.expect(FailedFunction &&
+ FailingPipeline.addPass(std::make_unique<MutatingFailurePass>()).has_value() &&
+ !FailingPipeline.run(**FailedFunction, Target, FailureAnalyses) &&
+ !FailureAnalyses.hasCached(
+ **FailedFunction, rcc::MachineAnalysisManager::id<BlockCountAnalysis>()),
+ "a pass that mutates and fails must not leave stale function analyses cached");
+
+ rcc::MachineAnalysisManager FailingAnalyses;
+ Context.expect(FailingAnalyses
+ .registerAnalysis<FailingAnalysis>({
+ .Name = "Failing",
+ .Dependencies = {},
+ })
+ .has_value(),
+ "failing analysis registration must succeed");
+ rcc::MachinePassInstrumentation FailureInstrumentation;
+ std::uint32_t BeforeFailingAnalysis = 0;
+ std::uint32_t FailingAnalysisEvents = 0;
+ std::uint32_t RequiredAnalysisPassFailures = 0;
+ FailureInstrumentation.addBeforeAnalysisCallback(
+ [&BeforeFailingAnalysis](std::string_view, const rcc::MachineFunction &) {
+ ++BeforeFailingAnalysis;
+ });
+ FailureInstrumentation.addAnalysisFailureCallback(
+ [&FailingAnalysisEvents](std::string_view, const rcc::MachineFunction &, const rcc::Error &) {
+ ++FailingAnalysisEvents;
+ });
+ FailureInstrumentation.addFailureCallback(
+ [&RequiredAnalysisPassFailures](std::string_view, const rcc::MachineFunction &,
+ const rcc::Error &) { ++RequiredAnalysisPassFailures; });
+ rcc::MachinePassManager RequiredAnalysisFailurePipeline;
+ Context.expect(
+ RequiredAnalysisFailurePipeline.addPass(std::make_unique<RequiresFailingAnalysisPass>())
+ .has_value() &&
+ !RequiredAnalysisFailurePipeline.run(**Function, Target, FailingAnalyses,
+ &FailureInstrumentation) &&
+ BeforeFailingAnalysis == 1 && FailingAnalysisEvents == 1 &&
+ RequiredAnalysisPassFailures == 1,
+ "analysis and pass failure instrumentation must report required-analysis failures");
+}
+
+void testCompilationUnitAndParallelism(TestContext &Context, const rcc::TargetDescription &Target) {
+ rcc::CompilationUnit Unit;
+ for (std::size_t Index = 0; Index < 8; ++Index) {
+ auto Function = Unit.createMachineFunction("f" + std::to_string(Index));
+ Context.expect(Function.has_value(), "parallel test function creation must succeed");
+ }
+
+ std::atomic<std::uint32_t> Runs = 0;
+ rcc::MachinePassManager Parallel;
+ Context.expect(Parallel.addPass(std::make_unique<ParallelCountingPass>(Runs)).has_value(),
+ "thread-safe pass registration must succeed");
+ rcc::MachineAnalysisManager Prototype;
+ auto ParallelRun = Parallel.runFunctions(Unit, Target, Prototype, 4);
+ Context.expect(ParallelRun.has_value() && Runs == Unit.functionCount(),
+ "parallel function execution must visit every function exactly once");
+
+ std::atomic<std::uint32_t> DuplicateRuns = 0;
+ rcc::MachinePassManager Repeated;
+ Context.expect(
+ Repeated.addPass(std::make_unique<ParallelCountingPass>(DuplicateRuns)).has_value() &&
+ Repeated.addPass(std::make_unique<ParallelCountingPass>(DuplicateRuns)).has_value() &&
+ Repeated.runFunctions(Unit, Target, Prototype, 4).has_value() &&
+ DuplicateRuns == Unit.functionCount() * 2,
+ "a pipeline must allow the same named pass to run more than once");
+
+ rcc::MachinePassInstrumentation Instrumentation;
+ std::vector<std::string> ObservedFunctions;
+ std::vector<std::size_t> BeforeBlockCounts;
+ std::vector<std::size_t> AfterBlockCounts;
+ Instrumentation.addBeforeCallback([&ObservedFunctions, &BeforeBlockCounts](
+ std::string_view, const rcc::MachineFunction &Function) {
+ ObservedFunctions.emplace_back(Function.name());
+ BeforeBlockCounts.push_back(Function.blockCount());
+ });
+ Instrumentation.addAfterCallback([&AfterBlockCounts](std::string_view,
+ const rcc::MachineFunction &Function,
+ const rcc::MachinePassResult &) {
+ AfterBlockCounts.push_back(Function.blockCount());
+ });
+ std::atomic<std::uint32_t> InstrumentedRuns = 0;
+ std::atomic<std::uint32_t> ActiveRuns = 0;
+ std::atomic<std::uint32_t> MaximumActiveRuns = 0;
+ rcc::MachinePassManager Instrumented;
+ Context.expect(
+ Instrumented
+ .addPass(std::make_unique<InstrumentedParallelPass>(InstrumentedRuns, ActiveRuns,
+ MaximumActiveRuns))
+ .has_value() &&
+ Instrumented.runFunctions(Unit, Target, Prototype, 4, &Instrumentation).has_value() &&
+ InstrumentedRuns == Unit.functionCount() && MaximumActiveRuns > 1,
+ "instrumented function pipelines must execute concurrently with a parallel request");
+ bool StableCallbackOrder = ObservedFunctions.size() == Unit.functionCount();
+ for (std::size_t Index = 0; StableCallbackOrder && Index < ObservedFunctions.size(); ++Index)
+ StableCallbackOrder = ObservedFunctions[Index] == "f" + std::to_string(Index);
+ Context.expect(StableCallbackOrder,
+ "instrumentation callbacks must commit in stable compilation-unit order");
+ Context.expect(
+ BeforeBlockCounts.size() == Unit.functionCount() &&
+ AfterBlockCounts.size() == Unit.functionCount() &&
+ std::ranges::all_of(BeforeBlockCounts, [](std::size_t Count) { return Count == 0; }) &&
+ std::ranges::all_of(AfterBlockCounts, [](std::size_t Count) { return Count == 1; }),
+ "buffered instrumentation must retain distinct before/after IR snapshots");
+
+ rcc::MachineAnalysisManager UnsafePrototype;
+ Context.expect(UnsafePrototype
+ .registerAnalysis<NonThreadSafeAnalysis>({
+ .Name = "NonThreadSafe",
+ .Dependencies = {},
+ .InvalidatedBy = rcc::MachinePassChanges::all(),
+ .ThreadSafety = rcc::PassThreadSafety::NotThreadSafe,
+ })
+ .has_value(),
+ "non-thread-safe analysis registration must be explicit");
+ rcc::MachinePassManager UnsafeParallel;
+ Context.expect(
+ UnsafeParallel.addPass(std::make_unique<RequiresNonThreadSafeAnalysisPass>()).has_value() &&
+ !UnsafeParallel.runFunctions(Unit, Target, UnsafePrototype, 4),
+ "parallel pipelines must reject a required analysis not marked thread-safe");
+
+ rcc::CompilationUnitAnalysisManager UnitAnalyses;
+ Context.expect(UnitAnalyses
+ .registerAnalysis<UnitFunctionCountAnalysis>({
+ .Name = "UnitFunctionCount",
+ .Dependencies = {},
+ .InvalidatedBy = rcc::MachinePassChanges::all(),
+ })
+ .has_value(),
+ "compilation-unit analysis registration must succeed");
+ rcc::MachineAnalysisManager FunctionAnalyses;
+ rcc::CompilationUnitPassManager UnitPipeline;
+ Context.expect(UnitPipeline.addPass(std::make_unique<ObserveUnitPass>()).has_value(),
+ "compilation-unit pass registration must succeed");
+ rcc::MachinePassInstrumentation UnitInstrumentation;
+ std::uint32_t BeforeUnit = 0;
+ std::uint32_t AfterUnit = 0;
+ std::uint32_t BeforeUnitAnalysis = 0;
+ std::uint32_t AfterUnitAnalysis = 0;
+ std::uint32_t UnitAnalysisCacheHits = 0;
+ UnitInstrumentation.addBeforeUnitCallback(
+ [&BeforeUnit](std::string_view, const rcc::CompilationUnit &) { ++BeforeUnit; });
+ UnitInstrumentation.addAfterUnitCallback(
+ [&AfterUnit](std::string_view, const rcc::CompilationUnit &,
+ const rcc::CompilationUnitPassResult &) { ++AfterUnit; });
+ UnitInstrumentation.addBeforeUnitAnalysisCallback(
+ [&BeforeUnitAnalysis](std::string_view, const rcc::CompilationUnit &) {
+ ++BeforeUnitAnalysis;
+ });
+ UnitInstrumentation.addAfterUnitAnalysisCallback(
+ [&AfterUnitAnalysis, &UnitAnalysisCacheHits](std::string_view, const rcc::CompilationUnit &,
+ bool CacheHit, std::uint64_t) {
+ ++AfterUnitAnalysis;
+ UnitAnalysisCacheHits += CacheHit ? 1U : 0U;
+ });
+ Context.expect(
+ UnitPipeline.run(Unit, Target, UnitAnalyses, FunctionAnalyses, &UnitInstrumentation)
+ .has_value(),
+ "compilation-unit pass and analysis must run through their own manager");
+ Context.expect(BeforeUnit == 1 && AfterUnit == 1 && BeforeUnitAnalysis == 1 &&
+ AfterUnitAnalysis == 2 && UnitAnalysisCacheHits == 1,
+ "compilation-unit instrumentation must report pass and analysis cache events");
+
+ rcc::CompilationUnitAnalysisManager FailingUnitAnalyses;
+ Context.expect(FailingUnitAnalyses
+ .registerAnalysis<FailingUnitAnalysis>({
+ .Name = "FailingUnit",
+ .Dependencies = {},
+ })
+ .has_value(),
+ "failing compilation-unit analysis registration must succeed");
+ rcc::CompilationUnitPassManager FailingUnitAnalysisPipeline;
+ Context.expect(
+ FailingUnitAnalysisPipeline.addPass(std::make_unique<RequiresFailingUnitAnalysisPass>())
+ .has_value(),
+ "a pass requiring a failing unit analysis must register");
+ rcc::MachinePassInstrumentation FailingUnitInstrumentation;
+ std::uint32_t BeforeFailingUnitAnalysis = 0;
+ std::uint32_t FailingUnitAnalysisEvents = 0;
+ std::uint32_t RequiredUnitAnalysisPassFailures = 0;
+ FailingUnitInstrumentation.addBeforeUnitAnalysisCallback(
+ [&BeforeFailingUnitAnalysis](std::string_view, const rcc::CompilationUnit &) {
+ ++BeforeFailingUnitAnalysis;
+ });
+ FailingUnitInstrumentation.addUnitAnalysisFailureCallback(
+ [&FailingUnitAnalysisEvents](std::string_view, const rcc::CompilationUnit &,
+ const rcc::Error &) { ++FailingUnitAnalysisEvents; });
+ FailingUnitInstrumentation.addUnitFailureCallback(
+ [&RequiredUnitAnalysisPassFailures](std::string_view, const rcc::CompilationUnit &,
+ const rcc::Error &) {
+ ++RequiredUnitAnalysisPassFailures;
+ });
+ Context.expect(
+ !FailingUnitAnalysisPipeline.run(Unit, Target, FailingUnitAnalyses, FunctionAnalyses,
+ &FailingUnitInstrumentation) &&
+ BeforeFailingUnitAnalysis == 1 && FailingUnitAnalysisEvents == 1 &&
+ RequiredUnitAnalysisPassFailures == 1,
+ "unit analysis and pass failure instrumentation must report required-analysis failures");
+
+ rcc::MachineFunction *FirstFunction = Unit.functionAt(0);
+ Context.expect(
+ FirstFunction != nullptr &&
+ FunctionAnalyses
+ .registerAnalysis<BlockCountAnalysis>({
+ .Name = "BlockCount",
+ .Dependencies = {},
+ .InvalidatedBy = rcc::MachinePassChanges::all(),
+ })
+ .has_value() &&
+ FunctionAnalyses.getResult<BlockCountAnalysis>(*FirstFunction, Target).has_value(),
+ "compilation-unit failure cache fixtures must materialize");
+ rcc::CompilationUnitPassManager FailingUnitPipeline;
+ Context.expect(
+ FirstFunction != nullptr &&
+ FailingUnitPipeline.addPass(std::make_unique<MutatingUnitFailurePass>()).has_value() &&
+ !FailingUnitPipeline.run(Unit, Target, UnitAnalyses, FunctionAnalyses) &&
+ !UnitAnalyses.hasCached(
+ Unit, rcc::CompilationUnitAnalysisManager::id<UnitFunctionCountAnalysis>()) &&
+ !FunctionAnalyses.hasCached(*FirstFunction,
+ rcc::MachineAnalysisManager::id<BlockCountAnalysis>()),
+ "a mutating compilation-unit failure must clear unit and function analysis caches");
+}
+
+} // namespace
+
+int main() {
+ TestContext Context;
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "machine pass tests require a valid target description");
+ if (!Target)
+ return Context.result();
+
+ testAnalysisInfrastructure(Context, *Target);
+ testPassAndInstrumentation(Context, *Target);
+ testCompilationUnitAndParallelism(Context, *Target);
+ return Context.result();
+}
diff --git a/backend/unittests/CodeGen/TargetCallLoweringTest.cpp b/backend/unittests/CodeGen/TargetCallLoweringTest.cpp
new file mode 100644
index 0000000..f826285
--- /dev/null
+++ b/backend/unittests/CodeGen/TargetCallLoweringTest.cpp
@@ -0,0 +1,329 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/TargetCallLowering.h"
+
+#include "Target/TestTarget.h"
+#include "rcc/CodeGen/InstructionDescriptor.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineIRBuilder.h"
+
+#include <concepts>
+#include <cstdint>
+#include <iostream>
+#include <memory>
+#include <span>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace {
+
+class TestContext final {
+public:
+ void expect(bool Condition, std::string_view Message) {
+ if (Condition)
+ return;
+ std::cerr << Message << '\n';
+ ++FailureCount;
+ }
+
+ [[nodiscard]] int result() const noexcept {
+ return FailureCount == 0 ? 0 : 1;
+ }
+
+private:
+ int FailureCount = 0;
+};
+
+[[nodiscard]] rcc::Alignment align(std::uint64_t Bytes) {
+ const auto Value = rcc::Alignment::create(Bytes);
+ if (!Value)
+ std::terminate();
+ return *Value;
+}
+
+[[nodiscard]] rcc::LLT integer(std::uint32_t Bits) {
+ const auto Type = rcc::LLT::makeInteger(Bits);
+ if (!Type)
+ std::terminate();
+ return *Type;
+}
+
+[[nodiscard]] rcc::PhysicalRegister physical(std::uint32_t ID) {
+ const auto Register = rcc::PhysicalRegister::create(ID);
+ if (!Register)
+ std::terminate();
+ return *Register;
+}
+
+[[nodiscard]] rcc::Expected<rcc::TargetDescription> createTarget() {
+ rcc::InstructionDescriptor Nop;
+ Nop.InstructionOpcode = rcc::Opcode::fromValue(1);
+ Nop.Name = "TEST_NOP";
+ Nop.Flags = {rcc::InstructionFlag::ProducesCode};
+ std::vector<rcc::InstructionDescriptor> Descriptors;
+ Descriptors.push_back(std::move(Nop));
+ auto Instructions = rcc::InstructionSet::create(std::move(Descriptors));
+ if (!Instructions)
+ return std::unexpected(Instructions.error());
+ return rcc::test::createTestTargetDescription(std::move(*Instructions));
+}
+
+[[nodiscard]] rcc::CallLoweringPlanDesc emptyPlanDescription(rcc::CallLoweringPlanKind Kind) {
+ rcc::CallLoweringPlanDesc Description;
+ Description.Kind = Kind;
+ Description.Target = "test-target:test-abi";
+ Description.Convention = rcc::CallingConventionID::fromValue(1);
+ Description.StackArguments.Align = align(8);
+ Description.CallFrame.Align = align(8);
+ if (Kind == rcc::CallLoweringPlanKind::Call) {
+ Description.CallFrame.EmitSetup = true;
+ Description.CallFrame.EmitDestroy = true;
+ Description.PreservedMask = rcc::test::createTestCallPreservedMask();
+ Description.Callee = *rcc::MachineCallTarget::externalSymbol("callee");
+ Description.CallSiteID = 1;
+ }
+ return Description;
+}
+
+[[nodiscard]] rcc::Expected<rcc::CallLoweringPlan> emptyPlan(rcc::CallLoweringPlanKind Kind) {
+ return rcc::CallLoweringPlan::create(emptyPlanDescription(Kind));
+}
+
+[[nodiscard]] rcc::CCAssignment registerAssignment() {
+ return {
+ .ValueIndex = 0,
+ .OriginalValue = rcc::Register::fromPhysical(physical(2)),
+ .OriginalValueIndex = 0,
+ .FragmentIndex = 0,
+ .FragmentCount = 1,
+ .ValueType = integer(64),
+ .LocationType = integer(64),
+ .LocationKind = rcc::CCLocationKind::Register,
+ .LocationRegister = physical(1),
+ .StackOffsetBytes = std::nullopt,
+ .LocationSizeBytes = 8,
+ .StackSlotSizeBytes = 8,
+ .LocationAlignment = align(8),
+ .Extension = rcc::ABIExtensionKind::None,
+ .CustomFlags = {},
+ };
+}
+
+void testPlanValidation(TestContext &Context) {
+ static_assert(
+ std::same_as<decltype(std::declval<const rcc::CallLoweringPlan &>().argumentAssignments()),
+ std::span<const rcc::CCAssignment>>);
+ static_assert(std::same_as<decltype(std::declval<const rcc::CallLoweringPlan &>().byValCopies()),
+ std::span<const rcc::ByValCopyAssignment>>);
+
+ auto Valid = emptyPlan(rcc::CallLoweringPlanKind::FormalArguments);
+ Context.expect(Valid && Valid->target() == "test-target:test-abi" &&
+ Valid->argumentAssignments().empty(),
+ "a valid immutable formal-argument plan must be accepted");
+
+ auto MissingRegister = emptyPlanDescription(rcc::CallLoweringPlanKind::FormalArguments);
+ auto Assignment = registerAssignment();
+ Assignment.OriginalValue = {};
+ MissingRegister.ArgumentAssignments.push_back(Assignment);
+ Context.expect(!rcc::CallLoweringPlan::create(std::move(MissingRegister)),
+ "an assignment without a machine value must be rejected");
+
+ auto BadStack = emptyPlanDescription(rcc::CallLoweringPlanKind::FormalArguments);
+ BadStack.StackArguments.SizeBytes = 8;
+ auto StackAssignment = registerAssignment();
+ StackAssignment.LocationKind = rcc::CCLocationKind::Stack;
+ StackAssignment.LocationRegister.reset();
+ StackAssignment.StackOffsetBytes = 8;
+ BadStack.ArgumentAssignments.push_back(StackAssignment);
+ Context.expect(!rcc::CallLoweringPlan::create(std::move(BadStack)),
+ "an assignment outside the declared stack area must be rejected");
+
+ auto Overlap = emptyPlanDescription(rcc::CallLoweringPlanKind::FormalArguments);
+ Overlap.StackArguments.SizeBytes = 16;
+ StackAssignment.StackOffsetBytes = 0;
+ Overlap.ArgumentAssignments.push_back(StackAssignment);
+ StackAssignment.OriginalValue = rcc::Register::fromPhysical(physical(3));
+ StackAssignment.StackOffsetBytes = 4;
+ Overlap.ArgumentAssignments.push_back(StackAssignment);
+ Context.expect(!rcc::CallLoweringPlan::create(std::move(Overlap)),
+ "overlapping stack assignments must be rejected");
+
+ auto MisalignedStack = emptyPlanDescription(rcc::CallLoweringPlanKind::FormalArguments);
+ MisalignedStack.StackArguments.SizeBytes = 16;
+ StackAssignment.StackOffsetBytes = 4;
+ MisalignedStack.ArgumentAssignments.push_back(StackAssignment);
+ Context.expect(!rcc::CallLoweringPlan::create(std::move(MisalignedStack)),
+ "a stack assignment must satisfy its declared alignment");
+
+ auto OversizedLocation = emptyPlanDescription(rcc::CallLoweringPlanKind::FormalArguments);
+ auto OversizedAssignment = registerAssignment();
+ OversizedAssignment.LocationSizeBytes = 16;
+ OversizedLocation.ArgumentAssignments.push_back(OversizedAssignment);
+ Context.expect(!rcc::CallLoweringPlan::create(std::move(OversizedLocation)),
+ "an ABI location cannot exceed its transport slot");
+
+ auto UnknownLocation = emptyPlanDescription(rcc::CallLoweringPlanKind::FormalArguments);
+ auto UnknownAssignment = registerAssignment();
+ UnknownAssignment.LocationKind =
+ static_cast<rcc::CCLocationKind>(255); // NOLINT(clang-analyzer-optin.core.EnumCastOutOfRange)
+ UnknownLocation.ArgumentAssignments.push_back(UnknownAssignment);
+ Context.expect(!rcc::CallLoweringPlan::create(std::move(UnknownLocation)),
+ "an unknown ABI location kind must be rejected");
+
+ auto BadFrame = emptyPlanDescription(rcc::CallLoweringPlanKind::Call);
+ BadFrame.CallFrame.EmitDestroy = false;
+ Context.expect(!rcc::CallLoweringPlan::create(std::move(BadFrame)),
+ "unpaired call-frame setup and destroy requirements must be rejected");
+
+ auto SmallFrame = emptyPlanDescription(rcc::CallLoweringPlanKind::Call);
+ SmallFrame.StackArguments.SizeBytes = 16;
+ SmallFrame.CallFrame.SizeBytes = 8;
+ StackAssignment.StackOffsetBytes = 0;
+ SmallFrame.ArgumentAssignments.push_back(StackAssignment);
+ Context.expect(!rcc::CallLoweringPlan::create(std::move(SmallFrame)),
+ "a call frame must contain its complete stack-argument area");
+
+ auto BadTail = emptyPlanDescription(rcc::CallLoweringPlanKind::Call);
+ BadTail.CallFrame.EmitSetup = false;
+ BadTail.CallFrame.EmitDestroy = false;
+ BadTail.CallFrame.ReusesIncomingArea = true;
+ BadTail.TailDecision = rcc::TailCallKind::Tail;
+ BadTail.TailRejectionReason = "inconsistent accepted tail call";
+ Context.expect(!rcc::CallLoweringPlan::create(std::move(BadTail)),
+ "an accepted tail call cannot retain a rejection reason");
+
+ auto UnknownTail = emptyPlanDescription(rcc::CallLoweringPlanKind::Call);
+ UnknownTail.TailDecision =
+ static_cast<rcc::TailCallKind>(255); // NOLINT(clang-analyzer-optin.core.EnumCastOutOfRange)
+ Context.expect(!rcc::CallLoweringPlan::create(std::move(UnknownTail)),
+ "an unknown tail-call decision must be rejected");
+
+ auto BadVarArgs = emptyPlanDescription(rcc::CallLoweringPlanKind::FormalArguments);
+ BadVarArgs.VarArgs.IsVariadic = true;
+ BadVarArgs.VarArgs.RegisterSaveAreaSizeBytes = 8;
+ BadVarArgs.VarArgs.RegistersToSave.push_back({physical(1), 8});
+ Context.expect(!rcc::CallLoweringPlan::create(std::move(BadVarArgs)),
+ "a variadic register save outside its area must be rejected");
+
+ auto BadByVal = emptyPlanDescription(rcc::CallLoweringPlanKind::Call);
+ auto ByValAssignment = registerAssignment();
+ ByValAssignment.ValueType = *rcc::LLT::makePointer(0, 64);
+ ByValAssignment.LocationType = ByValAssignment.ValueType;
+ ByValAssignment.CustomFlags.set(rcc::CCAssignmentFlag::ByVal);
+ BadByVal.ArgumentAssignments.push_back(ByValAssignment);
+ BadByVal.ByValCopies.push_back(
+ {0, ByValAssignment.OriginalValue, ByValAssignment.ValueType, 0, 16, align(8)});
+ Context.expect(!rcc::CallLoweringPlan::create(std::move(BadByVal)),
+ "a byval copy outside the call frame must be rejected");
+
+ auto OverlappingByVal = emptyPlanDescription(rcc::CallLoweringPlanKind::Call);
+ OverlappingByVal.CallFrame.SizeBytes = 32;
+ auto FirstByVal = registerAssignment();
+ FirstByVal.ValueType = *rcc::LLT::makePointer(0, 64);
+ FirstByVal.LocationType = FirstByVal.ValueType;
+ FirstByVal.CustomFlags.set(rcc::CCAssignmentFlag::ByVal);
+ auto SecondByVal = FirstByVal;
+ SecondByVal.ValueIndex = 1;
+ SecondByVal.OriginalValue = rcc::Register::fromPhysical(physical(3));
+ SecondByVal.OriginalValueIndex = 1;
+ SecondByVal.LocationRegister = physical(2);
+ OverlappingByVal.ArgumentAssignments = {FirstByVal, SecondByVal};
+ OverlappingByVal.ByValCopies = {
+ {0, FirstByVal.OriginalValue, FirstByVal.ValueType, 0, 16, align(8)},
+ {1, SecondByVal.OriginalValue, SecondByVal.ValueType, 8, 16, align(8)},
+ };
+ Context.expect(!rcc::CallLoweringPlan::create(std::move(OverlappingByVal)),
+ "overlapping byval copy ranges must be rejected");
+
+ Context.expect(!rcc::MachineCallTarget::globalSymbol("") && !rcc::MachineCallTarget::indirect({}),
+ "call-target factories must reject missing targets");
+}
+
+class RecordingCallLowering final : public rcc::TargetCallLowering {
+public:
+ enum class Behavior : std::uint8_t {
+ Valid,
+ PlannerFailure,
+ WrongKind,
+ };
+
+ explicit RecordingCallLowering(Behavior CurrentBehavior) : CurrentBehavior(CurrentBehavior) {}
+
+ [[nodiscard]] rcc::Expected<rcc::CallLoweringPlan>
+ planFormalArguments(const rcc::FormalArgumentRequest &) const override {
+ return plan(rcc::CallLoweringPlanKind::FormalArguments);
+ }
+
+ [[nodiscard]] rcc::Expected<rcc::CallLoweringPlan>
+ planCall(const rcc::CallLoweringRequest &) const override {
+ return plan(rcc::CallLoweringPlanKind::Call);
+ }
+
+ [[nodiscard]] rcc::Expected<rcc::CallLoweringPlan>
+ planReturn(const rcc::ReturnLoweringRequest &) const override {
+ return plan(rcc::CallLoweringPlanKind::Return);
+ }
+
+ [[nodiscard]] rcc::Expected<void> emitPlan(rcc::MachineIRBuilder &,
+ const rcc::CallLoweringPlan &) const override {
+ ++EmissionCount;
+ return {};
+ }
+
+ [[nodiscard]] std::uint32_t emissions() const noexcept {
+ return EmissionCount;
+ }
+
+private:
+ [[nodiscard]] rcc::Expected<rcc::CallLoweringPlan>
+ plan(rcc::CallLoweringPlanKind RequestedKind) const {
+ if (CurrentBehavior == Behavior::PlannerFailure) {
+ return rcc::makeUnexpected(rcc::ErrorCode::Unsupported,
+ "intentional call-lowering planner failure");
+ }
+ if (CurrentBehavior == Behavior::WrongKind)
+ RequestedKind = RequestedKind == rcc::CallLoweringPlanKind::Call
+ ? rcc::CallLoweringPlanKind::Return
+ : rcc::CallLoweringPlanKind::Call;
+ return emptyPlan(RequestedKind);
+ }
+
+ Behavior CurrentBehavior;
+ mutable std::uint32_t EmissionCount = 0;
+};
+
+void testPlanThenEmitBoundary(TestContext &Context) {
+ auto Target = createTarget();
+ auto Function =
+ rcc::MachineFunction::create("plan_then_emit", rcc::MachineFunctionStableID::fromValue(1));
+ Context.expect(Target && Function, "call-lowering workflow fixtures must be valid");
+ if (!Target || !Function)
+ return;
+ rcc::MachineIRBuilder Builder(**Function, *Target);
+ rcc::CallLoweringRequest Request;
+
+ RecordingCallLowering Failing(RecordingCallLowering::Behavior::PlannerFailure);
+ auto Failure = Failing.lowerCall(Builder, Request);
+ Context.expect(!Failure && Failing.emissions() == 0 && (*Function)->allInstructions().empty(),
+ "planner failure must not invoke emission or mutate MachineIR");
+
+ RecordingCallLowering WrongKind(RecordingCallLowering::Behavior::WrongKind);
+ auto Mismatch = WrongKind.lowerCall(Builder, Request);
+ Context.expect(!Mismatch && Mismatch.error().code() == rcc::ErrorCode::InternalError &&
+ WrongKind.emissions() == 0,
+ "a target returning the wrong plan kind must be rejected before emission");
+
+ RecordingCallLowering Valid(RecordingCallLowering::Behavior::Valid);
+ Context.expect(Valid.lowerCall(Builder, Request) && Valid.emissions() == 1,
+ "a valid plan must be emitted exactly once");
+}
+
+} // namespace
+
+int main() {
+ TestContext Context;
+ testPlanValidation(Context);
+ testPlanThenEmitBoundary(Context);
+ return Context.result();
+}
diff --git a/backend/unittests/Target/CMakeLists.txt b/backend/unittests/Target/CMakeLists.txt
index 1ceb1ef..aa45b90 100644
--- a/backend/unittests/Target/CMakeLists.txt
+++ b/backend/unittests/Target/CMakeLists.txt
@@ -23,14 +23,21 @@ rcc_backend_configure_target(RCCBackendRISCVTargetTests)
target_link_libraries(RCCBackendRISCVTargetTests PRIVATE RCCBackend::RISCV)
add_test(NAME RCCBackendRISCVTargetTests COMMAND RCCBackendRISCVTargetTests)
+add_executable(RCCBackendRISCVCallLoweringTests RISCVCallLoweringTest.cpp)
+rcc_backend_configure_target(RCCBackendRISCVCallLoweringTests)
+target_link_libraries(RCCBackendRISCVCallLoweringTests PRIVATE RCCBackend::RISCV)
+add_test(NAME RCCBackendRISCVCallLoweringTests COMMAND RCCBackendRISCVCallLoweringTests)
+
rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/DescriptionGeneratorTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVCallLoweringTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVTargetTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TestTarget.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TestTarget.h"
)
rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/DescriptionGeneratorTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVCallLoweringTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVTargetTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TestTarget.cpp"
)
diff --git a/backend/unittests/Target/RISCVCallLoweringTest.cpp b/backend/unittests/Target/RISCVCallLoweringTest.cpp
new file mode 100644
index 0000000..0262bf5
--- /dev/null
+++ b/backend/unittests/Target/RISCVCallLoweringTest.cpp
@@ -0,0 +1,802 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/Target/RISCV/RISCVCallLowering.h"
+
+#include "rcc/CodeGen/MachineBasicBlock.h"
+#include "rcc/CodeGen/MachineFrameInfo.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineIRBuilder.h"
+#include "rcc/CodeGen/MachineInstr.h"
+#include "rcc/CodeGen/MachineMemOperand.h"
+#include "rcc/CodeGen/MachineOperand.h"
+#include "rcc/CodeGen/MachineVerifier.h"
+#include "rcc/Target/RISCV/RISCVTarget.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <exception>
+#include <iostream>
+#include <memory>
+#include <optional>
+#include <ranges>
+#include <span>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace {
+
+class TestContext final {
+public:
+ void expect(bool Condition, std::string_view Message) {
+ if (Condition)
+ return;
+ std::cerr << Message << '\n';
+ ++FailureCount;
+ }
+
+ [[nodiscard]] int result() const noexcept {
+ return FailureCount == 0 ? 0 : 1;
+ }
+
+private:
+ int FailureCount = 0;
+};
+
+[[nodiscard]] rcc::CallingConventionID convention() noexcept {
+ return rcc::riscv::callingConventionID(rcc::riscv::CallingConvention::LP64D);
+}
+
+[[nodiscard]] rcc::Alignment align(std::uint64_t Bytes) {
+ const auto Value = rcc::Alignment::create(Bytes);
+ if (!Value)
+ std::terminate();
+ return *Value;
+}
+
+[[nodiscard]] rcc::LLT integer(std::uint32_t Bits) {
+ const auto Type = rcc::LLT::makeInteger(Bits);
+ if (!Type)
+ std::terminate();
+ return *Type;
+}
+
+[[nodiscard]] rcc::LLT floating(rcc::FloatSemantics Semantics) {
+ const auto Type = rcc::LLT::makeFloat(Semantics);
+ if (!Type)
+ std::terminate();
+ return *Type;
+}
+
+[[nodiscard]] rcc::LLT pointer() {
+ const auto Type = rcc::LLT::makePointer(0, 64);
+ if (!Type)
+ std::terminate();
+ return *Type;
+}
+
+[[nodiscard]] rcc::PhysicalRegister physical(rcc::riscv::PhysicalRegisterID ID) {
+ const auto Register = rcc::riscv::physicalRegister(ID);
+ if (!Register)
+ std::terminate();
+ return *Register;
+}
+
+[[nodiscard]] rcc::PhysicalRegister argumentGPR(std::size_t Index) {
+ const auto *Convention = rcc::riscv::callingConvention(rcc::riscv::CallingConvention::LP64D);
+ if (Convention == nullptr || Index >= Convention->IntegerArgumentRegisters.size())
+ std::terminate();
+ return Convention->IntegerArgumentRegisters[Index];
+}
+
+[[nodiscard]] rcc::PhysicalRegister argumentFPR(std::size_t Index) {
+ const auto *Convention = rcc::riscv::callingConvention(rcc::riscv::CallingConvention::LP64D);
+ if (Convention == nullptr || Index >= Convention->FloatingArgumentRegisters.size())
+ std::terminate();
+ return Convention->FloatingArgumentRegisters[Index];
+}
+
+[[nodiscard]] rcc::PhysicalRegister returnGPR(std::size_t Index) {
+ const auto *Convention = rcc::riscv::callingConvention(rcc::riscv::CallingConvention::LP64D);
+ if (Convention == nullptr || Index >= Convention->IntegerReturnRegisters.size())
+ std::terminate();
+ return Convention->IntegerReturnRegisters[Index];
+}
+
+[[nodiscard]] rcc::PhysicalRegister returnFPR(std::size_t Index) {
+ const auto *Convention = rcc::riscv::callingConvention(rcc::riscv::CallingConvention::LP64D);
+ if (Convention == nullptr || Index >= Convention->FloatingReturnRegisters.size())
+ std::terminate();
+ return Convention->FloatingReturnRegisters[Index];
+}
+
+[[nodiscard]] rcc::Register planningRegister() {
+ return rcc::Register::fromPhysical(physical(rcc::riscv::PhysicalRegisterID::X5));
+}
+
+[[nodiscard]] rcc::ABIValue
+abiValue(rcc::LLT Type, std::uint32_t OriginalValue, rcc::Register Value = planningRegister(),
+ rcc::ABIExtensionKind Extension = rcc::ABIExtensionKind::None) {
+ const std::uint64_t Size = std::max<std::uint64_t>(1, (Type.scalarBitWidth() + 7U) / 8U);
+ return {
+ .Value = Value,
+ .Type = Type,
+ .OriginalValue = OriginalValue,
+ .FragmentIndex = 0,
+ .FragmentCount = 1,
+ .FragmentOffsetBytes = 0,
+ .OriginalAlignment = align(std::min<std::uint64_t>(Size, 8)),
+ .Extension = Extension,
+ .Flags = {},
+ .ByValSizeBytes = 0,
+ .ByValAlignment = align(1),
+ };
+}
+
+[[nodiscard]] std::vector<rcc::ABIValue> integerValues(std::size_t Count) {
+ std::vector<rcc::ABIValue> Values;
+ Values.reserve(Count);
+ for (std::size_t Index = 0; Index < Count; ++Index)
+ Values.push_back(abiValue(integer(64), static_cast<std::uint32_t>(Index)));
+ return Values;
+}
+
+[[nodiscard]] std::vector<rcc::ABIValue> floatingValues(std::size_t Count) {
+ std::vector<rcc::ABIValue> Values;
+ Values.reserve(Count);
+ for (std::size_t Index = 0; Index < Count; ++Index) {
+ Values.push_back(
+ abiValue(floating(rcc::FloatSemantics::IEEEDouble), static_cast<std::uint32_t>(Index)));
+ }
+ return Values;
+}
+
+[[nodiscard]] rcc::MachineCallTarget externalTarget() {
+ auto Target = rcc::MachineCallTarget::externalSymbol("callee");
+ if (!Target)
+ std::terminate();
+ return std::move(*Target);
+}
+
+[[nodiscard]] rcc::CallLoweringRequest callRequest(std::vector<rcc::ABIValue> Arguments,
+ std::size_t FixedArgumentCount,
+ bool IsVariadic = false) {
+ rcc::CallLoweringRequest Request;
+ Request.Convention = convention();
+ Request.Callee = externalTarget();
+ Request.Arguments = std::move(Arguments);
+ Request.FixedArgumentCount = FixedArgumentCount;
+ Request.CallSiteID = 17;
+ Request.IsVariadic = IsVariadic;
+ return Request;
+}
+
+[[nodiscard]] bool isLocation(const rcc::CCAssignment &Assignment, rcc::PhysicalRegister Register) {
+ return Assignment.LocationRegister && *Assignment.LocationRegister == Register;
+}
+
+void testIntegerAndFloatingAssignments(TestContext &Context) {
+ auto Integers = callRequest(integerValues(9), 9);
+ auto IntegerPlan = rcc::riscv::callLowering().planCall(Integers);
+ Context.expect(IntegerPlan.has_value(), "nine integer arguments must produce an LP64D call plan");
+ if (!IntegerPlan)
+ return;
+ const auto IntegerAssignments = IntegerPlan->argumentAssignments();
+ bool IntegerRegistersMatch = IntegerAssignments.size() == 9;
+ for (std::size_t Index = 0; IntegerRegistersMatch && Index < 8; ++Index)
+ IntegerRegistersMatch = isLocation(IntegerAssignments[Index], argumentGPR(Index));
+ Context.expect(IntegerRegistersMatch &&
+ IntegerAssignments[8].LocationKind == rcc::CCLocationKind::Stack &&
+ IntegerAssignments[8].StackOffsetBytes == 0 &&
+ IntegerPlan->stackArguments().SizeBytes == 16 &&
+ IntegerPlan->callFrame().SizeBytes == 16 &&
+ IntegerPlan->callee()->targetFlags() ==
+ static_cast<std::uint32_t>(rcc::riscv::CallTargetFlag::DirectCall),
+ "LP64D integer arguments must use a0-a7 before a 16-byte stack area");
+
+ auto Floats = callRequest(floatingValues(17), 17);
+ auto FloatPlan = rcc::riscv::callLowering().planCall(Floats);
+ Context.expect(FloatPlan.has_value(),
+ "seventeen floating arguments must produce an LP64D call plan");
+ if (!FloatPlan)
+ return;
+ const auto FloatAssignments = FloatPlan->argumentAssignments();
+ bool FloatingRegistersMatch = FloatAssignments.size() == 17;
+ for (std::size_t Index = 0; FloatingRegistersMatch && Index < 8; ++Index)
+ FloatingRegistersMatch = isLocation(FloatAssignments[Index], argumentFPR(Index));
+ for (std::size_t Index = 0; FloatingRegistersMatch && Index < 8; ++Index) {
+ FloatingRegistersMatch =
+ isLocation(FloatAssignments[Index + 8], argumentGPR(Index)) &&
+ FloatAssignments[Index + 8].CustomFlags.has(rcc::CCAssignmentFlag::Bitcast);
+ }
+ Context.expect(FloatingRegistersMatch &&
+ FloatAssignments[16].LocationKind == rcc::CCLocationKind::Stack &&
+ FloatAssignments[16].StackOffsetBytes == 0,
+ "LP64D floating arguments must fall back from fa0-fa7 to a0-a7 and then stack");
+
+ auto FloatFragment = abiValue(floating(rcc::FloatSemantics::IEEEDouble), 0);
+ FloatFragment.FragmentCount = 2;
+ FloatFragment.OriginalAlignment = align(16);
+ auto IntegerFragment = abiValue(integer(64), 0);
+ IntegerFragment.FragmentIndex = 1;
+ IntegerFragment.FragmentCount = 2;
+ IntegerFragment.FragmentOffsetBytes = 8;
+ IntegerFragment.OriginalAlignment = align(16);
+ auto Mixed = callRequest({FloatFragment, IntegerFragment}, 1);
+ auto MixedPlan = rcc::riscv::callLowering().planCall(Mixed);
+ Context.expect(MixedPlan && MixedPlan->argumentAssignments().size() == 2 &&
+ isLocation(MixedPlan->argumentAssignments()[0], argumentFPR(0)) &&
+ isLocation(MixedPlan->argumentAssignments()[1], argumentGPR(0)),
+ "a mixed floating/integer aggregate must use one FPR and one GPR");
+
+ auto Narrow = abiValue(integer(32), 0);
+ auto InvalidNarrow = callRequest({Narrow}, 1);
+ Context.expect(!rcc::riscv::callLowering().planCall(InvalidNarrow),
+ "a narrow integer without an explicit ABI extension must be rejected");
+ Narrow.Extension = rcc::ABIExtensionKind::SignExtend;
+ auto ExtendedNarrow = callRequest({Narrow}, 1);
+ auto NarrowPlan = rcc::riscv::callLowering().planCall(ExtendedNarrow);
+ Context.expect(NarrowPlan && NarrowPlan->argumentAssignments()[0].LocationType == integer(64) &&
+ NarrowPlan->argumentAssignments()[0].Extension ==
+ rcc::ABIExtensionKind::SignExtend,
+ "a narrow integer extension must be explicit in its LP64D assignment");
+}
+
+void testVariadicAssignments(TestContext &Context) {
+ auto FixedFloat = abiValue(floating(rcc::FloatSemantics::IEEEDouble), 0);
+ auto VariableFloat = abiValue(floating(rcc::FloatSemantics::IEEEDouble), 1);
+ auto Request = callRequest({FixedFloat, VariableFloat}, 1, true);
+ auto Plan = rcc::riscv::callLowering().planCall(Request);
+ Context.expect(
+ Plan && isLocation(Plan->argumentAssignments()[0], argumentFPR(0)) &&
+ isLocation(Plan->argumentAssignments()[1], argumentGPR(0)) &&
+ Plan->argumentAssignments()[1].CustomFlags.has(rcc::CCAssignmentFlag::Variadic) &&
+ Plan->argumentAssignments()[1].CustomFlags.has(rcc::CCAssignmentFlag::Bitcast),
+ "variadic floating arguments must use integer calling-convention transport");
+
+ auto FixedInteger = abiValue(integer(64), 0);
+ auto PairLow = abiValue(integer(64), 1);
+ PairLow.FragmentCount = 2;
+ PairLow.OriginalAlignment = align(16);
+ auto PairHigh = abiValue(integer(64), 1);
+ PairHigh.FragmentIndex = 1;
+ PairHigh.FragmentCount = 2;
+ PairHigh.FragmentOffsetBytes = 8;
+ PairHigh.OriginalAlignment = align(16);
+ auto PairRequest = callRequest({FixedInteger, PairLow, PairHigh}, 1, true);
+ auto PairPlan = rcc::riscv::callLowering().planCall(PairRequest);
+ Context.expect(PairPlan && isLocation(PairPlan->argumentAssignments()[0], argumentGPR(0)) &&
+ isLocation(PairPlan->argumentAssignments()[1], argumentGPR(2)) &&
+ isLocation(PairPlan->argumentAssignments()[2], argumentGPR(3)),
+ "an aligned variadic pair must skip an odd GPR before assignment");
+
+ rcc::FormalArgumentRequest Formal;
+ Formal.Convention = convention();
+ Formal.Arguments = {FixedInteger};
+ Formal.FixedArgumentCount = 1;
+ Formal.IsVariadic = true;
+ auto FormalPlan = rcc::riscv::callLowering().planFormalArguments(Formal);
+ Context.expect(FormalPlan && FormalPlan->varArgs().IsVariadic &&
+ FormalPlan->varArgs().FirstUnallocatedIntegerRegister == 1 &&
+ FormalPlan->varArgs().RegisterSaveAreaSizeBytes == 56 &&
+ FormalPlan->varArgs().RegisterSaveAreaOffsetBytes == -56 &&
+ FormalPlan->varArgs().RegisterSavePaddingBytes == 8 &&
+ FormalPlan->varArgs().RegistersToSave.size() == 7 &&
+ FormalPlan->varArgs().RegistersToSave.front().Reg == argumentGPR(1) &&
+ FormalPlan->varArgs().RegistersToSave.back().Reg == argumentGPR(7),
+ "a variadic formal function must describe its GPR save area and padding");
+}
+
+void testSpecialArgumentsAndReturns(TestContext &Context) {
+ auto ByVal = abiValue(pointer(), 0);
+ ByVal.Flags.set(rcc::ABIValueFlag::ByVal);
+ ByVal.ByValSizeBytes = 24;
+ ByVal.ByValAlignment = align(16);
+ auto ByValRequest = callRequest({ByVal}, 1);
+ auto ByValPlan = rcc::riscv::callLowering().planCall(ByValRequest);
+ Context.expect(
+ ByValPlan && ByValPlan->byValCopies().size() == 1 &&
+ ByValPlan->byValCopies()[0].StackOffsetBytes == 0 &&
+ ByValPlan->callFrame().SizeBytes == 32 &&
+ ByValPlan->argumentAssignments()[0].CustomFlags.has(rcc::CCAssignmentFlag::ByVal),
+ "byval must allocate an aligned caller-owned copy and pass its pointer");
+
+ auto SRet = abiValue(pointer(), 0);
+ SRet.Flags.set(rcc::ABIValueFlag::StructReturn);
+ auto SRetPlan = rcc::riscv::callLowering().planCall(callRequest({SRet}, 1));
+ Context.expect(
+ SRetPlan && isLocation(SRetPlan->argumentAssignments()[0], argumentGPR(0)) &&
+ SRetPlan->argumentAssignments()[0].CustomFlags.has(rcc::CCAssignmentFlag::StructReturn),
+ "sret must be carried as the first integer argument");
+ auto SRetWithDirectResult = callRequest({SRet}, 1);
+ SRetWithDirectResult.Results = {abiValue(integer(64), 0)};
+ Context.expect(!rcc::riscv::callLowering().planCall(SRetWithDirectResult),
+ "an sret call cannot also expose direct ABI return registers");
+ auto Plain = abiValue(integer(64), 0);
+ SRet.OriginalValue = 1;
+ Context.expect(!rcc::riscv::callLowering().planCall(callRequest({Plain, SRet}, 2)),
+ "sret outside the first argument position must be rejected");
+
+ auto Indirect = abiValue(pointer(), 0);
+ Indirect.Flags.set(rcc::ABIValueFlag::Indirect);
+ auto IndirectPlan = rcc::riscv::callLowering().planCall(callRequest({Indirect}, 1));
+ Context.expect(
+ IndirectPlan &&
+ IndirectPlan->argumentAssignments()[0].LocationKind == rcc::CCLocationKind::Indirect &&
+ IndirectPlan->argumentAssignments()[0].CustomFlags.has(rcc::CCAssignmentFlag::Indirect),
+ "an explicitly indirect ABI value must preserve its transport classification");
+
+ auto InRegister = integerValues(9);
+ InRegister.back().Flags.set(rcc::ABIValueFlag::InRegister);
+ Context.expect(!rcc::riscv::callLowering().planCall(callRequest(std::move(InRegister), 9)),
+ "an inreg argument that would spill to the stack must be rejected");
+
+ rcc::ReturnLoweringRequest Return;
+ Return.Convention = convention();
+ Return.Values = {abiValue(integer(64), 0),
+ abiValue(floating(rcc::FloatSemantics::IEEEDouble), 1)};
+ auto ReturnPlan = rcc::riscv::callLowering().planReturn(Return);
+ Context.expect(ReturnPlan && ReturnPlan->returnAssignments().size() == 2 &&
+ isLocation(ReturnPlan->returnAssignments()[0], returnGPR(0)) &&
+ isLocation(ReturnPlan->returnAssignments()[1], returnFPR(0)),
+ "mixed integer and floating returns must use a0 and fa0 independently");
+}
+
+[[nodiscard]] rcc::TailCallContext emptyTailContext() {
+ rcc::TailCallContext Context;
+ Context.CallerConvention = convention();
+ Context.ReusableIncomingArgumentBytes = 0;
+ Context.CallerFixedArgumentCount = 0;
+ return Context;
+}
+
+void testTailCalls(TestContext &Context) {
+ auto Tail = callRequest({}, 0);
+ Tail.TailKind = rcc::TailCallKind::Tail;
+ Tail.TailContext = emptyTailContext();
+ auto TailPlan = rcc::riscv::callLowering().planCall(Tail);
+ Context.expect(TailPlan && TailPlan->tailDecision() == rcc::TailCallKind::Tail &&
+ TailPlan->callFrame().ReusesIncomingArea && !TailPlan->callFrame().EmitSetup,
+ "an ABI-compatible tail call must reuse the incoming argument area");
+
+ auto Fallback = callRequest({}, 0);
+ Fallback.TailKind = rcc::TailCallKind::Tail;
+ auto FallbackPlan = rcc::riscv::callLowering().planCall(Fallback);
+ Context.expect(FallbackPlan && FallbackPlan->tailDecision() == rcc::TailCallKind::NotTail &&
+ !FallbackPlan->tailRejectionReason().empty() &&
+ FallbackPlan->callFrame().EmitSetup,
+ "a non-mandatory incompatible tail call must fall back with a reason");
+
+ auto MustTail = callRequest({}, 0);
+ MustTail.TailKind = rcc::TailCallKind::MustTail;
+ auto MustTailPlan = rcc::riscv::callLowering().planCall(MustTail);
+ Context.expect(!MustTailPlan && MustTailPlan.error().code() == rcc::ErrorCode::Unsupported,
+ "an incompatible musttail call must fail instead of falling back");
+
+ auto StackTail = callRequest(integerValues(9), 9);
+ StackTail.TailKind = rcc::TailCallKind::Tail;
+ auto StackContext = emptyTailContext();
+ StackContext.CallerFixedArgumentCount = 9;
+ StackContext.ReusableIncomingArgumentBytes = 16;
+ StackTail.TailContext = std::move(StackContext);
+ auto StackTailPlan = rcc::riscv::callLowering().planCall(StackTail);
+ Context.expect(StackTailPlan && StackTailPlan->tailDecision() == rcc::TailCallKind::Tail &&
+ StackTailPlan->callFrame().SizeBytes == 16,
+ "a tail call may reuse a sufficiently large incoming stack argument area");
+}
+
+[[nodiscard]] std::unique_ptr<rcc::MachineFunction> createFunction(TestContext &Context,
+ std::string Name,
+ std::uint64_t ID,
+ rcc::MachineBasicBlock *&Block) {
+ auto Function =
+ rcc::MachineFunction::create(std::move(Name), rcc::MachineFunctionStableID::fromValue(ID));
+ Context.expect(Function.has_value(), "machine function creation must succeed");
+ if (!Function)
+ return nullptr;
+ auto CreatedBlock = (*Function)->createBasicBlock();
+ Context.expect(CreatedBlock && (*Function)->appendBlock(**CreatedBlock),
+ "machine entry block creation must succeed");
+ if (!CreatedBlock || !(*Function)->firstBlock())
+ return nullptr;
+ Block = *CreatedBlock;
+ return std::move(*Function);
+}
+
+[[nodiscard]] rcc::Register virtualRegister(rcc::MachineFunction &Function, rcc::LLT Type) {
+ auto Register = Function.registerInfo().createVirtualRegister(Type);
+ if (!Register)
+ std::terminate();
+ return *Register;
+}
+
+[[nodiscard]] rcc::Opcode opcode(rcc::riscv::InstructionOpcode Opcode) noexcept {
+ return rcc::Opcode::fromValue(static_cast<std::uint32_t>(Opcode));
+}
+
+[[nodiscard]] std::vector<rcc::MachineInstr *> instructions(rcc::MachineBasicBlock &Block) {
+ std::vector<rcc::MachineInstr *> Result;
+ for (rcc::MachineInstr *Instruction = Block.firstInstruction(); Instruction != nullptr;
+ Instruction = Instruction->next()) {
+ Result.push_back(Instruction);
+ }
+ return Result;
+}
+
+[[nodiscard]] rcc::MachineInstr *findInstruction(rcc::MachineBasicBlock &Block,
+ rcc::riscv::InstructionOpcode OpcodeValue) {
+ for (rcc::MachineInstr *Instruction = Block.firstInstruction(); Instruction != nullptr;
+ Instruction = Instruction->next()) {
+ if (Instruction->opcode() == opcode(OpcodeValue))
+ return Instruction;
+ }
+ return nullptr;
+}
+
+[[nodiscard]] bool verifyFunction(const rcc::MachineFunction &Function,
+ const rcc::TargetDescription &Target) {
+ rcc::MachineVerifierOptions Options;
+ Options.VerifyProperties = false;
+ return rcc::MachineVerifier::verify(Function, Target, Options).has_value();
+}
+
+void testOrdinaryAndIndirectEmission(TestContext &Context) {
+ auto Target = rcc::riscv::createTargetDescription();
+ rcc::MachineBasicBlock *Block = nullptr;
+ auto Function = createFunction(Context, "ordinary_call", 1, Block);
+ Context.expect(Target && Function && Block != nullptr, "ordinary-call fixtures must be valid");
+ if (!Target || !Function || Block == nullptr)
+ return;
+
+ const rcc::LLT I64 = integer(64);
+ const rcc::LLT F64 = floating(rcc::FloatSemantics::IEEEDouble);
+ const rcc::Register Argument = virtualRegister(*Function, I64);
+ const rcc::Register Result = virtualRegister(*Function, F64);
+ auto Request = callRequest({abiValue(I64, 0, Argument)}, 1);
+ Request.Results = {abiValue(F64, 0, Result)};
+
+ rcc::MachineIRBuilder Builder(*Function, *Target);
+ Context.expect(Builder.setInsertPoint(*Block).has_value(),
+ "ordinary-call insertion point must be valid");
+ auto Lowered = rcc::riscv::callLowering().lowerCall(Builder, Request);
+ Context.expect(Lowered.has_value(), "ordinary direct call emission must succeed");
+ if (!Lowered)
+ return;
+
+ const auto Instructions = instructions(*Block);
+ rcc::MachineInstr *Call = findInstruction(*Block, rcc::riscv::InstructionOpcode::PseudoCALL);
+ Context.expect(Instructions.size() == 5 &&
+ Instructions.front()->opcode() ==
+ opcode(rcc::riscv::InstructionOpcode::ADJCALLSTACKDOWN) &&
+ Instructions.back()->opcode() ==
+ opcode(rcc::riscv::InstructionOpcode::PseudoCC_COPY) &&
+ Call != nullptr && Call->operandCount() >= 6 &&
+ Call->operandAt(0)->kind() == rcc::MachineOperand::Kind::ExternalSymbol &&
+ Call->operandAt(0)->targetFlags() ==
+ static_cast<std::uint32_t>(rcc::riscv::CallTargetFlag::DirectCall) &&
+ Call->operandAt(1)->kind() == rcc::MachineOperand::Kind::IntegerImmediate &&
+ Call->operandAt(2)->kind() == rcc::MachineOperand::Kind::RegisterMask,
+ "ordinary emission must materialize call-frame, copies, target, ID, and RegMask");
+ bool HasArgumentUse = false;
+ bool HasResultDef = false;
+ for (const rcc::MachineOperand &Operand : Call->operands()) {
+ if (!Operand.isRegister() || !Operand.isImplicit())
+ continue;
+ HasArgumentUse |=
+ Operand.isUse() && Operand.registerValue() == rcc::Register::fromPhysical(argumentGPR(0));
+ HasResultDef |=
+ Operand.isDef() && Operand.registerValue() == rcc::Register::fromPhysical(returnFPR(0));
+ }
+ Context.expect(HasArgumentUse && HasResultDef && Function->frameInfo().state().HasCalls &&
+ Function->frameInfo().maxCallFrameSize() == 0 &&
+ Function->frameInfo().stackAlignment().bytes() == 16 &&
+ verifyFunction(*Function, *Target),
+ "ordinary call emission must record implicit ABI registers and valid frame state");
+
+ rcc::MachineBasicBlock *IndirectBlock = nullptr;
+ auto IndirectFunction = createFunction(Context, "indirect_call", 2, IndirectBlock);
+ if (!IndirectFunction || IndirectBlock == nullptr)
+ return;
+ const rcc::Register Callee = virtualRegister(*IndirectFunction, pointer());
+ auto IndirectTarget = rcc::MachineCallTarget::indirect(Callee);
+ if (!IndirectTarget)
+ return;
+ auto IndirectRequest = callRequest({}, 0);
+ IndirectRequest.Callee = std::move(*IndirectTarget);
+ rcc::MachineIRBuilder IndirectBuilder(*IndirectFunction, *Target);
+ if (!IndirectBuilder.setInsertPoint(*IndirectBlock))
+ return;
+ auto IndirectLowered = rcc::riscv::callLowering().lowerCall(IndirectBuilder, IndirectRequest);
+ rcc::MachineInstr *IndirectCall =
+ findInstruction(*IndirectBlock, rcc::riscv::InstructionOpcode::PseudoCALL);
+ Context.expect(IndirectLowered && IndirectCall != nullptr &&
+ IndirectCall->operandAt(0)->registerValue() == Callee &&
+ verifyFunction(*IndirectFunction, *Target),
+ "indirect call emission must retain the callee register operand");
+}
+
+void testStackAndByValEmission(TestContext &Context) {
+ auto Target = rcc::riscv::createTargetDescription();
+ rcc::MachineBasicBlock *Block = nullptr;
+ auto Function = createFunction(Context, "stack_byval_call", 3, Block);
+ if (!Target || !Function || Block == nullptr)
+ return;
+
+ std::vector<rcc::ABIValue> Arguments;
+ const rcc::Register ByValSource = virtualRegister(*Function, pointer());
+ auto ByVal = abiValue(pointer(), 0, ByValSource);
+ ByVal.Flags.set(rcc::ABIValueFlag::ByVal);
+ ByVal.ByValSizeBytes = 24;
+ ByVal.ByValAlignment = align(16);
+ Arguments.push_back(ByVal);
+ for (std::uint32_t Index = 1; Index < 9; ++Index) {
+ Arguments.push_back(abiValue(integer(64), Index, virtualRegister(*Function, integer(64))));
+ }
+ auto Request = callRequest(std::move(Arguments), 9);
+ rcc::MachineIRBuilder Builder(*Function, *Target);
+ if (!Builder.setInsertPoint(*Block))
+ return;
+ auto Lowered = rcc::riscv::callLowering().lowerCall(Builder, Request);
+ rcc::MachineInstr *ByValCopy =
+ findInstruction(*Block, rcc::riscv::InstructionOpcode::PseudoBYVAL_COPY);
+ rcc::MachineInstr *StackStore =
+ findInstruction(*Block, rcc::riscv::InstructionOpcode::PseudoSTORE_STACK_ARG);
+ bool HasByValObject = false;
+ bool HasOutgoingObject = false;
+ for (const rcc::StackObject &Object : Function->frameInfo().objects()) {
+ const rcc::StackObjectDesc *Description = Object.description();
+ if (Description == nullptr)
+ continue;
+ HasByValObject |= Description->Kind == rcc::StackObjectKind::Target &&
+ Description->TargetFlags ==
+ static_cast<std::uint64_t>(rcc::riscv::CallStackObjectFlag::ByValCopy);
+ HasOutgoingObject |=
+ Description->Kind == rcc::StackObjectKind::Target &&
+ Description->TargetFlags ==
+ static_cast<std::uint64_t>(rcc::riscv::CallStackObjectFlag::OutgoingArgument);
+ }
+ Context.expect(
+ Lowered && ByValCopy != nullptr && ByValCopy->memOperands().size() == 2 &&
+ ByValCopy->memOperands()[0]->isLoad() && ByValCopy->memOperands()[1]->isStore() &&
+ StackStore != nullptr && StackStore->memOperands().size() == 1 &&
+ StackStore->memOperands()[0]->isStore() && HasByValObject && HasOutgoingObject &&
+ Function->frameInfo().liveObjectCount() == 2 &&
+ Function->frameInfo().maxCallFrameSize() == 48 && verifyFunction(*Function, *Target),
+ "stack and byval emission must create precise objects, MMOs, and frame size");
+}
+
+void testFormalReturnAndTailEmission(TestContext &Context) {
+ auto Target = rcc::riscv::createTargetDescription();
+ if (!Target)
+ return;
+
+ rcc::MachineBasicBlock *FormalBlock = nullptr;
+ auto FormalFunction = createFunction(Context, "variadic_formal", 4, FormalBlock);
+ if (!FormalFunction || FormalBlock == nullptr)
+ return;
+ const rcc::Register FormalValue = virtualRegister(*FormalFunction, integer(64));
+ rcc::FormalArgumentRequest Formal;
+ Formal.Convention = convention();
+ Formal.Arguments = {abiValue(integer(64), 0, FormalValue)};
+ Formal.FixedArgumentCount = 1;
+ Formal.IsVariadic = true;
+ rcc::MachineIRBuilder FormalBuilder(*FormalFunction, *Target);
+ if (!FormalBuilder.setInsertPoint(*FormalBlock))
+ return;
+ auto FormalLowered = rcc::riscv::callLowering().lowerFormalArguments(FormalBuilder, Formal);
+ std::size_t SaveStores = 0;
+ for (rcc::MachineInstr *Instruction : instructions(*FormalBlock)) {
+ if (Instruction->opcode() == opcode(rcc::riscv::InstructionOpcode::PseudoSTORE_STACK_ARG))
+ ++SaveStores;
+ }
+ bool HasSaveArea = false;
+ bool HasPadding = false;
+ for (const rcc::StackObject &Object : FormalFunction->frameInfo().objects()) {
+ const rcc::StackObjectDesc *Description = Object.description();
+ if (Description == nullptr)
+ continue;
+ HasSaveArea |= Description->TargetFlags ==
+ static_cast<std::uint64_t>(rcc::riscv::CallStackObjectFlag::VarArgSaveArea);
+ HasPadding |=
+ Description->TargetFlags ==
+ static_cast<std::uint64_t>(rcc::riscv::CallStackObjectFlag::VarArgAlignmentPadding);
+ }
+ Context.expect(FormalLowered && SaveStores == 7 && HasSaveArea && HasPadding &&
+ FormalFunction->registerInfo().liveIns().size() == 8 &&
+ FormalBlock->liveIns().size() == 8 && verifyFunction(*FormalFunction, *Target),
+ "variadic formal emission must save remaining GPRs with explicit padding");
+
+ rcc::MachineBasicBlock *ReturnBlock = nullptr;
+ auto ReturnFunction = createFunction(Context, "return_value", 5, ReturnBlock);
+ if (!ReturnFunction || ReturnBlock == nullptr)
+ return;
+ const rcc::Register ReturnValue = virtualRegister(*ReturnFunction, integer(64));
+ rcc::ReturnLoweringRequest Return;
+ Return.Convention = convention();
+ Return.Values = {abiValue(integer(64), 0, ReturnValue)};
+ rcc::MachineIRBuilder ReturnBuilder(*ReturnFunction, *Target);
+ if (!ReturnBuilder.setInsertPoint(*ReturnBlock))
+ return;
+ auto ReturnLowered = rcc::riscv::callLowering().lowerReturn(ReturnBuilder, Return);
+ rcc::MachineInstr *ReturnPseudo =
+ findInstruction(*ReturnBlock, rcc::riscv::InstructionOpcode::PseudoRET);
+ bool HasReturnRegister = false;
+ if (ReturnPseudo != nullptr) {
+ HasReturnRegister = std::ranges::any_of(ReturnPseudo->operands(), [](const auto &Operand) {
+ return Operand.isRegister() && Operand.isImplicit() && Operand.isUse() &&
+ Operand.registerValue() == rcc::Register::fromPhysical(returnGPR(0));
+ });
+ }
+ Context.expect(ReturnLowered && instructions(*ReturnBlock).size() == 2 &&
+ ReturnPseudo != nullptr && HasReturnRegister &&
+ verifyFunction(*ReturnFunction, *Target),
+ "return emission must copy into a0 and record its implicit PseudoRET use");
+
+ rcc::MachineBasicBlock *TailBlock = nullptr;
+ auto TailFunction = createFunction(Context, "tail_call", 6, TailBlock);
+ if (!TailFunction || TailBlock == nullptr)
+ return;
+ auto Tail = callRequest({}, 0);
+ Tail.TailKind = rcc::TailCallKind::Tail;
+ Tail.TailContext = emptyTailContext();
+ rcc::MachineIRBuilder TailBuilder(*TailFunction, *Target);
+ if (!TailBuilder.setInsertPoint(*TailBlock))
+ return;
+ auto TailLowered = rcc::riscv::callLowering().lowerCall(TailBuilder, Tail);
+ Context.expect(TailLowered && instructions(*TailBlock).size() == 1 &&
+ TailBlock->firstInstruction()->opcode() ==
+ opcode(rcc::riscv::InstructionOpcode::PseudoTAIL) &&
+ TailFunction->frameInfo().state().HasCalls &&
+ TailFunction->frameInfo().state().HasTailCall &&
+ !TailFunction->frameInfo().state().AdjustsStack &&
+ verifyFunction(*TailFunction, *Target),
+ "tail emission must avoid a private call frame and mark function frame state");
+
+ rcc::MachineBasicBlock *StackTailBlock = nullptr;
+ auto StackTailFunction = createFunction(Context, "stack_tail_call", 8, StackTailBlock);
+ if (!StackTailFunction || StackTailBlock == nullptr)
+ return;
+ std::vector<rcc::ABIValue> StackTailArguments;
+ StackTailArguments.reserve(9);
+ for (std::uint32_t Index = 0; Index < 9; ++Index) {
+ StackTailArguments.push_back(
+ abiValue(integer(64), Index, virtualRegister(*StackTailFunction, integer(64))));
+ }
+ auto StackTail = callRequest(std::move(StackTailArguments), 9);
+ StackTail.TailKind = rcc::TailCallKind::Tail;
+ auto StackTailContext = emptyTailContext();
+ StackTailContext.CallerFixedArgumentCount = 9;
+ StackTailContext.ReusableIncomingArgumentBytes = 16;
+ StackTail.TailContext = std::move(StackTailContext);
+ rcc::MachineIRBuilder StackTailBuilder(*StackTailFunction, *Target);
+ if (!StackTailBuilder.setInsertPoint(*StackTailBlock))
+ return;
+ auto StackTailLowered = rcc::riscv::callLowering().lowerCall(StackTailBuilder, StackTail);
+ Context.expect(
+ StackTailLowered &&
+ findInstruction(*StackTailBlock, rcc::riscv::InstructionOpcode::PseudoSTORE_STACK_ARG) !=
+ nullptr &&
+ findInstruction(*StackTailBlock, rcc::riscv::InstructionOpcode::ADJCALLSTACKDOWN) ==
+ nullptr &&
+ StackTailFunction->frameInfo().state().HasTailCall &&
+ !StackTailFunction->frameInfo().state().AdjustsStack &&
+ verifyFunction(*StackTailFunction, *Target),
+ "a stack-argument tail call must reuse fixed incoming slots without claiming an SP change");
+}
+
+void testFormalStackAlignmentAndPreflight(TestContext &Context) {
+ auto Target = rcc::riscv::createTargetDescription();
+ if (!Target)
+ return;
+
+ rcc::MachineBasicBlock *StackBlock = nullptr;
+ auto StackFunction = createFunction(Context, "stack_formal", 9, StackBlock);
+ if (!StackFunction || StackBlock == nullptr)
+ return;
+ std::vector<rcc::ABIValue> StackArguments;
+ StackArguments.reserve(9);
+ for (std::uint32_t Index = 0; Index < 9; ++Index) {
+ StackArguments.push_back(
+ abiValue(integer(64), Index, virtualRegister(*StackFunction, integer(64))));
+ }
+ rcc::FormalArgumentRequest StackRequest;
+ StackRequest.Convention = convention();
+ StackRequest.Arguments = std::move(StackArguments);
+ StackRequest.FixedArgumentCount = 9;
+ rcc::MachineIRBuilder StackBuilder(*StackFunction, *Target);
+ if (!StackBuilder.setInsertPoint(*StackBlock))
+ return;
+ auto StackLowered = rcc::riscv::callLowering().lowerFormalArguments(StackBuilder, StackRequest);
+ bool HasIncomingObject = false;
+ for (const rcc::StackObject &Object : StackFunction->frameInfo().objects()) {
+ const rcc::StackObjectDesc *Description = Object.description();
+ HasIncomingObject |=
+ Description != nullptr && Description->Kind == rcc::StackObjectKind::FixedABI &&
+ Description->Align.bytes() == 8 && Description->Offset == 0 &&
+ Description->TargetFlags ==
+ static_cast<std::uint64_t>(rcc::riscv::CallStackObjectFlag::IncomingArgument);
+ }
+ Context.expect(
+ StackLowered && StackFunction->frameInfo().liveObjectCount() == 1 && HasIncomingObject &&
+ StackFunction->frameInfo().stackAlignment().bytes() == 8 &&
+ findInstruction(*StackBlock, rcc::riscv::InstructionOpcode::PseudoLOAD_STACK_ARG) !=
+ nullptr &&
+ verifyFunction(*StackFunction, *Target),
+ "non-variadic stack formals must publish fixed objects and maximum frame alignment");
+
+ rcc::MachineBasicBlock *ConflictBlock = nullptr;
+ auto ConflictFunction = createFunction(Context, "conflicting_formal", 10, ConflictBlock);
+ if (!ConflictFunction || ConflictBlock == nullptr)
+ return;
+ const rcc::Register Existing = virtualRegister(*ConflictFunction, integer(64));
+ const rcc::Register First = virtualRegister(*ConflictFunction, integer(64));
+ const rcc::Register Second = virtualRegister(*ConflictFunction, integer(64));
+ const rcc::PhysicalRegister ConflictingRegister = argumentGPR(1);
+ if (!ConflictFunction->registerInfo().addLiveIn(ConflictingRegister, Existing) ||
+ !ConflictBlock->addLiveIn(ConflictingRegister, rcc::LaneMask::all())) {
+ Context.expect(false, "formal live-in conflict fixture must be valid");
+ return;
+ }
+ rcc::FormalArgumentRequest ConflictRequest;
+ ConflictRequest.Convention = convention();
+ ConflictRequest.Arguments = {abiValue(integer(64), 0, First), abiValue(integer(64), 1, Second)};
+ ConflictRequest.FixedArgumentCount = 2;
+ rcc::MachineIRBuilder ConflictBuilder(*ConflictFunction, *Target);
+ if (!ConflictBuilder.setInsertPoint(*ConflictBlock))
+ return;
+ auto ConflictLowered =
+ rcc::riscv::callLowering().lowerFormalArguments(ConflictBuilder, ConflictRequest);
+ Context.expect(
+ !ConflictLowered && ConflictBlock->instructionCount() == 0 &&
+ ConflictFunction->allInstructions().empty() &&
+ ConflictFunction->registerInfo().liveIns().size() == 1 &&
+ ConflictBlock->liveIns().size() == 1 &&
+ ConflictFunction->frameInfo().liveObjectCount() == 0,
+ "formal live-in conflicts must fail preflight without partially modifying MachineIR");
+}
+
+void testPlanningFailureDoesNotMutate(TestContext &Context) {
+ auto Target = rcc::riscv::createTargetDescription();
+ rcc::MachineBasicBlock *Block = nullptr;
+ auto Function = createFunction(Context, "failed_call", 7, Block);
+ if (!Target || !Function || Block == nullptr)
+ return;
+ const rcc::Register Value = virtualRegister(*Function, integer(32));
+ auto Request = callRequest({abiValue(integer(32), 0, Value)}, 1);
+ rcc::MachineIRBuilder Builder(*Function, *Target);
+ if (!Builder.setInsertPoint(*Block))
+ return;
+ auto Lowered = rcc::riscv::callLowering().lowerCall(Builder, Request);
+ auto UnknownPhysical = rcc::PhysicalRegister::create(999);
+ auto UnknownRequest =
+ UnknownPhysical
+ ? callRequest({abiValue(integer(64), 0, rcc::Register::fromPhysical(*UnknownPhysical))},
+ 1)
+ : rcc::CallLoweringRequest{};
+ auto UnknownLowered = UnknownPhysical
+ ? rcc::riscv::callLowering().lowerCall(Builder, UnknownRequest)
+ : rcc::Expected<void>{std::unexpected(UnknownPhysical.error())};
+ Context.expect(!Lowered && !UnknownLowered && Block->instructionCount() == 0 &&
+ Function->allInstructions().empty() &&
+ Function->frameInfo().liveObjectCount() == 0,
+ "RISC-V planning and preflight failures must leave MachineIR and frame state "
+ "untouched");
+}
+
+} // namespace
+
+int main() {
+ TestContext Context;
+ testIntegerAndFloatingAssignments(Context);
+ testVariadicAssignments(Context);
+ testSpecialArgumentsAndReturns(Context);
+ testTailCalls(Context);
+ testOrdinaryAndIndirectEmission(Context);
+ testStackAndByValEmission(Context);
+ testFormalReturnAndTailEmission(Context);
+ testFormalStackAlignmentAndPreflight(Context);
+ testPlanningFailureDoesNotMutate(Context);
+ return Context.result();
+}