diff options
| author | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-08-07 22:06:28 +0800 |
|---|---|---|
| committer | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-08-07 22:06:28 +0800 |
| commit | f99ef4322a7a2c09380fff61f88ab9440f05a92c (patch) | |
| tree | 69538d9ac3acb4a4a680dbce143f39c7b576035c /backend | |
| parent | 3e15bfd7b075209ec3b65d30d0611e060f6fbb77 (diff) | |
| download | rcc-project-f99ef4322a7a2c09380fff61f88ab9440f05a92c.tar.gz rcc-project-f99ef4322a7a2c09380fff61f88ab9440f05a92c.tar.xz | |
[CodeGen] Complete machine pass and ABI lowering infrastructure
Diffstat (limited to 'backend')
24 files changed, 10625 insertions, 0 deletions
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(); +} |
