goalc: Get CodeTester tests passing on Arm64 (only targetting macOS atm) (#3290)

This PR does the following:
- Designs a mechanism by which arm64 instructions can be encoded and
emitted
- Dispatch our higher-level instruction emitting calls to either x86 or
arm64 instructions depending on what the compiler is set to (defaults to
x86)
- Bare minimum scaffolding to get the arm64 instructions successfully
executing atleast on apple silicon
- Implement enough instructions to get the codetester test suite passing
on arm
This commit is contained in:
Tyler Wilding
2026-03-30 20:20:47 -04:00
committed by GitHub
parent 40cc1f0ae7
commit 64bcd8c030
72 changed files with 17001 additions and 9598 deletions
+70 -42
View File
@@ -7,6 +7,7 @@
#include "CodeGenerator.h"
#include <stdexcept>
#include <unordered_set>
#include "IR.h"
@@ -18,8 +19,11 @@
using namespace emitter;
CodeGenerator::CodeGenerator(FileEnv* env, DebugInfo* debug_info, GameVersion version)
: m_gen(version), m_fe(env), m_debug_info(debug_info) {}
CodeGenerator::CodeGenerator(FileEnv* env,
DebugInfo* debug_info,
GameVersion version,
InstructionSet instruction_set)
: m_gen(version, instruction_set), m_fe(env), m_debug_info(debug_info) {}
/*!
* Generate an object file.
@@ -62,9 +66,21 @@ std::vector<u8> CodeGenerator::run(const TypeSystem* ts) {
void CodeGenerator::do_function(FunctionEnv* env, int f_idx) {
if (env->is_asm_func) {
do_asm_function(env, f_idx, env->asm_func_saved_regs);
if (m_gen.instr_set() == InstructionSet::X86) {
do_asm_function_x86(env, f_idx, env->asm_func_saved_regs);
} else if (m_gen.instr_set() == InstructionSet::ARM64) {
do_asm_function_arm64(env, f_idx, env->asm_func_saved_regs);
} else {
throw std::runtime_error("CodeGenerator::do_function, instruction set not supported");
}
} else {
do_goal_function(env, f_idx);
if (m_gen.instr_set() == InstructionSet::X86) {
do_goal_function_x86(env, f_idx);
} else if (m_gen.instr_set() == InstructionSet::ARM64) {
do_goal_function_arm64(env, f_idx);
} else {
throw std::runtime_error("CodeGenerator::do_function, instruction set not supported");
}
}
}
@@ -72,7 +88,7 @@ void CodeGenerator::do_function(FunctionEnv* env, int f_idx) {
* Add instructions to the function, specified by index.
* Generates prologues / epilogues.
*/
void CodeGenerator::do_goal_function(FunctionEnv* env, int f_idx) {
void CodeGenerator::do_goal_function_x86(FunctionEnv* env, int f_idx) {
bool use_new_xmms = true;
auto* debug = &m_debug_info->function_by_name(env->name());
@@ -88,7 +104,7 @@ void CodeGenerator::do_goal_function(FunctionEnv* env, int f_idx) {
// count how many xmm's we have to backup
int n_xmm_backups = 0;
for (auto& saved_reg : allocs.used_saved_regs) {
if (saved_reg.is_xmm()) {
if (saved_reg.is_xmm(m_gen.instr_set())) {
n_xmm_backups++;
}
}
@@ -100,14 +116,15 @@ void CodeGenerator::do_goal_function(FunctionEnv* env, int f_idx) {
if (n_xmm_backups > 0) {
// offset the stack
stack_offset += xmm_backup_stack_offset;
m_gen.add_instr_no_ir(f_rec, IGen::sub_gpr64_imm(RSP, xmm_backup_stack_offset),
m_gen.add_instr_no_ir(f_rec, IGen::sub_gpr64_imm(m_gen, RSP, xmm_backup_stack_offset),
InstructionInfo::Kind::PROLOGUE);
// back up xmms
int i = 0;
for (auto& saved_reg : allocs.used_saved_regs) {
if (saved_reg.is_xmm()) {
if (saved_reg.is_xmm(m_gen.instr_set())) {
int offset = i * XMM_SIZE;
m_gen.add_instr_no_ir(f_rec, IGen::store128_xmm128_reg_offset(RSP, saved_reg, offset),
m_gen.add_instr_no_ir(f_rec,
IGen::store128_xmm128_reg_offset(m_gen, RSP, saved_reg, offset),
InstructionInfo::Kind::PROLOGUE);
i++;
}
@@ -116,10 +133,10 @@ void CodeGenerator::do_goal_function(FunctionEnv* env, int f_idx) {
} else {
// back up xmms (currently not aligned)
for (auto& saved_reg : allocs.used_saved_regs) {
if (saved_reg.is_xmm()) {
m_gen.add_instr_no_ir(f_rec, IGen::sub_gpr64_imm8s(RSP, XMM_SIZE),
if (saved_reg.is_xmm(m_gen.instr_set())) {
m_gen.add_instr_no_ir(f_rec, IGen::sub_gpr64_imm8s(m_gen, RSP, XMM_SIZE),
InstructionInfo::Kind::PROLOGUE);
m_gen.add_instr_no_ir(f_rec, IGen::store128_gpr64_xmm128(RSP, saved_reg),
m_gen.add_instr_no_ir(f_rec, IGen::store128_gpr64_simd128(m_gen, RSP, saved_reg),
InstructionInfo::Kind::PROLOGUE);
stack_offset += XMM_SIZE;
}
@@ -128,8 +145,9 @@ void CodeGenerator::do_goal_function(FunctionEnv* env, int f_idx) {
// back up gprs
for (auto& saved_reg : allocs.used_saved_regs) {
if (saved_reg.is_gpr()) {
m_gen.add_instr_no_ir(f_rec, IGen::push_gpr64(saved_reg), InstructionInfo::Kind::PROLOGUE);
if (saved_reg.is_gpr(m_gen.instr_set())) {
m_gen.add_instr_no_ir(f_rec, IGen::push_gpr64(m_gen, saved_reg),
InstructionInfo::Kind::PROLOGUE);
stack_offset += GPR_SIZE;
}
}
@@ -152,7 +170,7 @@ void CodeGenerator::do_goal_function(FunctionEnv* env, int f_idx) {
} else {
// otherwise to an extra push, and remember so we can do an extra pop later on.
bonus_push = true;
m_gen.add_instr_no_ir(f_rec, IGen::push_gpr64(ri.get_saved_gpr(0)),
m_gen.add_instr_no_ir(f_rec, IGen::push_gpr64(m_gen, ri.get_saved_gpr(0)),
InstructionInfo::Kind::PROLOGUE);
}
stack_offset += 8;
@@ -162,7 +180,7 @@ void CodeGenerator::do_goal_function(FunctionEnv* env, int f_idx) {
// do manual stack offset.
if (manually_added_stack_offset) {
m_gen.add_instr_no_ir(f_rec, IGen::sub_gpr64_imm(RSP, manually_added_stack_offset),
m_gen.add_instr_no_ir(f_rec, IGen::sub_gpr64_imm(m_gen, RSP, manually_added_stack_offset),
InstructionInfo::Kind::PROLOGUE);
}
}
@@ -178,20 +196,20 @@ void CodeGenerator::do_goal_function(FunctionEnv* env, int f_idx) {
auto& bonus = allocs.stack_ops.at(ir_idx);
for (auto& op : bonus.ops) {
if (op.load) {
if (op.reg.is_gpr() && op.reg_class == RegClass::GPR_64) {
if (op.reg.is_gpr(m_gen.instr_set()) && op.reg_class == RegClass::GPR_64) {
// todo, s8 or 0 offset if possible?
m_gen.add_instr(IGen::load64_gpr64_plus_s32(
op.reg, allocs.get_slot_for_spill(op.slot) * GPR_SIZE, RSP),
m_gen, op.reg, allocs.get_slot_for_spill(op.slot) * GPR_SIZE, RSP),
i_rec);
} else if (op.reg.is_xmm() && op.reg_class == RegClass::FLOAT) {
} else if (op.reg.is_xmm(m_gen.instr_set()) && op.reg_class == RegClass::FLOAT) {
// load xmm32 off of the stack
m_gen.add_instr(IGen::load_reg_offset_xmm32(
op.reg, RSP, allocs.get_slot_for_spill(op.slot) * GPR_SIZE),
m_gen, op.reg, RSP, allocs.get_slot_for_spill(op.slot) * GPR_SIZE),
i_rec);
} else if (op.reg.is_xmm() &&
} else if (op.reg.is_xmm(m_gen.instr_set()) &&
(op.reg_class == RegClass::VECTOR_FLOAT || op.reg_class == RegClass::INT_128)) {
m_gen.add_instr(IGen::load128_xmm128_reg_offset(
op.reg, RSP, allocs.get_slot_for_spill(op.slot) * GPR_SIZE),
m_gen, op.reg, RSP, allocs.get_slot_for_spill(op.slot) * GPR_SIZE),
i_rec);
} else {
ASSERT(false);
@@ -200,25 +218,25 @@ void CodeGenerator::do_goal_function(FunctionEnv* env, int f_idx) {
}
// do the actual op
ir->do_codegen(&m_gen, allocs, i_rec);
ir->do_codegen_x86(&m_gen, allocs, i_rec);
// store things back on the stack if needed.
for (auto& op : bonus.ops) {
if (op.store) {
if (op.reg.is_gpr() && op.reg_class == RegClass::GPR_64) {
if (op.reg.is_gpr(m_gen.instr_set()) && op.reg_class == RegClass::GPR_64) {
// todo, s8 or 0 offset if possible?
m_gen.add_instr(IGen::store64_gpr64_plus_s32(
RSP, allocs.get_slot_for_spill(op.slot) * GPR_SIZE, op.reg),
m_gen, RSP, allocs.get_slot_for_spill(op.slot) * GPR_SIZE, op.reg),
i_rec);
} else if (op.reg.is_xmm() && op.reg_class == RegClass::FLOAT) {
} else if (op.reg.is_xmm(m_gen.instr_set()) && op.reg_class == RegClass::FLOAT) {
// store xmm32 on the stack
m_gen.add_instr(IGen::store_reg_offset_xmm32(
RSP, op.reg, allocs.get_slot_for_spill(op.slot) * GPR_SIZE),
m_gen, RSP, op.reg, allocs.get_slot_for_spill(op.slot) * GPR_SIZE),
i_rec);
} else if (op.reg.is_xmm() &&
} else if (op.reg.is_xmm(m_gen.instr_set()) &&
(op.reg_class == RegClass::VECTOR_FLOAT || op.reg_class == RegClass::INT_128)) {
m_gen.add_instr(IGen::store128_xmm128_reg_offset(
RSP, op.reg, allocs.get_slot_for_spill(op.slot) * GPR_SIZE),
m_gen, RSP, op.reg, allocs.get_slot_for_spill(op.slot) * GPR_SIZE),
i_rec);
} else {
ASSERT(false);
@@ -231,21 +249,22 @@ void CodeGenerator::do_goal_function(FunctionEnv* env, int f_idx) {
if (manually_added_stack_offset || allocs.needs_aligned_stack_for_spills ||
env->needs_aligned_stack()) {
if (manually_added_stack_offset) {
m_gen.add_instr_no_ir(f_rec, IGen::add_gpr64_imm(RSP, manually_added_stack_offset),
m_gen.add_instr_no_ir(f_rec, IGen::add_gpr64_imm(m_gen, RSP, manually_added_stack_offset),
InstructionInfo::Kind::EPILOGUE);
}
if (bonus_push) {
ASSERT(!manually_added_stack_offset);
m_gen.add_instr_no_ir(f_rec, IGen::pop_gpr64(ri.get_saved_gpr(0)),
m_gen.add_instr_no_ir(f_rec, IGen::pop_gpr64(m_gen, ri.get_saved_gpr(0)),
InstructionInfo::Kind::EPILOGUE);
}
}
for (int i = int(allocs.used_saved_regs.size()); i-- > 0;) {
auto& saved_reg = allocs.used_saved_regs.at(i);
if (saved_reg.is_gpr()) {
m_gen.add_instr_no_ir(f_rec, IGen::pop_gpr64(saved_reg), InstructionInfo::Kind::EPILOGUE);
if (saved_reg.is_gpr(m_gen.instr_set())) {
m_gen.add_instr_no_ir(f_rec, IGen::pop_gpr64(m_gen, saved_reg),
InstructionInfo::Kind::EPILOGUE);
}
}
@@ -254,33 +273,38 @@ void CodeGenerator::do_goal_function(FunctionEnv* env, int f_idx) {
int j = n_xmm_backups;
for (int i = int(allocs.used_saved_regs.size()); i-- > 0;) {
auto& saved_reg = allocs.used_saved_regs.at(i);
if (saved_reg.is_xmm()) {
if (saved_reg.is_xmm(m_gen.instr_set())) {
j--;
int offset = j * XMM_SIZE;
m_gen.add_instr_no_ir(f_rec, IGen::load128_xmm128_reg_offset(saved_reg, RSP, offset),
m_gen.add_instr_no_ir(f_rec,
IGen::load128_xmm128_reg_offset(m_gen, saved_reg, RSP, offset),
InstructionInfo::Kind::EPILOGUE);
}
}
ASSERT(j == 0);
m_gen.add_instr_no_ir(f_rec, IGen::add_gpr64_imm(RSP, xmm_backup_stack_offset),
m_gen.add_instr_no_ir(f_rec, IGen::add_gpr64_imm(m_gen, RSP, xmm_backup_stack_offset),
InstructionInfo::Kind::EPILOGUE);
}
} else {
for (int i = int(allocs.used_saved_regs.size()); i-- > 0;) {
auto& saved_reg = allocs.used_saved_regs.at(i);
if (saved_reg.is_xmm()) {
m_gen.add_instr_no_ir(f_rec, IGen::load128_xmm128_gpr64(saved_reg, RSP),
if (saved_reg.is_xmm(m_gen.instr_set())) {
m_gen.add_instr_no_ir(f_rec, IGen::load128_simd128_gpr64(m_gen, saved_reg, RSP),
InstructionInfo::Kind::EPILOGUE);
m_gen.add_instr_no_ir(f_rec, IGen::add_gpr64_imm8s(RSP, XMM_SIZE),
m_gen.add_instr_no_ir(f_rec, IGen::add_gpr64_imm8s(m_gen, RSP, XMM_SIZE),
InstructionInfo::Kind::EPILOGUE);
}
}
}
m_gen.add_instr_no_ir(f_rec, IGen::ret(), InstructionInfo::Kind::EPILOGUE);
m_gen.add_instr_no_ir(f_rec, IGen::ret(m_gen), InstructionInfo::Kind::EPILOGUE);
}
void CodeGenerator::do_asm_function(FunctionEnv* env, int f_idx, bool allow_saved_regs) {
void CodeGenerator::do_goal_function_arm64(FunctionEnv* env, int f_idx) {
throw std::runtime_error("NYI - CodeGenerator::do_goal_function_arm64");
}
void CodeGenerator::do_asm_function_x86(FunctionEnv* env, int f_idx, bool allow_saved_regs) {
auto f_rec = m_gen.get_existing_function_record(f_idx);
const auto& allocs = env->alloc_result();
@@ -316,6 +340,10 @@ void CodeGenerator::do_asm_function(FunctionEnv* env, int f_idx, bool allow_save
}
// do the actual op
ir->do_codegen(&m_gen, allocs, i_rec);
ir->do_codegen_x86(&m_gen, allocs, i_rec);
}
}
void CodeGenerator::do_asm_function_arm64(FunctionEnv* env, int f_idx, bool allow_saved_regs) {
throw std::runtime_error("NYI - CodeGenerator::do_asm_function");
}