diff --git a/CMakeLists.txt b/CMakeLists.txt index 45706cfa7f..7dac48121b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,4 +38,7 @@ add_subdirectory(test) add_subdirectory(third-party/minilzo) # build format library -add_subdirectory(third-party/fmt) \ No newline at end of file +add_subdirectory(third-party/fmt) + +# for now... +add_subdirectory(old_compiler/cpp) \ No newline at end of file diff --git a/game/kernel/kdgo.cpp b/game/kernel/kdgo.cpp index 6181c18bb2..ba444cad6b 100644 --- a/game/kernel/kdgo.cpp +++ b/game/kernel/kdgo.cpp @@ -137,12 +137,12 @@ u32 InitRPC() { * Send a message to the IOP to stop it. */ void StopIOP() { - x[2] = 0x14; // todo - this type and message - RpcSync(PLAYER_RPC_CHANNEL); - RpcCall(PLAYER_RPC_CHANNEL, 0, false, x, 0x50, nullptr, 0); - printf("IOP shut down\n"); - // sceDmaSync(0x10009000, 0, 0); - printf("DMA shut down\n"); + // x[2] = 0x14; // todo - this type and message + // RpcSync(PLAYER_RPC_CHANNEL); + // RpcCall(PLAYER_RPC_CHANNEL, 0, false, x, 0x50, nullptr, 0); + // printf("IOP shut down\n"); + // // sceDmaSync(0x10009000, 0, 0); + // printf("DMA shut down\n"); } /*! diff --git a/game/kernel/kscheme.cpp b/game/kernel/kscheme.cpp index eff441ee01..fe100f6cb3 100644 --- a/game/kernel/kscheme.cpp +++ b/game/kernel/kscheme.cpp @@ -619,7 +619,7 @@ Ptr intern_type_from_c(const char* name, u64 methods) { "dkernel: trying to redefine a type '%s' with %d methods when it had %d, try " "restarting\n", name, (u32)methods, type->num_methods); - assert(false); + // assert(false); } return type; } diff --git a/old_compiler/cpp/CMakeLists.txt b/old_compiler/cpp/CMakeLists.txt new file mode 100644 index 0000000000..0f2c692598 --- /dev/null +++ b/old_compiler/cpp/CMakeLists.txt @@ -0,0 +1,23 @@ +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_FLAGS "-O0 -g -march=native -ggdb -Wall \ +-Wextra -Wcast-align -Wcast-qual -Wdisabled-optimization -Wformat=2 \ +-Winit-self -Wmissing-include-dirs -Woverloaded-virtual \ +-Wredundant-decls -Wshadow -Wsign-promo ") + +include_directories(./) +include_directories(../) + +add_subdirectory(reader) +add_subdirectory(goos) +add_subdirectory(goal) +add_subdirectory(listener) +add_subdirectory(codegen) +add_subdirectory(regalloc) +add_subdirectory(logger) + + +#include_directories(../shared_config) +#include_directories(../third-party/cpp-linenoise) + +add_executable(goalc_old main.cpp) +target_link_libraries(goalc_old reader_old goos_old goal listener_old codegen regalloc logger pthread) \ No newline at end of file diff --git a/old_compiler/cpp/codegen/CMakeLists.txt b/old_compiler/cpp/codegen/CMakeLists.txt new file mode 100644 index 0000000000..34a8414597 --- /dev/null +++ b/old_compiler/cpp/codegen/CMakeLists.txt @@ -0,0 +1,6 @@ +add_library(codegen SHARED + Coloring.cpp + CodegenOutput.cpp + x86_Emitter.cpp + x86_Emitter_LinkData.cpp + x86_Emitter_ConvertIR.cpp) \ No newline at end of file diff --git a/old_compiler/cpp/codegen/CodegenOutput.cpp b/old_compiler/cpp/codegen/CodegenOutput.cpp new file mode 100644 index 0000000000..116072444b --- /dev/null +++ b/old_compiler/cpp/codegen/CodegenOutput.cpp @@ -0,0 +1,21 @@ +#include "CodegenOutput.h" + +/*! + * Convert codegen output to a single binary blob. + */ +std::vector CodegenOutput::to_vector() { + std::vector result; + // header + result.insert(result.end(), header.begin(), header.end()); + + // link tables + for (int seg = N_SEG; seg-- > 0;) { + result.insert(result.end(), link_tables[seg].begin(), link_tables[seg].end()); + } + + // data (code + static objects, by segment) + for (int seg = N_SEG; seg-- > 0;) { + result.insert(result.end(), code[seg].begin(), code[seg].end()); + } + return result; +} \ No newline at end of file diff --git a/old_compiler/cpp/codegen/CodegenOutput.h b/old_compiler/cpp/codegen/CodegenOutput.h new file mode 100644 index 0000000000..420e966351 --- /dev/null +++ b/old_compiler/cpp/codegen/CodegenOutput.h @@ -0,0 +1,34 @@ +#ifndef JAK_CODEGENOUTPUT_H +#define JAK_CODEGENOUTPUT_H + +#include +#include +#include +#include "shared_config.h" + +/*! + * The result of the codegen process. + * It is stored part by part for debugging purposes at this point + * but the to_vector() method knows how to combine it into a blob for loading. + */ +struct CodegenOutput { + // code and objects + std::array, N_SEG> code; + + // the link data + std::array, N_SEG> link_tables; + + // maps from instr_idx to offset into code + std::array, N_SEG> instr_offsets; + + // the header data (goes before the link_tables to form the link section) + std::vector header; + + // offset into the segment's code for where the static objects start. + std::array static_start; + + // make into a single blob for loading into the runtime. + std::vector to_vector(); +}; + +#endif // JAK_CODEGENOUTPUT_H diff --git a/old_compiler/cpp/codegen/Coloring.cpp b/old_compiler/cpp/codegen/Coloring.cpp new file mode 100644 index 0000000000..0cae33b7fb --- /dev/null +++ b/old_compiler/cpp/codegen/Coloring.cpp @@ -0,0 +1,203 @@ +/*! + * @file Coloring.cpp + * High Level Interface for register coloring. + */ + +#include "logger/Logger.h" +#include "goal/GoalEnv.h" +#include "regalloc/RegAllocProgram.h" +#include "Coloring.h" + +#define LOG(...) gLogger.log(MSG_WARN, __VA_ARGS__) + +bool debug_linear_scan = false; + +/*! + * Print each instruction, both as an IR instruction and a RegAllocInstruction. + */ +static void debug_print_register_use(FunctionEnv& f, RegAllocProgram& program) { + printf("IR Register Use Analysis\n"); + printf("-----------------------------------------------------------------\n"); + for (uint32_t i = 0; i < f.code.size(); i++) { + printf("[%03d] %30s -> %30s\n", i, f.code.at(i)->print().c_str(), + program.instructions.at(i).print().c_str()); + } +} + +/*! + * Print the basic blocks and live ranges of a RegAllocProgram and a Function. + */ +static void debug_print_basic_blocks_and_live_ranges(FunctionEnv& f, RegAllocProgram& program) { + LOG("\nBasic Blocks\n"); + LOG("-----------------------------------------------------------------\n"); + LOG("%s\n", program.print_block_detailed().c_str()); + + LOG("\nLive Ranges (no alloc)\n"); + LOG("-----------------------------------------------------------------\n"); + // align to where we start putting live stuff + LOG(" %30s ", ""); + for (int i = 0; i < program.max_var; i++) { + LOG("%2d ", i); + } + LOG("\n"); + LOG("_________________________________________________________________\n"); + for (uint32_t i = 0; i < f.code.size(); i++) { + std::vector ids_live; + std::string lives; + + ids_live.resize(program.max_var, false); + + for (int j = 0; j < program.max_var; j++) { + if (program.live_ranges.at(j).is_live_at_instr(i)) { + ids_live.at(j) = true; + } + } + + for (uint32_t j = 0; j < ids_live.size(); j++) { + if (ids_live[j]) { + char buff[256]; + sprintf(buff, "%2d ", j); + lives.append(buff); + } else { + lives.append(".. "); + } + } + + std::string code_str = f.code.at(i)->print(); + if (code_str.length() >= 50) { + code_str = code_str.substr(0, 48); + code_str.push_back('~'); + } + LOG("[%03d] %30s -> %s\n", i, code_str.c_str(), lives.c_str()); + } +} + +/*! + * Print the result of coloring. + */ +static void debug_print_coloring(FunctionEnv& f, RegAllocProgram& program) { + LOG("\nLive Ranges (after alloc)\n"); + LOG("-----------------------------------------------------------------\n"); + for (uint32_t i = 0; i < f.code.size(); i++) { + std::vector ids_live; + std::string lives; + + ids_live.resize(program.max_var, false); + + for (int j = 0; j < program.max_var; j++) { + if (program.live_ranges.at(j).is_live_at_instr(i)) { + lives += std::to_string(j) + " " + program.live_ranges.at(j).get(i).print() + " "; + } + } + + std::string code_str = f.code.at(i)->print(); + if (code_str.length() >= 50) { + code_str = code_str.substr(0, 48); + code_str.push_back('~'); + } + LOG("[%03d] %30s | %30s | %30s\n", i, code_str.c_str(), lives.c_str(), + program.bonus_instructions.at(i).print().c_str()); + } +} + +/*! + * Attempt linear scan coloring algorithm on the given function. Return true if it succeeds. + */ +bool do_linear_scan_coloring(FunctionEnv& f) { + // first we translate to a RegAllocProgram + RegAllocProgram program; + for (auto& ir : f.code) { + // convert to instruction + auto inst = ir->to_rai(); + auto id = program.add_instruction(inst); + // add more complicated constraints for the IR into the program, if the IR needs it. + ir->add_constraints_to_program(program.constraints, id); + + // check if we need the RBP register + if (ir->kind == STATIC_VAR_32 || ir->kind == STATIC_VAR_ADDR || ir->kind == FUNC_ADDR) { + f.uses_rbp = true; + } + + // if we have a function call, we should align our stack + if (ir->kind == FUNCTION_CALL) { + f.requires_aligned_stack = true; + } + } + + // add constraints contained in the function definition too. + for (auto& c : f.register_constraints) { + program.constraints.push_back(c); + } + + // at this point, we can print which instruction reads/writes each register. + if (debug_linear_scan) { + debug_print_register_use(f, program); + } + + // use analysis functions to find basic blocks and register liveliness (including ranges) + program.find_basic_blocks(); + program.analyze_block_liveliness(f.vars.size()); + + // prepare! + program.prepare_for_allocation(f.code.size()); + + // print basic blocks and live ranges + if (debug_linear_scan) { + debug_print_basic_blocks_and_live_ranges(f, program); + } + + // constrained alloc + program.do_constrained_allocations(); + program.check_constrained_allocations(); + + // do other allocs + program.allocate(); + + if (debug_linear_scan) { + debug_print_coloring(f, program); + } + + // TODO - final check? + + // check if the coloring needed to use any saved registers + for (int sr_id = 0; sr_id < SAVED_REG_COUNT; sr_id++) { + auto sr = SAVED_REGS[sr_id]; + for (auto& lr : program.live_ranges) { + for (int instr_idx = lr.min; instr_idx < lr.max; instr_idx++) { + if (lr.get(instr_idx).reg_id == sr) { + f.uses_saved_reg[sr_id] = true; + } + } + } + // for(auto& instr : program.instructions) { + // for(size_t i = 0; i < program.instructions.size(); i++) { + // auto& instr = program.instructions.at(i); + // + // auto sr_color = f.coloring.at(sr).get(i).reg_id; + // if(instr.reads(sr_color) || instr.writes(sr_color)) { + // printf("instruction %s uses sr id %d\n", instr.print().c_str(), sr_id); + // f.uses_saved_reg[sr_id] = true; + // break; + // } + // } + } + + if (program.coloring_error) { + LOG("Coloring was unsuccessful. Please try harder next time.\n"); + return false; + } else { + f.coloring = program.live_ranges; + f.bonus_instructions = program.bonus_instructions; + f.stack_slots = program.get_stack_slot_count(); + f.coloring_done = true; + if (program.used_stack && f.is_asm_func) { + printf("-- WARNING -- asm func %s used the stack!\n", f.name.c_str()); + } + f.requires_aligned_stack = f.requires_aligned_stack || program.used_stack; + // auto move_stats = program.get_move_stats(); + // auto spill_stats = program.get_spill_count(); + // printf("%d/%d moves eliminated, %d spill moves\n", move_stats.first, move_stats.second, + // spill_stats); + return true; + } +} diff --git a/old_compiler/cpp/codegen/Coloring.h b/old_compiler/cpp/codegen/Coloring.h new file mode 100644 index 0000000000..4e42031fe2 --- /dev/null +++ b/old_compiler/cpp/codegen/Coloring.h @@ -0,0 +1,14 @@ +/*! + * @file Coloring.h + * High Level Interface for register coloring. + */ + +#ifndef JAK_COLORING_H +#define JAK_COLORING_H + +#include + +class FunctionEnv; +bool do_linear_scan_coloring(FunctionEnv& func); + +#endif // JAK_COLORING_H diff --git a/old_compiler/cpp/codegen/ColoringAssignment.h b/old_compiler/cpp/codegen/ColoringAssignment.h new file mode 100644 index 0000000000..54d442f26e --- /dev/null +++ b/old_compiler/cpp/codegen/ColoringAssignment.h @@ -0,0 +1,296 @@ +/*! + * @file ColoringAssignment.h + * Input and Output Types for the Coloring System + */ + +#ifndef JAK_COLORINGASSIGNMENT_H +#define JAK_COLORINGASSIGNMENT_H + +#include +#include +#include "codegen/x86.h" + +// Assignment type which is used for constraints and output of the coloring +enum AssignmentKind { STACK, REGISTER, UNASSIGNED }; + +constexpr bool enable_fancy_coloring = true; +constexpr bool move_eliminator = true; + +// The description of where a variable is assigned. +// Can represent a register, the stack, or UNASSIGNED. +// Uses the integer-based register IDs of X86_Registers +struct ColoringAssignment { + ColoringAssignment() = default; + ColoringAssignment(AssignmentKind _kind, int _reg_id) : kind(_kind), reg_id(_reg_id) {} + AssignmentKind kind = UNASSIGNED; + int reg_id = -1; + + // which slot of spilled variables on the stack this variable goes in. + // Only valid if spilled is true + int stack_slot = -1; + + // set if this variable is ever spilled. + bool spilled = false; + + std::string print() const { + std::string result = spilled ? "S!" : ""; + switch (kind) { + case REGISTER: + result += x86_gpr_names[reg_id]; + break; + case UNASSIGNED: + result += "unassigned"; + break; + case STACK: + result += "stack " + std::to_string(stack_slot); + break; + default: + throw std::runtime_error("can't print this coloring assignment"); + } + return result; + } + + /*! + * Will these two assignments use the same hardware register? + * If unassigned or on the stack, always no. + */ + bool occupies_same_reg(const ColoringAssignment& other) const { + return other.reg_id == reg_id && (reg_id != -1); + } + + /*! + * Are these exactly identical? (not including stack settings) + */ + bool operator==(const ColoringAssignment& other) const { + return (other.kind == kind) && (other.reg_id == reg_id); + } + + /*! + * Has this assignment been set? + */ + bool is_assigned() const { return kind != UNASSIGNED; } +}; + +// A constraint on a specific variable at a specific instruction +struct RegConstraint { + int var_id; // the variable + int instr_id; // the instruction + ColoringAssignment ass; // the assignment of the variable at the instruction +}; + +// An input to the coloring to tell the system what type of reg it can get. +enum RegisterKind { + REG_GPR, + REG_XMM_FLOAT, + UNASSIGNED_REG // a default, which is invalid. This will error if passed to coloring algorithms +}; + +// The input to the coloring system for a variable. +struct ColoringInput { + int id = -1; // variable id + RegisterKind kind = UNASSIGNED_REG; // what type of register it must go in + + std::string print() { + std::string result; + switch (kind) { + case REG_GPR: + result += "gpr "; + break; + case REG_XMM_FLOAT: + result += "xmm "; + break; + default: + throw std::runtime_error("unknown register kind in ColoringInput"); + } + + result += std::to_string(id); + return result; + } +}; + +// Indication of where a variable is live and what assignment it has at each point in the range. +struct LiveRange { + public: + LiveRange(int start, int end) : min(start), max(end) {} + // min, max are inclusive. + // meaning the variable written for the first time at min, and read for the last time at max. + int min, max; + + std::vector is_alive; + std::vector indices_of_alive; + + // which variable is this? + int var = -1; + + // have we actually seen this variable in the code? + bool seen = false; + + // does this variable have a constraint? + bool has_constraint = false; + + // the assignment of this variable at each instruction in [min, max] + std::vector assignment; + + // a hint on where to put this variable. + ColoringAssignment best_hint; + + /*! + * Add an instruction id where this variable is live. + */ + void add_live_instruction(int value) { + if (value > max) + max = value; + if (value < min) + min = value; + indices_of_alive.push_back(value); + // remember that this variable is actually used + seen = true; + } + + /*! + * Is the given instruction contained in the live range? + */ + bool is_live_at_instr(int value) { + if (value >= min && value <= max) { + if (enable_fancy_coloring) { + return is_alive.at(value - min); + } else { + return true; + } + } + return false; + } + + bool becomes_live_at_instr(int idx) { + if (enable_fancy_coloring) { + if (idx == min) + return true; + if (idx < min || idx > max) + return false; + assert(idx > min); + return is_alive.at(idx - min) && !is_alive.at(idx - min - 1); + } else { + return idx == min; + } + } + + bool dies_next_at_instr(int idx) { + if (enable_fancy_coloring) { + if (idx == max) + return true; + if (idx < min || idx > max) + return false; + assert(idx < max); + return is_alive.at(idx - min) && !is_alive.at(idx - min + 1); + } else { + return idx == max; + } + } + + /*! + * Resize Live Range after instructions have been added. Do this before assigning. + */ + void prepare_for_allocation(int id) { + var = id; + if (!seen) + return; // don't do any prep for a variable which isn't used. + assert(max - min >= 0); + assignment.resize(max - min + 1); + is_alive.resize(max - min + 1); + for (auto& x : indices_of_alive) { + is_alive.at(x - min) = true; + } + } + + /*! + * Lock an assignment at a given instruction. + * Will overwrite any previous assignment here + * Will set best_hint to this assignment. + */ + void constrain_at_one(int id, ColoringAssignment ass) { + assert(id >= min && id <= max); + assignment.at(id - min) = ass; + has_constraint = true; + best_hint = ass; + } + + /*! + * At the given instruction, does the given assignment conflict with this one? + */ + bool conflicts_at(int id, ColoringAssignment ass) { + assert(id >= min && id <= max); + return assignment.at(id - min).occupies_same_reg(ass); + } + + /*! + * Assign variable to the given assignment at all instructions + * Throws if this would require modifying a currently set assignment. + */ + void assign_no_overwrite(ColoringAssignment ass) { + assert(seen); + assert(ass.is_assigned()); + for (int i = min; i <= max; i++) { + auto& a = assignment.at(i - min); + if (a.is_assigned() && !(a.occupies_same_reg(ass))) { + throw std::runtime_error("assign_no_overwrite failed!"); + } else { + a = ass; + } + } + } + + /*! + * Get the assignment at the given instruction. + */ + const ColoringAssignment& get(int id) { + assert(id >= min && id <= max); + return assignment.at(id - min); + } + + std::string print() { + std::string result = "Live Range for var " + std::to_string(var) + "\n"; + for (uint32_t i = 0; i < assignment.size(); i++) { + result += "instr " + std::to_string(i + min) + ":" + assignment.at(i).print() + "\n"; + } + return result; + } +}; + +// An extra instruction to load/store variables from the stack +struct BonusOp { + int stack_slot = -1; // stack slot to load/store into + ColoringAssignment ass; // register to load/store into + bool load_from_stack = false; // load from stack into register + bool store_into_stack = false; // store into stack from register + + std::string print() const { + if (!load_from_stack && !store_into_stack) + return ""; + std::string result = ""; + if (load_from_stack) { + result += "load-from-stack "; + } + if (store_into_stack) { + result += "store-into-stack "; + } + result += std::to_string(stack_slot); + return result + ass.print(); + } +}; + +// A list of bonus operations to go with an Instruction +struct RegAllocBonusInstruction { + std::vector ops; + + void clear() { ops.clear(); } + + std::string print() const { + std::string result; + for (auto& op : ops) { + result += op.print() + " "; + } + return result; + } +}; + +#endif // JAK_COLORINGASSIGNMENT_H diff --git a/old_compiler/cpp/codegen/IGen.h b/old_compiler/cpp/codegen/IGen.h new file mode 100644 index 0000000000..21a46ddae1 --- /dev/null +++ b/old_compiler/cpp/codegen/IGen.h @@ -0,0 +1,746 @@ +/*! + * @file IGen.h + * Instruction Generation for x86-64 + * Generate Instruction objects + */ + +#ifndef JAK_IGEN_H +#define JAK_IGEN_H + +#include +#include "Instruction.h" + +class IGen { + public: + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + // MOVES + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + /*! + * mov gpr, gpr, 64 bits + */ + static Instruction mov_gpr64_gpr64(uint8_t dst, uint8_t src) { + Instruction instr(0x89); + instr.set_modrm_and_rex(src, dst, 3, true); + return instr; + } + + /*! + * Move a 64-bit constant into a register. + */ + static Instruction mov_gpr64_u64(uint8_t dst, uint64_t val) { + bool rex_b = false; + if (dst >= 8) { + dst -= 8; + rex_b = true; + } + if (dst < 8) { + Instruction instr(0xb8 + dst); + instr.set(REX(true, false, false, rex_b)); + instr.set(Imm(8, val)); + return instr; + } else { + throw std::runtime_error("bad instruction mov_gpr64_u64"); + } + } + + /*! + * Move a 32-bit constant into a register. + */ + static Instruction mov_gpr64_u32(uint8_t dst, uint64_t val) { + assert(val <= UINT32_MAX); + bool rex_b = false; + if (dst >= 8) { + dst -= 8; + rex_b = true; + } + if (dst < 8) { + Instruction instr(0xb8 + dst); + if (rex_b) { + instr.set(REX(false, false, false, rex_b)); + } + instr.set(Imm(4, val)); + return instr; + } else { + throw std::runtime_error("bad instruction mov_gpr64_u32"); + } + } + + /*! + * Move a signed 32-bit constant into a register. + * When possible prefer mov_gpr64_u32. (use this only for negative values...) + */ + static Instruction mov_gpr64_s32(uint8_t dst, int64_t val) { + assert(val >= INT32_MIN && val <= INT32_MAX); + + Instruction instr(0xc7); + instr.set_modrm_and_rex(0, dst, 3, true); + instr.set(Imm(4, val)); + return instr; + } + + /*! + * Move 32-bits of xmm to 32 bits of gpr (no sign extension). + */ + static Instruction movd_gpr32_xmm32(uint8_t dst, uint8_t src) { + Instruction instr(0x66); + instr.set_op2(0x0f); + instr.set_op3(0x7e); + instr.set_modrm_and_rex(src, dst, 3, false); + instr.swap_op0_rex(); + return instr; + } + + /*! + * Move 32-bits of gpr to 32-bits of xmm (no sign extenion) + */ + static Instruction movd_xmm32_gpr32(uint8_t dst, uint8_t src) { + Instruction instr(0x66); + instr.set_op2(0x0f); + instr.set_op3(0x6e); + instr.set_modrm_and_rex(dst, src, 3, false); + instr.swap_op0_rex(); + return instr; + } + + /*! + * Move 32-bits between xmm's + */ + static Instruction mov_xmm32_xmm32(uint8_t dst, uint8_t src) { + Instruction instr(0xf3); + instr.set_op2(0x0f); + instr.set_op3(0x10); + instr.set_modrm_and_rex(dst, src, 3, false); + return instr; + } + + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + // LOADS n' STORES + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + /*! + * Store 8-bits from register into a memory location that is the sum of a 64-bit register + * and signed 32-bit offset. + */ + static Instruction store8_r64off32s_gpr8(uint8_t dst_reg, int32_t offset, uint8_t src_reg) { + Instruction instr(0x88); + instr.set_modrm_and_rex_for_addr(src_reg, dst_reg, 2, false); + instr.set_disp(Imm(4, offset)); + if (src_reg > RBX) { + instr.add_rex(); + } + return instr; + } + + /*! + * Store 16-bits from register into a memory location that is the sum of a 64-bit register + * and signed 32-bit offset. + */ + static Instruction store16_r64off32s_gpr16(uint8_t dst_reg, int32_t offset, uint8_t src_reg) { + Instruction instr(0x66); + instr.set_op2(0x89); + instr.set_modrm_and_rex_for_addr(src_reg, dst_reg, 2, false); + instr.set_disp(Imm(4, offset)); + return instr; + } + + /*! + * Store 32-bits from register into a memory location that is the sum of a 64-bit register + * and signed 32-bit offset. + */ + static Instruction store32_r64off32s_gpr32(uint8_t dst_reg, int32_t offset, uint8_t src_reg) { + Instruction instr(0x89); + instr.set_modrm_and_rex_for_addr(src_reg, dst_reg, 2, false); + instr.set_disp(Imm(4, offset)); + return instr; + } + + /*! + * Store 64-bits from gpr into memory located at 64-bit reg + 32-bit signed offset. + */ + static Instruction store64_r64off32s_gpr64(uint8_t dst_reg, int32_t offset, uint8_t src_reg) { + Instruction instr(0x89); + instr.set_modrm_rex_sib_for_reg_reg_disp32(src_reg, 2, dst_reg, true); + instr.set_disp(Imm(4, offset)); + return instr; + } + + /*! + * Load 8-bits from memory (at address of 64-bit reg + 32-bit signed offset) into gpr (zero + * extended) + */ + static Instruction load16_gpr8z_r64off32s(uint8_t dst, uint8_t src, int32_t offset) { + Instruction instr(0x0f); + instr.set_op2(0xb6); + instr.set_modrm_rex_sib_for_reg_reg_disp32(dst, 2, src, true); + instr.set_disp(Imm(4, offset)); + return instr; + } + + /*! + * Load 16-bits from memory (at address of 64-bit reg + 32-bit signed offset) into gpr (zero + * extended) + */ + static Instruction load16_gpr16z_r64off32s(uint8_t dst, uint8_t src, int32_t offset) { + Instruction instr(0x0f); + instr.set_op2(0xb7); + instr.set_modrm_rex_sib_for_reg_reg_disp32(dst, 2, src, true); + instr.set_disp(Imm(4, offset)); + return instr; + } + + /*! + * Load 16-bits from memory (at address of 64-bit reg + 32-bit signed offset) into gpr (sign + * extended) + */ + static Instruction load16_gpr16s_r64off32s(uint8_t dst, uint8_t src, int32_t offset) { + Instruction instr(0x0f); + instr.set_op2(0xbf); + instr.set_modrm_rex_sib_for_reg_reg_disp32(dst, 2, src, true); + instr.set_disp(Imm(4, offset)); + return instr; + } + + /*! + * Load 32-bits from memory (at address of 64-bit reg + 32-bit signed offset) into gpr. + * Use the sext flag to enable sign extension. + */ + static Instruction load32_gpr32sz_r64off32s(uint8_t dst_reg, + int32_t offset, + uint8_t src_reg, + bool sext = false) { + Instruction instr(0x8b); + if (sext) { + instr.op = 0x63; + } + instr.set_modrm_rex_sib_for_reg_reg_disp32(dst_reg, 2, src_reg, sext); + instr.set_disp(Imm(4, offset)); + return instr; + } + + /*! + * Load 64-bits from memory located at 64-bit reg + 32-bit signed offset into gpr + */ + static Instruction load64_gpr64_r64off32s(uint8_t dst_reg, int32_t offset, uint8_t src_reg) { + Instruction instr(0x8b); + instr.set_modrm_rex_sib_for_reg_reg_disp32(dst_reg, 2, src_reg, true); + instr.set_disp(Imm(4, offset)); + return instr; + } + + /*! + * Load 32-bits form memory located at 64-bit reg + 32-bit signed offset into xmm (32-bits) + * movss + */ + static Instruction load32_xmm32_r64off32s(uint8_t dst, uint8_t src, int32_t offset) { + Instruction instr(0xf3); + instr.set_op2(0x0f); + instr.set_op3(0x10); + instr.set_modrm_rex_sib_for_reg_reg_disp32(dst, 2, src, false); + instr.set_disp(Imm(4, offset)); + instr.swap_op0_rex(); + return instr; + } + + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + // FUNCTION STUFF + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + /*! + * Return instruction + */ + static Instruction ret() { return Instruction(0xc3); } + + /*! + * Instruction to push gpr (64-bits) onto the stack + */ + static Instruction push_gpr64(uint8_t reg) { + if (reg >= 8) { + auto i = Instruction(0x50 + reg - 8); + i.set(REX(false, false, false, true)); + return i; + } + return Instruction(0x50 + reg); + } + + /*! + * Instruction to pop 64 bit gpr from the stack + */ + static Instruction pop_gpr64(uint8_t reg) { + if (reg >= 8) { + auto i = Instruction(0x58 + reg - 8); + i.set(REX(false, false, false, true)); + return i; + } + return Instruction(0x58 + reg); + } + + /*! + * Call a function stored in a 64-bit gpr + */ + static Instruction call_r64(uint8_t reg) { + Instruction instr(0xff); + if (reg >= 8) { + instr.set(REX(false, false, false, true)); + reg -= 8; + } + assert(reg < 8); + ModRM mrm; + mrm.rm = reg; + mrm.reg_op = 2; + mrm.mod = 3; + instr.set(mrm); + return instr; + } + + /*! + * Call a function stored in a 64-bit gpr + */ + static Instruction jmp_r64(uint8_t reg) { + Instruction instr(0xff); + if (reg >= 8) { + instr.set(REX(false, false, false, true)); + reg -= 8; + } + assert(reg < 8); + ModRM mrm; + mrm.rm = reg; + mrm.reg_op = 4; + mrm.mod = 3; + instr.set(mrm); + return instr; + } + + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + // INTEGER MATH + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + /*! + * Add 64-bit registers. + */ + static Instruction add_gpr64_gpr64(uint8_t dst, uint8_t src) { + Instruction instr(0x01); + instr.set_modrm_and_rex(src, dst, 3, true); + return instr; + } + + /*! + * Add a signed 32 bit immediate to a 64 bit register + * TODO: determine if we can decrease to imm16? + */ + static Instruction add_gpr64_imm32s(uint8_t dst, int32_t offset) { + Instruction instr(0x81); + instr.set_modrm_and_rex(0, dst, 3, true); + instr.set(Imm(4, offset)); + return instr; + } + + /*! + * Add a signed 32 bit immediate to a 64 bit register + * TODO: determine if we can decrease to imm16? + */ + static Instruction add_gpr64_imm8s(uint8_t dst, int8_t v) { + Instruction instr(0x83); + instr.set_modrm_and_rex(0, dst, 3, true); + instr.set(Imm(1, v)); + return instr; + } + + /*! + * Subtract 64-bit registers + */ + static Instruction sub_gpr64_gpr64(uint8_t dst, uint8_t src) { + Instruction instr(0x29); + instr.set_modrm_and_rex(src, dst, 3, true); + return instr; + } + + /*! + * Multiply gprs (32-bit, signed). + */ + static Instruction imul_gpr32_gpr32(uint8_t dst, uint8_t src) { + Instruction instr(0xf); + instr.set_op2(0xaf); + instr.set_modrm_and_rex(dst, src, 3, false); + return instr; + } + + /*! + * Divide (idiv, 32 bit) + */ + static Instruction idiv_gpr32(uint8_t reg) { + Instruction instr(0xf7); + instr.set_modrm_and_rex(7, reg, 3, false); + return instr; + } + + /*! + * Convert doubleword to quadword for division. + * Blame Intel for this disaster. + */ + static Instruction cdq() { + Instruction instr(0x99); + return instr; + } + + /*! + * Move from gpr32 to gpr64, with sign extension. + * Needed for division madness. + */ + static Instruction movsx_r64_r32(uint8_t dst, uint8_t src) { + Instruction instr(0x63); + instr.set_modrm_and_rex(dst, src, 3, true); + return instr; + } + + /*! + * Compare gpr64. This sets the flags for the jumps. + */ + static Instruction cmp_gpr64_gpr64(uint8_t a, uint8_t b) { + Instruction instr(0x3b); + instr.set_modrm_and_rex(a, b, 3, true); + return instr; + } + + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + // BIT STUFF + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + /*! + * Or of two gprs + */ + static Instruction or_gpr64_gpr64(uint8_t dst, uint8_t src) { + Instruction instr(0x0b); + instr.set_modrm_and_rex(dst, src, 3, true); + return instr; + } + + /*! + * And of two gprs + */ + static Instruction and_gpr64_gpr64(uint8_t dst, uint8_t src) { + Instruction instr(0x23); + instr.set_modrm_and_rex(dst, src, 3, true); + return instr; + } + + /*! + * Xor of two gprs + */ + static Instruction xor_gpr64_gpr64(uint8_t dst, uint8_t src) { + Instruction instr(0x33); + instr.set_modrm_and_rex(dst, src, 3, true); + return instr; + } + + /*! + * This is the way "real" compilers zero registers, so we should do it too. + */ + static Instruction xor_zero_gpr(uint8_t reg) { + Instruction instr(0x31); + instr.set_modrm_and_rex(reg, reg, 3, false); + return instr; + } + + /*! + * Bitwise not a gpr + */ + static Instruction not_gpr64(uint8_t reg) { + Instruction instr(0xf7); + instr.set_modrm_and_rex(2, reg, 3, true); + return instr; + } + + /*! + * Shift 64-bit gpr left by CL register + */ + static Instruction shl_gpr64_cl(uint8_t reg) { + Instruction instr(0xd3); + instr.set_modrm_and_rex(4, reg, 3, true); + return instr; + } + + /*! + * Shift 64-bit gpr right (logical) by CL register + */ + static Instruction shr_gpr64_cl(uint8_t reg) { + Instruction instr(0xd3); + instr.set_modrm_and_rex(5, reg, 3, true); + return instr; + } + + /*! + * Shift 64-bit gpr right (arithmetic) by CL register + */ + static Instruction sar_gpr64_cl(uint8_t reg) { + Instruction instr(0xd3); + instr.set_modrm_and_rex(7, reg, 3, true); + return instr; + } + + /*! + * Shift 64-ptr left (logical) by the constant shift amount "sa". + */ + static Instruction shl_gpr64_u8(uint8_t reg, uint8_t sa) { + Instruction instr(0xc1); + instr.set_modrm_and_rex(4, reg, 3, true); + instr.set(Imm(1, sa)); + return instr; + } + + /*! + * Shift 64-ptr right (logical) by the constant shift amount "sa". + */ + static Instruction shr_gpr64_u8(uint8_t reg, uint8_t sa) { + Instruction instr(0xc1); + instr.set_modrm_and_rex(5, reg, 3, true); + instr.set(Imm(1, sa)); + return instr; + } + + /*! + * Shift 64-ptr right (arithmetic) by the constant shift amount "sa". + */ + static Instruction sar_gpr64_u8(uint8_t reg, uint8_t sa) { + Instruction instr(0xc1); + instr.set_modrm_and_rex(7, reg, 3, true); + instr.set(Imm(1, sa)); + return instr; + } + + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + // CONTROL FLOW + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + /*! + * Jump, 32-bit constant offset. The offset is by default 0 and must be patched later. + */ + static Instruction jmp_32() { + Instruction instr(0xe9); + instr.set(Imm(4, 0)); + return instr; + } + + /*! + * Jump if equal. + * TODO - can we get away with 16 bits? + */ + static Instruction je_32() { + Instruction instr(0x0f); + instr.set_op2(0x84); + instr.set(Imm(4, 0)); + return instr; + } + + /*! + * Jump not equal. + * TODO - can we get away with 16 bits? + */ + static Instruction jne_32() { + Instruction instr(0x0f); + instr.set_op2(0x85); + instr.set(Imm(4, 0)); + return instr; + } + + /*! + * Jump less than or equal. + * TODO - can we get away with 16 bits? + */ + static Instruction jle_32() { + Instruction instr(0x0f); + instr.set_op2(0x8e); + instr.set(Imm(4, 0)); + return instr; + } + + /*! + * Jump greater than or equal. + * TODO - can we get away with 16 bits? + */ + static Instruction jge_32() { + Instruction instr(0x0f); + instr.set_op2(0x8d); + instr.set(Imm(4, 0)); + return instr; + } + + /*! + * Jump less than + * TODO - can we get away with 16 bits? + */ + static Instruction jl_32() { + Instruction instr(0x0f); + instr.set_op2(0x8c); + instr.set(Imm(4, 0)); + return instr; + } + + /*! + * Jump greater than + * TODO - can we get away with 16 bits? + */ + static Instruction jg_32() { + Instruction instr(0x0f); + instr.set_op2(0x8f); + instr.set(Imm(4, 0)); + return instr; + } + + /*! + * Jump below or equal + * TODO - can we get away with 16 bits? + */ + static Instruction jbe_32() { + Instruction instr(0x0f); + instr.set_op2(0x86); + instr.set(Imm(4, 0)); + return instr; + } + + /*! + * Jump above or equal + * TODO - can we get away with 16 bits? + */ + static Instruction jae_32() { + Instruction instr(0x0f); + instr.set_op2(0x83); + instr.set(Imm(4, 0)); + return instr; + } + + /*! + * Jump below + * TODO - can we get away with 16 bits? + */ + static Instruction jb_32() { + Instruction instr(0x0f); + instr.set_op2(0x82); + instr.set(Imm(4, 0)); + return instr; + } + + /*! + * Jump above + * TODO - can we get away with 16 bits? + */ + static Instruction ja_32() { + Instruction instr(0x0f); + instr.set_op2(0x87); + instr.set(Imm(4, 0)); + return instr; + } + + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + // FLOAT MATH + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + /*! + * Compare two floats and set flag register for jump + */ + static Instruction cmp_flt_flt(uint8_t a, uint8_t b) { + Instruction instr(0x0f); + instr.set_op2(0x2e); + instr.set_modrm_and_rex(a, b, 3, false); + return instr; + } + + /*! + * Multiply two floats in xmm's + */ + static Instruction mulss_xmm_xmm(uint8_t dst, uint8_t src) { + Instruction instr(0xf3); + instr.set_op2(0x0f); + instr.set_op3(0x59); + instr.set_modrm_and_rex(dst, src, 3, false); + instr.swap_op0_rex(); + return instr; + } + + /*! + * Divide two floats in xmm's + */ + static Instruction divss_xmm_xmm(uint8_t dst, uint8_t src) { + Instruction instr(0xf3); + instr.set_op2(0x0f); + instr.set_op3(0x5e); + instr.set_modrm_and_rex(dst, src, 3, false); + instr.swap_op0_rex(); + return instr; + } + + /*! + * Subtract two floats in xmm's + */ + static Instruction subss_xmm_xmm(uint8_t dst, uint8_t src) { + Instruction instr(0xf3); + instr.set_op2(0x0f); + instr.set_op3(0x5c); + instr.set_modrm_and_rex(dst, src, 3, false); + instr.swap_op0_rex(); + return instr; + } + + /*! + * Add two floats in xmm's + */ + static Instruction addss_xmm_xmm(uint8_t dst, uint8_t src) { + Instruction instr(0xf3); + instr.set_op2(0x0f); + instr.set_op3(0x58); + instr.set_modrm_and_rex(dst, src, 3, false); + instr.swap_op0_rex(); + return instr; + } + + /*! + * Convert GPR int32 to XMM float (single precision) + */ + static Instruction int32_to_float(uint8_t dst, uint8_t src) { + Instruction instr(0xf3); + instr.set_op2(0x0f); + instr.set_op3(0x2a); + instr.set_modrm_and_rex(dst, src, 3, false); + instr.swap_op0_rex(); + return instr; + } + + /*! + * Convert XMM float to GPR int32(single precision) (truncate) + */ + static Instruction float_to_int64(uint8_t dst, uint8_t src) { + Instruction instr(0xf3); + instr.set_op2(0x0f); + instr.set_op3(0x2c); + instr.set_modrm_and_rex(dst, src, 3, true); + instr.swap_op0_rex(); + return instr; + } + + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + // UTILITIES + //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + /*! + * A "null" instruction. This instruction does not generate any bytes + * but can be referred to by a label. Useful to insert in place of a real instruction + * if the real instruction has been optimized out. + */ + static Instruction null() { + Instruction i(0); + i.is_null = true; + return i; + } + + /*! + * A "function start" instruction. This emits no opcodes, but is used + * to determine where to insert the function type tag and how to align a function. + */ + static Instruction function_start() { + Instruction i(0); + i.is_null = true; + i.is_function_start = true; + return i; + } +}; + +#endif // JAK_IGEN_H diff --git a/old_compiler/cpp/codegen/Instruction.h b/old_compiler/cpp/codegen/Instruction.h new file mode 100644 index 0000000000..bef3f2c7a1 --- /dev/null +++ b/old_compiler/cpp/codegen/Instruction.h @@ -0,0 +1,344 @@ +/*! + * @file: Instruction.h + * x86-64 Instruction encoding. + */ + +#ifndef JAK_INSTRUCTION_H +#define JAK_INSTRUCTION_H + +#include + +/*! + * The ModRM byte + */ +struct ModRM { + uint8_t mod; + uint8_t reg_op; + uint8_t rm; + + uint8_t operator()() { return (mod << 6) | (reg_op << 3) | (rm << 0); } +}; + +/*! + * The SIB Byte + */ +struct SIB { + uint8_t scale, index, base; + + uint8_t operator()() { return (scale << 6) | (index << 3) | (base << 0); } +}; + +/*! + * An Immediate (either imm or disp) + */ +struct Imm { + Imm() = default; + Imm(uint8_t sz, uint64_t v) : size(sz), value(v) {} + uint8_t size; + union { + uint64_t value; + uint8_t v_arr[8]; + }; +}; + +/*! + * The REX prefix byte + */ +struct REX { + REX(bool w = false, bool r = false, bool x = false, bool b = false) : W(w), R(r), X(x), B(b) {} + // W - 64-bit operands + // R - reg extension + // X - SIB i extnsion + // B - other extension + bool W, R, X, B; + uint8_t operator()() { return (1 << 6) | (W << 3) | (R << 2) | (X << 1) | (B << 0); } +}; + +/*! + * A high-level description of an x86-64 opcode. It can emit itself. + */ +struct Instruction { + Instruction(uint8_t opcode) : op(opcode) {} + uint8_t op; + + bool op2_set = false; + uint8_t op2; + + bool op3_set = false; + uint8_t op3; + + // if true, don't emit anything + bool is_null = false; + + // flag to indicate it's the first instruction of a function and needs align and type tag + bool is_function_start = false; + + // the rex byte + bool set_rex = false; + uint8_t m_rex = 0; + + // the modrm byte + bool set_modrm = false; + uint8_t m_modrm = 0; + + // the sib byte + bool set_sib = false; + uint8_t m_sib = 0; + + // the displacement + bool set_disp_imm = false; + Imm disp; + + // the immediate + bool set_imm = false; + Imm imm; + + // which IR instruction does this go with? + // this is only set for the first instruction generated from an IR. + int ir_index = -1; + + /*! + * Move opcode byte 0 to before the rex prefix. + */ + void swap_op0_rex() { + if (!set_rex) + return; + auto temp = op; + op = m_rex; + m_rex = temp; + } + + void set(REX r) { + m_rex = r(); + set_rex = true; + } + + void set(ModRM modrm) { + m_modrm = modrm(); + set_modrm = true; + } + + void set(SIB sib) { + m_sib = sib(); + set_sib = true; + } + + void set_disp(Imm i) { + disp = i; + set_disp_imm = true; + } + + void set(Imm i) { + imm = i; + set_imm = true; + } + + void set_op2(uint8_t b) { + op2_set = true; + op2 = b; + } + + void set_op3(uint8_t b) { + op3_set = true; + op3 = b; + } + + /*! + * Set modrm and rex as needed for two regs. + */ + void set_modrm_and_rex(uint8_t reg, uint8_t rm, uint8_t mod, bool rex_w = false) { + bool rex_b = false, rex_r = false; + + if (rm >= 8) { + rm -= 8; + rex_b = true; + } + + if (reg >= 8) { + reg -= 8; + rex_r = true; + } + + ModRM modrm; + modrm.mod = mod; + modrm.reg_op = reg; + modrm.rm = rm; + + set(modrm); + + if (rex_b || rex_w || rex_r) { + set(REX(rex_w, rex_r, false, rex_b)); + } + } + + /*! + * Set modrm and rex as needed for two regs for an addressing mode. + * Will set SIB if R12 or RSP indexing is used. + */ + void set_modrm_and_rex_for_addr(uint8_t reg, uint8_t rm, uint8_t mod, bool rex_w = false) { + bool rex_b = false, rex_r = false; + + if (rm >= 8) { + rm -= 8; + rex_b = true; + } + + if (reg >= 8) { + reg -= 8; + rex_r = true; + } + + ModRM modrm; + modrm.mod = mod; + modrm.reg_op = reg; + modrm.rm = rm; + + set(modrm); + + if (rm == 4) { + SIB sib; + sib.scale = 0; + sib.base = 4; + sib.index = 4; + + set(sib); + } + + if (rex_b || rex_w || rex_r) { + set(REX(rex_w, rex_r, false, rex_b)); + } + } + + void add_rex() { + if (!set_rex) { + set(REX()); + } + } + + /*! + * Set up modrm and rex for the commonly used 32-bit immediate displacement indexing mode. + */ + void set_modrm_rex_sib_for_reg_reg_disp32(uint8_t reg, uint8_t mod, uint8_t rm, bool rex_w) { + ModRM modrm; + + bool rex_r = false; + if (reg >= 8) { + reg -= 8; + rex_r = true; + } + modrm.reg_op = reg; + + modrm.mod = mod; + + modrm.rm = 4; // use sib + + SIB sib; + sib.scale = 0; + sib.index = 4; + bool rex_b = false; + if (rm >= 8) { + rex_b = true; + rm -= 8; + } + + sib.base = rm; + + set(modrm); + set(sib); + + if (rex_r || rex_w || rex_b) { + set(REX(rex_w, rex_r, false, rex_b)); + } + } + + /*! + * Get the position of the disp immediate relative to the start of the instruction + */ + int offset_of_disp() { + if (is_null) + return 0; + assert(set_disp_imm); + int offset = 0; + if (set_rex) + offset++; + offset++; // opcode + if (op2_set) + offset++; + if (op3_set) + offset++; + if (set_modrm) + offset++; + if (set_sib) + offset++; + return offset; + } + + /*! + * Get the position of the imm immediate relative to the start of the instruction + */ + int offset_of_imm() { + if (is_null) + return 0; + assert(set_imm); + int offset = 0; + if (set_rex) + offset++; + offset++; // opcode + if (op2_set) + offset++; + if (op3_set) + offset++; + if (set_modrm) + offset++; + if (set_sib) + offset++; + if (set_disp_imm) + offset += disp.size; + return offset; + } + + /*! + * Emit into a buffer and return how many bytes written (can be zero) + */ + uint8_t emit(uint8_t* buffer) { + if (is_null) + return 0; + uint8_t count = 0; + if (set_rex) { + buffer[count++] = m_rex; + } + + buffer[count++] = op; + + if (op2_set) { + buffer[count++] = op2; + } + + if (op3_set) { + buffer[count++] = op3; + } + + if (set_modrm) { + buffer[count++] = m_modrm; + } + + if (set_sib) { + buffer[count++] = m_sib; + } + + if (set_disp_imm) { + for (int i = 0; i < disp.size; i++) { + buffer[count++] = disp.v_arr[i]; + } + } + + if (set_imm) { + for (int i = 0; i < imm.size; i++) { + buffer[count++] = imm.v_arr[i]; + } + } + return count; + } +}; + +#endif // JAK_INSTRUCTION_H diff --git a/old_compiler/cpp/codegen/StaticLinkRecord.h b/old_compiler/cpp/codegen/StaticLinkRecord.h new file mode 100644 index 0000000000..9c4e339b39 --- /dev/null +++ b/old_compiler/cpp/codegen/StaticLinkRecord.h @@ -0,0 +1,13 @@ +#ifndef JAK_V2_STATICRECORD_H +#define JAK_V2_STATICRECORD_H + +struct StaticLinkRecord { + enum Kind { TYPE_PTR, SYMBOL_PTR } kind; + + StaticLinkRecord() = default; + StaticLinkRecord(Kind _kind, int _offset) : kind(_kind), offset(_offset) {} + + int offset = -1; +}; + +#endif // JAK_V2_STATICRECORD_H diff --git a/old_compiler/cpp/codegen/codegen_utils.h b/old_compiler/cpp/codegen/codegen_utils.h new file mode 100644 index 0000000000..4c9305f5a3 --- /dev/null +++ b/old_compiler/cpp/codegen/codegen_utils.h @@ -0,0 +1,23 @@ +/*! + * @file codegen_utils.h + * Commonly used utility functions in the codegen library. + */ + +#ifndef JAK_CODEGEN_UTILS_H +#define JAK_CODEGEN_UTILS_H + +#include + +/*! + * Push a thing into a byte vector. + */ +template +uint32_t push_data_to_byte_vector(T data, std::vector& v) { + auto* ptr = (uint8_t*)(&data); + for (std::size_t i = 0; i < sizeof(T); i++) { + v.push_back(ptr[i]); + } + return sizeof(T); +} + +#endif // JAK_CODEGEN_UTILS_H diff --git a/old_compiler/cpp/codegen/x86.h b/old_compiler/cpp/codegen/x86.h new file mode 100644 index 0000000000..60cd8505b0 --- /dev/null +++ b/old_compiler/cpp/codegen/x86.h @@ -0,0 +1,110 @@ +/*! + * @file x86.h + * x86-64 register definitions and calling convention + */ + +#ifndef JAK_X86_H +#define JAK_X86_H + +// nicknames for gprs and xmm's +enum X86_Registers { + RAX, // return, temp + RCX, // arg 3 + RDX, // arg 2 + RBX, // X saved + + RSP, // stack pointer + RBP, // X base pointer (like fp) + RSI, // arg 1 + RDI, // arg 0 + + R8, // arg 4 + R9, // arg 5 + R10, // arg 6 - GOAL only + R11, // arg 7 - GOAL only + R12, // X saved + R13, // X saved - function call register (like t9) + R14, // X saved - offset + R15, // X saved - st + XMM0, + XMM1, + XMM2, + XMM3, + XMM4, + XMM5, + XMM6, + XMM7, + XMM8, + XMM9, + XMM10, + XMM11, + XMM12, + XMM13, + XMM14, + XMM15 +}; + +// the argument registers of GOAL. +// the first 6 are shared with Linux. +constexpr uint8_t ARG_REGS[8] = { + RDI, RSI, RDX, RCX, R8, R9, R10, R11, +}; + +constexpr int SAVED_REG_COUNT = 2; +constexpr uint8_t SAVED_REGS[SAVED_REG_COUNT] = { + RBX, + // R12, + R13 // we don't really have to do this... +}; + +// todo - move to an xmm? +constexpr uint8_t PP_REG = R12; + +// register used to hold the address of the function we're calling +// this is a GOAL only thing +constexpr uint8_t T9_REG = R13; + +// register used to hold the address of the current function +constexpr uint8_t BP_REG = RBP; + +// register used to return data (GOAL and Linux) +constexpr uint8_t RET_REG = RAX; + +// reserved register which holds the offset from GOAL pointers to real memory addresses +constexpr uint8_t OFF_REG = R14; + +// reserved register which holds the pointer to the symbol table +// todo should this hold a GOAL pointer or real pointer? currently a real pointer +constexpr uint8_t ST_REG = R15; + +constexpr int PTR_SIZE = 4; +constexpr int GPR_SIZE = 8; + +static const char* x86_gpr_names[] = { + "rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", "r8", "r9", "r10", + "r11", "r12", "r13", "r14", "r15", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", + "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15"}; + +/* + Name Arg ID Clobber? Special + RAX - y return + RCX 3 y arg + RDX 2 y arg + RBX - n + + RSP - n stack pointer + RBP - n base pointer + RSI 1 y arg + RDI 0 y arg + + R8 4 y arg + R9 5 y arg + R10 + R11 + R12 + R13 + R14 + R15 + */ + +#endif // JAK_X86_H diff --git a/old_compiler/cpp/codegen/x86_Emitter.cpp b/old_compiler/cpp/codegen/x86_Emitter.cpp new file mode 100644 index 0000000000..03f1dd862f --- /dev/null +++ b/old_compiler/cpp/codegen/x86_Emitter.cpp @@ -0,0 +1,476 @@ +/*! + * @file x86_Emitter.cpp + * Emitter for converting IR and static objects into GOAL object files for x86. + */ + +#include +#include "x86_Emitter.h" +#include "x86.h" +#include "shared_config.h" +#include "IGen.h" +#include "codegen_utils.h" +#include "goal/GoalEnv.h" + +/*! + * Add link data to insert a pointer to the function type at the given offset into the given + * segment. + */ +void x86_Emitter::link_function_type_ptr(int segment, int offset) { + function_type_ptr_recs[segment].push_back(offset); +} + +/*! + * Write a CodegenOutput from everything in the emitter. + */ +CodegenOutput x86_Emitter::write() { + CodegenOutput out; + + // first pass over segments to emit code + for (int seg = N_SEG; seg-- > 0;) { + // loop over instructions + for (auto& i : instructions[seg]) { + // do alignment/typing if it's the beginning of a function + if (i.is_function_start) { + // align function (todo consider aligning more for good cache performance) + while (out.code[seg].size() & 7) + out.code[seg].push_back(0); + + // functions should start with a function type tag + link_function_type_ptr(seg, out.code[seg].size()); + + // add padding for that type tag + for (int j = 0; j < PTR_SIZE; j++) { + out.code[seg].push_back(0xae); + } + } + + // emit instruction + uint8_t temp[128]; + auto count = i.emit(temp); + + // remember where this instruction is in the output + out.instr_offsets[seg].push_back(out.code[seg].size()); + + // add instruction to output + for (int j = 0; j < count; j++) { + out.code[seg].push_back(temp[j]); + } + } + } + + // second pass over segments to emit static objects + for (int seg = N_SEG; seg-- > 0;) { + emit_static_objects(out, seg); + } + + // third pass over segments to fix jumps and emit link tables + for (int seg = N_SEG; seg-- > 0;) { + // fix up jumps + patch_jumps_and_recs(out, out.instr_offsets[seg], seg); + + // link table can now be emitted + emit_link_table_data(out, out.instr_offsets[seg], seg); + } + + // now we are all done, we know enough to set up the header + emit_link_table_header(out); + return out; +} + +/*! + * Moves static objects from the temporary static object holding vector to the code. + * Also updates the type_ptr_recs to be relative to the start of the code, not the start of the + * statics. + */ +void x86_Emitter::emit_static_objects(CodegenOutput& out, int segment) { + auto& code = out.code.at(segment); + auto& statics = static_objects.at(segment); + + // 16 byte align the statics section + while (code.size() & 15) { + code.push_back(0); + } + + // remember the start location + auto static_start = code.size(); + out.static_start[segment] = static_start; + + // apply offset to recs to include the size of the code segment + for (auto& rec : type_ptr_recs_in_statics.at(segment)) { + for (auto& v : rec.second) { + v.offset += static_start; + } + } + + // add to output + code.insert(code.end(), statics.begin(), statics.end()); +} + +/*! + * Do patching that can only be done after all instruction lengths are known + * Patch jumps to go the right spot. Forward jumps can't know how long the instructions will actully + * be, so this has to be done after. + * + * Also computes the full offset of symbol mem access recs, which also need to know how long + * instructions are + */ +void x86_Emitter::patch_jumps_and_recs(CodegenOutput& out, std::vector& offsets, int seg) { + for (auto& jmp : static_jumps[seg]) { + switch (jmp.type) { + case SIGNED_32_RIP: { + // calculate the location of the offset + int32_t* slot = + (int32_t*)(out.code[seg].data() + offsets.at(jmp.instr_idx) + jmp.offset_into_instr); + + // find the target instruction (must be forward jump) + auto instr = + std::find_if(instructions[seg].begin() + jmp.function_offset, instructions[seg].end(), + [&](const Instruction& i) { return i.ir_index == jmp.target_ir_idx; }); + + if (instr == instructions[seg].end()) { + throw std::runtime_error("couldn't find instruction matching IR idx in patch jumps!"); + } + + int target_instr_idx = std::distance(instructions[seg].begin(), instr); + + // store the correct offset. + *slot = offsets.at(target_instr_idx) - offsets.at(jmp.instr_idx + 1); + } break; + default: + throw std::runtime_error("unknown jump kind in x86_Emitter::patch_jumps_and_recs"); + } + } + + // patch symbol access as well. + for (auto& sym_recs_map : symbol_mem_access_recs) { + for (auto& sym_recs : sym_recs_map) { + for (auto& rec : sym_recs.second) { + if (rec.seg == seg) { + rec.total_offset = rec.offset + offsets.at(rec.instr_idx); + } + } + } + } +} + +/*! + * Emit the function prologue + */ +void x86_Emitter::emit_prologue() { + stack_offset = 0; + + // currently we back up all the registers always. + for (int i = 0; i < SAVED_REG_COUNT; i++) { + if (f->uses_saved_reg[i]) { + push(ColoringAssignment(REGISTER, SAVED_REGS[i])); + stack_offset += GPR_SIZE; + } + } + + if (f->uses_rbp) { + // Setup function registers + ColoringAssignment rbp(REGISTER, BP_REG); // base register + ColoringAssignment r13(REGISTER, T9_REG); // function call register + + // push old base register + push(rbp); + stack_offset += GPR_SIZE; + + // set base register to call register + mov(rbp, r13); + } + + // compute total required stack offset of this function, including stack variable + stack_offset += GPR_SIZE * f->stack_slots; + + // the portion of the stack offset which must be added manually (not by push instructions) + additional_stack_offset = f->stack_slots * GPR_SIZE; + + extra_push_sr0 = false; + + if (additional_stack_offset || f->requires_aligned_stack) { + // check that the total is aligned correctly + if (!(stack_offset & 15)) { + // if not, add some additional offset to make it correct + if (additional_stack_offset) { + additional_stack_offset += 8; + } else { + extra_push_sr0 = true; + push(ColoringAssignment(REGISTER, SAVED_REGS[0])); + } + + stack_offset += 8; + } + + // ok, stack should be aligned now + assert((stack_offset & 15)); + + // move RSP manually, if we need to. + if (additional_stack_offset) { + if (additional_stack_offset < 126) { + instructions[current_seg].push_back(IGen::add_gpr64_imm8s(RSP, -additional_stack_offset)); + } else { + instructions[current_seg].push_back(IGen::add_gpr64_imm32s(RSP, -additional_stack_offset)); + } + } + } +} + +/*! + * Emit the function epilogue + */ +void x86_Emitter::emit_epilogue() { + // reset RSP if needed. + + if (additional_stack_offset || f->requires_aligned_stack) { + if (additional_stack_offset) { + if (additional_stack_offset < 126) { + instructions[current_seg].push_back(IGen::add_gpr64_imm8s(RSP, additional_stack_offset)); + } else { + instructions[current_seg].push_back(IGen::add_gpr64_imm32s(RSP, additional_stack_offset)); + } + } + + if (extra_push_sr0) { + assert(!additional_stack_offset); + pop(ColoringAssignment(REGISTER, SAVED_REGS[0])); + } + } + + // reset RBP + if (f->uses_rbp) { + pop(ColoringAssignment(REGISTER, BP_REG)); + } + + for (int i = SAVED_REG_COUNT; i-- > 0;) { + if (f->uses_saved_reg[i]) { + pop(ColoringAssignment(REGISTER, SAVED_REGS[i])); + } + } + + // + // // reset all registers + // pop(ColoringAssignment(REGISTER, RBX)); + // for(int i = (R15 + 1); i-- > R12;) { + // pop(ColoringAssignment(REGISTER, i)); + // } + + // return! + instructions[current_seg].push_back(IGen::ret()); +} + +/*! + * Process a function. + */ +int x86_Emitter::run(FunctionEnv* func, int target_segment) { + // insert a function start instruction + instructions[target_segment].push_back(IGen::function_start()); + + // set up + function_offset = instructions[target_segment].size(); + f = func; + current_rbp_instr_idx = instructions[target_segment].size(); + current_seg = target_segment; + + // add the function prologue + if (!f->is_asm_func) { + emit_prologue(); + } else { + stack_offset = 0; + additional_stack_offset = 0; + } + + // add all the instructions + for (ir_idx = 0; ir_idx < (int)f->code.size(); ir_idx++) { + auto& x = f->code.at(ir_idx); + + // load anything off the stack needed for this instruction + auto& bonus = f->bonus_instructions.at(ir_idx); + for (auto& op : bonus.ops) { + if (op.load_from_stack) { + emit_instr(IGen::load64_gpr64_r64off32s(op.ass.reg_id, op.stack_slot * GPR_SIZE, RSP)); + } + } + + // ugly switch to dispatch the right function to turn the IR into x86 instructions + switch (x->kind) { + case RETURN: + do_return(*dynamic_cast(x.get())); + break; + case LOAD_INTEGER: + do_constvar(*dynamic_cast(x.get())); + break; + case SET: + do_set(*dynamic_cast(x.get())); + break; + case GOTO_LABEL: + do_goto_label(*dynamic_cast(x.get())); + break; + case SET_SYMBOL_VALUE: + do_set_symbol(*dynamic_cast(x.get())); + break; + case GET_SYMBOL_VALUE: + do_get_symbol(*dynamic_cast(x.get())); + break; + case FUNCTION_CALL: + do_function_call(*dynamic_cast(x.get())); + break; + case STATIC_VAR_ADDR: + do_static_var_addr(*dynamic_cast(x.get())); + break; + case FUNC_ADDR: + do_function_addr(*dynamic_cast(x.get())); + break; + case IR_NULL: + emit_instr(IGen::null()); + break; + case FUNCTION_BEGIN: + // do nothing! + break; + case INTEGER_MATH: + do_integer_math(*dynamic_cast(x.get())); + break; + case GET_SYMBOL_OBJ: + do_get_symbol_object(*dynamic_cast(x.get())); + break; + case CONDITIONAL_BRANCH: + do_cond_branch(*dynamic_cast(x.get())); + break; + case STATIC_VAR_32: + do_static_var_32(*dynamic_cast(x.get())); + break; + case FLOAT_MATH: + do_float_math(*dynamic_cast(x.get())); + break; + case LOAD_CONST_OFFSET: + do_load_const_offset(*dynamic_cast(x.get())); + break; + case STORE_CONST_OFFSET: + do_store_const_offset(*dynamic_cast(x.get())); + break; + case FLOAT_TO_INT: + do_float_to_int(*dynamic_cast(x.get())); + break; + case INT_TO_FLOAT: + do_int_to_float(*dynamic_cast(x.get())); + break; + case GET_RETURN_ADDRESS_POINTER: + do_get_ra_ptr(*dynamic_cast(x.get())); + break; + case ASM: + do_asm(*dynamic_cast(x.get())); + break; + default: + throw std::runtime_error("unknown IR in emitter: " + x->print()); + } + + // store anything onto the stack that is requested. + for (auto& op : bonus.ops) { + if (op.store_into_stack) { + emit_instr(IGen::store64_r64off32s_gpr64(RSP, op.stack_slot * 8, op.ass.reg_id)); + } + } + } + + // function epilogue + if (!f->is_asm_func) { + emit_epilogue(); + } + + // clean up + f = nullptr; + current_seg = -1; + return function_offset; +} + +/*! + * Insert a static object + */ +void x86_Emitter::run(StaticObject* obj, int target_segment) { + // this goes in temp storage because we want all functions before any static objects + // TODO - support for static object with symbols. + obj->emit_into(static_objects.at(target_segment), type_ptr_recs_in_statics.at(target_segment)); +} + +/*! + * Utility function to generate moves for function prologues/epilogues. + * The newer stuff is preferred for IR translation. + */ +void x86_Emitter::mov(ColoringAssignment dst, ColoringAssignment src) { + switch (dst.kind) { + case REGISTER: + switch (src.kind) { + case REGISTER: + instructions[current_seg].push_back(IGen::mov_gpr64_gpr64(dst.reg_id, src.reg_id)); + break; + default: + throw std::runtime_error("can't move from this place"); + } + break; + default: + throw std::runtime_error("can't move to this place"); + } +} + +/*! + * Utility function to generate pushes for function prologues/epilogues + * The newer stuff is preferred for IR translation. + */ +void x86_Emitter::push(ColoringAssignment src) { + switch (src.kind) { + case REGISTER: + instructions[current_seg].push_back(IGen::push_gpr64(src.reg_id)); + break; + default: + throw std::runtime_error("can't push this"); + } +} + +/*! + * Utility function to generate pops for function prologues/epilogues + * The newer stuff is preferred for IR translation. + */ +void x86_Emitter::pop(ColoringAssignment src) { + switch (src.kind) { + case REGISTER: + instructions[current_seg].push_back(IGen::pop_gpr64(src.reg_id)); + break; + default: + throw std::runtime_error("can't pop this"); + } +} + +/*! + * Get the coloring assignment of a given place. + */ +ColoringAssignment x86_Emitter::get_ca(Place& var) { + return f->coloring.at(var.get_assignment().id).get(ir_idx); +} + +/*! + * Get the gpr id number of the current variable, at the current IR. + */ +uint8_t x86_Emitter::gpr_id(Place& var) { + if (var.get_assignment().kind != REG_GPR) { + throw std::runtime_error("looked up coloring as gpr something which isn't a colored as a gpr " + + var.print() + " ass " + var.get_assignment().print()); + } + const auto& result = get_ca(var); + assert(result.is_assigned()); + assert(result.kind == REGISTER); + return result.reg_id; +} + +/*! + * Get the xmm id number of the current variable, at the current IR. + */ +uint8_t x86_Emitter::xmm_id(Place& var) { + if (var.get_assignment().kind != REG_XMM_FLOAT) { + throw std::runtime_error("looked up coloring as xmm something which isn't a colored as a xmm" + + var.print() + " ass " + var.get_assignment().print()); + } + const auto& result = get_ca(var); + assert(result.is_assigned()); + assert(result.kind == REGISTER); + return result.reg_id - 16; +} diff --git a/old_compiler/cpp/codegen/x86_Emitter.h b/old_compiler/cpp/codegen/x86_Emitter.h new file mode 100644 index 0000000000..fe400af138 --- /dev/null +++ b/old_compiler/cpp/codegen/x86_Emitter.h @@ -0,0 +1,179 @@ +#ifndef JAK_X86_EMITTER_H +#define JAK_X86_EMITTER_H + +#include +#include +#include +#include +#include +#include +#include +#include "goal/IR.h" +#include "shared_config.h" +#include "Instruction.h" +#include "CodegenOutput.h" + +// Types of jumps to patch +enum StaticJumpType { SIGNED_32_RIP, INVALID_JUMP_TYPE }; + +// Record for a jump to be patched. +struct StaticJumpRecord { + int instr_idx = -1; + int target_ir_idx = -1; + int offset_into_instr = 0; + int function_offset = -1; + StaticJumpType type = INVALID_JUMP_TYPE; + bool resolved = false; +}; + +/*! + * Record for generating link data for a memory access in the symbol table + */ +struct SymbolMemAccessRec { + int offset; + int instr_idx; + + // offset into the segment's code where the patch should be made + int total_offset = INT32_MAX; + + // which segment the patch should be made + int seg = -1; +}; + +/*! + * Record for generating link to get the address of a static. + */ +struct StaticVarAddrRecord { + // the object (which should know where it is eventually) + std::shared_ptr place; + + // which instruction needs the patch + int instr_idx; + + // how far into the instruction is the patch location + int offset_into_instr; + + // what will rbp be at the instruction (relative to start of segment's code) + int current_rbp_instr_idx; + bool resolved = false; + + int size = -1; +}; + +/*! + * Record for generating link to get the address of a function. + * Very similar to StaticVarAddrRecord, but LambdaPlace isn't technically a StaticPlace + * so it needs its own thing (this might be a sign that LambdaPlace should be a StaticPlace...) + */ +struct FuncAddrRecord { + std::shared_ptr place; + int instr_idx; + int offset_into_instr; + int current_rbp_instr_idx; + bool resolve = false; +}; + +struct Instruction; + +class x86_Emitter { + public: + x86_Emitter() {} + int run(FunctionEnv* func, int target_segment); + void run(StaticObject* obj, int target_segment); + CodegenOutput write(); + + private: + // linking + void emit_link_table_header(CodegenOutput& out); + void emit_link_table_data(CodegenOutput& out, std::vector& instruction_offsets, int seg); + void emit_link_table_symbol_mem_recs(CodegenOutput& out, int seg); + void emit_link_table_type_ptrs(CodegenOutput& out, int seg); + void emit_link_table_func_type_ptr(CodegenOutput& out, int seg); + void emit_link_table_var_addr(CodegenOutput& out, std::vector& instruction_offsets, int seg); + void emit_link_table_func_addr(CodegenOutput& out, + std::vector& instruction_offsets, + int seg); + + // utilities + void emit_prologue(); + void emit_epilogue(); + void emit_static_objects(CodegenOutput& out, int segment); + void patch_jumps_and_recs(CodegenOutput& out, std::vector& offsets, int seg); + + void do_constvar(IR_LoadInteger& cv); + void do_return(IR_Return& ret); + void do_set(IR_Set& set); + void do_goto_label(IR_Goto_Label& go_to); + void do_set_symbol(IR_SetSymbolValue& set_symbol); + void do_get_symbol(IR_GetSymbolValue& get_symbol); + void do_get_symbol_object(IR_GetSymbolObj& get_sym); + void do_function_call(IR_FunctionCall& fcall); + void do_static_var_addr(IR_StaticVarAddr& var_addr); + void do_static_var_32(IR_StaticVar32& var_addr); + void do_function_addr(IR_FunctionAddr& func_addr); + void do_integer_math(IR_IntegerMath& math); + void do_cond_branch(IR_ConditionalBranch& br); + void do_cmp_branch(IR_ConditionalBranch& br, Instruction jump_instr); + void do_float_math(IR_FloatMath& fl); + void do_load_const_offset(IR_LoadConstOffset& load); + void do_store_const_offset(IR_StoreConstOffset& store); + void do_get_ra_ptr(IR_GetReturnAddressPointer& get_ra); + void do_asm(IR_Asm& asm_op); + + void do_float_to_int(IR_FloatToInt& f2i); + void do_int_to_float(IR_IntToFloat& i2f); + + void load_u64_to_gpr(uint64_t value, std::shared_ptr var); + + void emit_mov_gpr64_gpr64_or_null(uint8_t dst, uint8_t src); + void emit_mov_gpr64_gpr64_or_null(std::shared_ptr dst, std::shared_ptr src); + void emit_mov_xmm32_xmm32_or_null(std::shared_ptr dst, std::shared_ptr src); + + void mov(ColoringAssignment dst, ColoringAssignment src); + void push(ColoringAssignment src); + void pop(ColoringAssignment dst); + + void link_function_type_ptr(int segment, int offset); + + void emit_instr(Instruction i) { + instructions[current_seg].push_back(i); + instructions[current_seg].back().ir_index = ir_idx; + } + + uint8_t gpr_id(Place& var); + uint8_t xmm_id(Place& var); + ColoringAssignment get_ca(Place& var); + + // the current function being emitted + FunctionEnv* f = nullptr; + + // the instructions per segment. Note that one IR may expand into 0, 1, or multiple instructions + std::array, N_SEG> instructions; + + std::array, N_SEG> static_jumps; + std::array, N_SEG> static_objects; + std::array, N_SEG> static_var_addr_recs; + std::array, N_SEG> func_addr_recs; + int current_seg = -1; + + // map of symbol name -> list of mem access recs for each segment + std::array>, N_SEG> + symbol_mem_access_recs; + + // type pointers in statics: map of type name -> list of offsets + std::array>, N_SEG> + type_ptr_recs_in_statics; + + // per-segment, holds the offset into that segment's code where a function type pointer should go + std::array, N_SEG> function_type_ptr_recs; + + // emitter state + int ir_idx = -1; + int current_rbp_instr_idx = -1; + int function_offset = -1; + int additional_stack_offset = -1; + int stack_offset = -1; + bool extra_push_sr0 = false; +}; + +#endif // JAK_X86_EMITTER_H diff --git a/old_compiler/cpp/codegen/x86_Emitter_ConvertIR.cpp b/old_compiler/cpp/codegen/x86_Emitter_ConvertIR.cpp new file mode 100644 index 0000000000..6bb37abccf --- /dev/null +++ b/old_compiler/cpp/codegen/x86_Emitter_ConvertIR.cpp @@ -0,0 +1,542 @@ +/*! + * @file x86_Emitter_Code.cpp + * Emitter for converting IR and static objects into GOAL object files for x86 - Code Generation + * from IR + */ + +#include "x86_Emitter.h" +#include "IGen.h" + +//;;;;;;;;;;;;;;;;;;;;;;;;;;;; +// IR TRANSLATION +//;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +/*! + * Move a constant into a register + */ +void x86_Emitter::do_constvar(IR_LoadInteger& cv) { + auto gpr = gpr_id(*cv.value); + + // todo zero + + if (cv.s_value == 0) { + emit_instr(IGen::xor_zero_gpr(gpr)); + } else if (cv.s_value > 0) { + if (cv.us_value < UINT32_MAX) { + emit_instr(IGen::mov_gpr64_u32(gpr, cv.us_value)); + } else { + // need a real 64 bit load + emit_instr(IGen::mov_gpr64_u64(gpr, cv.us_value)); + } + } else { + if (cv.s_value >= INT32_MIN) { + emit_instr(IGen::mov_gpr64_s32(gpr, cv.s_value)); + } else { + // need a real 64 bit load + emit_instr(IGen::mov_gpr64_u64(gpr, cv.us_value)); + } + } +} + +/*! + * Return a variable + */ +void x86_Emitter::do_return(IR_Return& ret) { + // we need to insert a null instruction here so we can have a jump target to here, even if we + // don't emit any real instructions + emit_instr(IGen::null()); + + // if we aren't None, we should actually return something + if (!std::dynamic_pointer_cast(ret.value)) { + emit_mov_gpr64_gpr64_or_null(ret.dest, ret.value); + } +} + +/*! + * Set one reg equal to another. Can handle XMM/GPR sets. + * Currently all XMM sets are treated as floats. + */ +void x86_Emitter::do_set(IR_Set& set) { + auto dreg = get_ca(*set.dest).reg_id; + auto sreg = get_ca(*set.src).reg_id; + + if (dreg < 16 && sreg < 16) { + emit_mov_gpr64_gpr64_or_null(set.dest, set.src); + } else if (dreg >= 16 && sreg >= 16) { + // emit_instr(IGen::mov_xmm32_xmm32(xmm_id(*set.dest), xmm_id(*set.src))); + emit_mov_xmm32_xmm32_or_null(set.dest, set.src); + } else if (dreg < 16 && sreg >= 16) { + emit_instr(IGen::movd_gpr32_xmm32(gpr_id(*set.dest), xmm_id(*set.src))); + } else if (dreg >= 16 && sreg < 16) { + emit_instr(IGen::movd_xmm32_gpr32(xmm_id(*set.dest), gpr_id(*set.src))); + } else { + throw std::runtime_error("invalid set - mixed operands"); + } +} + +/*! + * A jump to a label. Can be forward or backward. + */ +void x86_Emitter::do_goto_label(IR_Goto_Label& go_to) { + assert(go_to.resolved); // make sure the label is actually valid... + emit_instr(IGen::jmp_32()); + + // create a record. + StaticJumpRecord rec; + rec.resolved = false; + rec.instr_idx = instructions[current_seg].size() - 1; + rec.offset_into_instr = 1; + rec.type = SIGNED_32_RIP; + rec.target_ir_idx = go_to.label->idx; + rec.function_offset = function_offset; + static_jumps[current_seg].push_back(rec); +} + +/*! + * The various kinds of conditional branches + */ +void x86_Emitter::do_cond_branch(IR_ConditionalBranch& br) { + switch (br.cond.kind) { + case EQUAL_64: + do_cmp_branch(br, IGen::je_32()); + break; + case NOT_EQUAL_64: + do_cmp_branch(br, IGen::jne_32()); + break; + case LEQ_64: + if (br.cond.is_signed) { + do_cmp_branch(br, IGen::jle_32()); + } else { + do_cmp_branch(br, IGen::jbe_32()); + } + break; + case GEQ_64: + if (br.cond.is_signed) { + do_cmp_branch(br, IGen::jge_32()); + } else { + do_cmp_branch(br, IGen::jae_32()); + } + break; + case LT_64: + if (br.cond.is_signed) { + do_cmp_branch(br, IGen::jl_32()); + } else { + do_cmp_branch(br, IGen::jb_32()); + } + break; + case GT_64: + if (br.cond.is_signed) { + do_cmp_branch(br, IGen::jg_32()); + } else { + do_cmp_branch(br, IGen::ja_32()); + } + break; + default: + throw std::runtime_error("unknown branch type in do_cond_branch"); + } +} + +/*! + * Set the value of a symbol + */ +void x86_Emitter::do_set_symbol(IR_SetSymbolValue& set_symbol) { + auto dst_as_sym = std::dynamic_pointer_cast(set_symbol.dest); + assert(dst_as_sym); + emit_instr(IGen::store32_r64off32s_gpr32(ST_REG, 0x0badbeef, gpr_id(*set_symbol.value))); + SymbolMemAccessRec rec; + rec.offset = instructions[current_seg].back().offset_of_disp(); + rec.instr_idx = instructions[current_seg].size() - 1; + rec.seg = current_seg; + symbol_mem_access_recs[current_seg][dst_as_sym->name].push_back(rec); +} + +/*! + * Get the value of a symbol + */ +void x86_Emitter::do_get_symbol(IR_GetSymbolValue& get_symbol) { + emit_instr(IGen::load32_gpr32sz_r64off32s(gpr_id(*get_symbol.dest), 0xbad0beef, ST_REG, + get_symbol.sext)); + SymbolMemAccessRec rec; + rec.offset = instructions[current_seg].back().offset_of_disp(); + rec.instr_idx = instructions[current_seg].size() - 1; + rec.seg = current_seg; + symbol_mem_access_recs[current_seg][get_symbol.symbol->name].push_back(rec); +} + +/*! + * Call a function + */ +void x86_Emitter::do_function_call(IR_FunctionCall& fcall) { + // currently the function call pointer has the GOAL offset address. + // we need to change this to actually do a function call. + + emit_mov_gpr64_gpr64_or_null(fcall.func_call, fcall.func_in); + + // make sure the function address reg is correct + auto freg = get_ca(*fcall.func_call); + assert(freg.kind == REGISTER); + assert(freg.reg_id == T9_REG); + + // do the add + emit_instr(IGen::add_gpr64_gpr64(freg.reg_id, OFF_REG)); + + // now do the call + emit_instr(IGen::call_r64(freg.reg_id)); +} + +/*! + * Get the address of a static variable + * TODO - can the size of this be decreased? + */ +void x86_Emitter::do_static_var_addr(IR_StaticVarAddr& var_addr) { + // get the offset: + StaticVarAddrRecord rec; + load_u64_to_gpr(0xbadcafebadcafe, var_addr.dest); + rec.instr_idx = instructions[current_seg].size() - 1; + rec.offset_into_instr = instructions[current_seg].back().offset_of_imm(); + rec.resolved = false; + rec.place = std::dynamic_pointer_cast(var_addr.src); + rec.current_rbp_instr_idx = current_rbp_instr_idx; + rec.size = 8; + assert(rec.place); + static_var_addr_recs[current_seg].push_back(rec); + + // and add the base + emit_instr(IGen::add_gpr64_gpr64(gpr_id(*var_addr.dest), RBP)); + emit_instr(IGen::sub_gpr64_gpr64(gpr_id(*var_addr.dest), OFF_REG)); +} + +/*! + * Load a static variable (32 bits) + * TODO - generalize this (and maybe the IR) for other sizes + */ +void x86_Emitter::do_static_var_32(IR_StaticVar32& var_addr) { + auto ca = get_ca(*var_addr.dest); + if (ca.reg_id >= 16) { + emit_instr(IGen::load32_xmm32_r64off32s(xmm_id(*var_addr.dest), RBP, 0xcafecafe)); + StaticVarAddrRecord rec; + rec.instr_idx = instructions[current_seg].size() - 1; + rec.offset_into_instr = instructions[current_seg].back().offset_of_disp(); + rec.resolved = false; + rec.place = std::dynamic_pointer_cast(var_addr.src); + rec.current_rbp_instr_idx = current_rbp_instr_idx; + rec.size = 4; + assert(rec.place); + static_var_addr_recs[current_seg].push_back(rec); + } else { + emit_instr(IGen::load32_gpr32sz_r64off32s(gpr_id(*var_addr.dest), 0xcafecafe, RBP, + var_addr.src->type.type->load_signed)); + StaticVarAddrRecord rec; + rec.instr_idx = instructions[current_seg].size() - 1; + rec.offset_into_instr = instructions[current_seg].back().offset_of_disp(); + rec.resolved = false; + rec.place = std::dynamic_pointer_cast(var_addr.src); + rec.current_rbp_instr_idx = current_rbp_instr_idx; + rec.size = 4; + assert(rec.place); + static_var_addr_recs[current_seg].push_back(rec); + } +} + +/*! + * Get the address of a function + */ +void x86_Emitter::do_function_addr(IR_FunctionAddr& func_addr) { + FuncAddrRecord rec; + load_u64_to_gpr(0xcafebadcafebad, func_addr.dest); + rec.instr_idx = instructions[current_seg].size() - 1; + rec.offset_into_instr = instructions[current_seg].back().offset_of_imm(); + rec.place = std::dynamic_pointer_cast(func_addr.src); + rec.resolve = false; + rec.current_rbp_instr_idx = current_rbp_instr_idx; + assert(rec.place); + func_addr_recs[current_seg].push_back(rec); + emit_instr(IGen::add_gpr64_gpr64(gpr_id(*func_addr.dest), RBP)); + emit_instr(IGen::sub_gpr64_gpr64(gpr_id(*func_addr.dest), OFF_REG)); +} + +/*! + * Do math on integers + */ +void x86_Emitter::do_integer_math(IR_IntegerMath& math) { + switch (math.math_kind) { + case ADD_64: + emit_instr(IGen::add_gpr64_gpr64(gpr_id(*math.d), gpr_id(*math.a0))); + break; + + case SUB_64: + emit_instr(IGen::sub_gpr64_gpr64(gpr_id(*math.d), gpr_id(*math.a0))); + break; + + case IMUL_32: + emit_instr(IGen::imul_gpr32_gpr32(gpr_id(*math.d), gpr_id(*math.a0))); + emit_instr(IGen::movsx_r64_r32(gpr_id(*math.d), gpr_id(*math.d))); + break; + + case IDIV_32: + emit_instr(IGen::cdq()); + emit_instr(IGen::idiv_gpr32(gpr_id(*math.a0))); + emit_instr(IGen::movsx_r64_r32(gpr_id(*math.d), RAX)); + break; + + case IMOD_32: + emit_instr(IGen::cdq()); + emit_instr(IGen::idiv_gpr32(gpr_id(*math.a0))); + emit_instr(IGen::movsx_r64_r32(gpr_id(*math.d), RDX)); + break; + + case SHLV_64: + emit_instr(IGen::shl_gpr64_cl(gpr_id(*math.d))); + break; + case SHRV_64: + emit_instr(IGen::shr_gpr64_cl(gpr_id(*math.d))); + break; + case SARV_64: + emit_instr(IGen::sar_gpr64_cl(gpr_id(*math.d))); + break; + + case SHL_64: + emit_instr(IGen::shl_gpr64_u8(gpr_id(*math.d), math.sa)); + break; + case SHR_64: + emit_instr(IGen::shr_gpr64_u8(gpr_id(*math.d), math.sa)); + break; + case SAR_64: + emit_instr(IGen::sar_gpr64_u8(gpr_id(*math.d), math.sa)); + break; + + case OR_64: + emit_instr(IGen::or_gpr64_gpr64(gpr_id(*math.d), gpr_id(*math.a0))); + break; + case AND_64: + emit_instr(IGen::and_gpr64_gpr64(gpr_id(*math.d), gpr_id(*math.a0))); + break; + case XOR_64: + emit_instr(IGen::xor_gpr64_gpr64(gpr_id(*math.d), gpr_id(*math.a0))); + break; + case NOT_64: + emit_instr(IGen::not_gpr64(gpr_id(*math.d))); + break; + + default: + throw std::runtime_error("unknown integer math kind in emitter"); + } +} + +/*! + * Do Math on Floats + */ +void x86_Emitter::do_float_math(IR_FloatMath& fl) { + switch (fl.math_kind) { + case MUL_SS: + emit_instr(IGen::mulss_xmm_xmm(xmm_id(*fl.d), xmm_id(*fl.a0))); + break; + case DIV_SS: + emit_instr(IGen::divss_xmm_xmm(xmm_id(*fl.d), xmm_id(*fl.a0))); + break; + case SUB_SS: + emit_instr(IGen::subss_xmm_xmm(xmm_id(*fl.d), xmm_id(*fl.a0))); + break; + case ADD_SS: + emit_instr(IGen::addss_xmm_xmm(xmm_id(*fl.d), xmm_id(*fl.a0))); + break; + default: + throw std::runtime_error("unknown float math kind in emitter"); + } +} + +void x86_Emitter::do_int_to_float(IR_IntToFloat& i2f) { + emit_instr(IGen::int32_to_float(xmm_id(*i2f.dest), gpr_id(*i2f.src))); +} + +void x86_Emitter::do_float_to_int(IR_FloatToInt& f2i) { + emit_instr(IGen::float_to_int64(gpr_id(*f2i.dest), xmm_id(*f2i.src))); +} + +/*! + * Get a pointer to a symbol. + */ +void x86_Emitter::do_get_symbol_object(IR_GetSymbolObj& get_sym) { + auto dest_id = get_ca(*get_sym.dest).reg_id; + emit_instr(IGen::mov_gpr64_gpr64(dest_id, ST_REG)); // s7 + emit_instr(IGen::add_gpr64_imm32s(dest_id, 0xcafecafe)); // + offset + SymbolMemAccessRec rec; + rec.offset = instructions[current_seg].back().offset_of_imm(); + rec.instr_idx = instructions[current_seg].size() - 1; + rec.seg = current_seg; + symbol_mem_access_recs[current_seg][get_sym.sym->name].push_back(rec); + emit_instr(IGen::sub_gpr64_gpr64(dest_id, OFF_REG)); +} + +/*! + * Load from memory! + */ +void x86_Emitter::do_load_const_offset(IR_LoadConstOffset& load) { + auto dst_reg = gpr_id(*load.dst); + auto src_reg = gpr_id(*load.src); + emit_instr(IGen::mov_gpr64_gpr64(dst_reg, src_reg)); + emit_instr(IGen::add_gpr64_gpr64(dst_reg, OFF_REG)); + if (load.size == 8) { + emit_instr(IGen::load64_gpr64_r64off32s(dst_reg, load.offset, dst_reg)); + } else if (load.size == 4) { + emit_instr(IGen::load32_gpr32sz_r64off32s(dst_reg, load.offset, dst_reg, load.is_signed)); + } else if (load.size == 2 && !load.is_signed) { + emit_instr(IGen::load16_gpr16z_r64off32s(dst_reg, dst_reg, load.offset)); + } else if (load.size == 2 && load.is_signed) { + emit_instr(IGen::load16_gpr16s_r64off32s(dst_reg, dst_reg, load.offset)); + } else if (load.size == 1 && !load.is_signed) { + emit_instr(IGen::load16_gpr8z_r64off32s(dst_reg, dst_reg, load.offset)); + } else { + throw std::runtime_error("unsupported load size in do_load_const_offset " + load.print()); + } +} + +/*! + * Store into memory! + */ +void x86_Emitter::do_store_const_offset(IR_StoreConstOffset& store) { + // this sucks + auto mem_reg = gpr_id(*store.mem); + auto val_reg = gpr_id(*store.val); + + if (mem_reg == val_reg) { + assert(mem_reg != BP_REG); + assert(val_reg != BP_REG); + emit_instr(IGen::push_gpr64(BP_REG)); + emit_instr(IGen::mov_gpr64_gpr64(BP_REG, OFF_REG)); + emit_instr(IGen::add_gpr64_gpr64(BP_REG, mem_reg)); + if (store.size == 8) { + emit_instr(IGen::store64_r64off32s_gpr64(BP_REG, store.offset, val_reg)); + } else if (store.size == 4) { + emit_instr(IGen::store32_r64off32s_gpr32(BP_REG, store.offset, val_reg)); + } else if (store.size == 2) { + emit_instr(IGen::store16_r64off32s_gpr16(BP_REG, store.offset, val_reg)); + } else if (store.size == 1) { + emit_instr(IGen::store8_r64off32s_gpr8(BP_REG, store.offset, val_reg)); + } else { + throw std::runtime_error("unsupported load size in do_store_const_offset"); + } + emit_instr(IGen::pop_gpr64(BP_REG)); + } else { + emit_instr(IGen::add_gpr64_gpr64(mem_reg, OFF_REG)); + if (store.size == 8) { + emit_instr(IGen::store64_r64off32s_gpr64(mem_reg, store.offset, val_reg)); + } else if (store.size == 4) { + emit_instr(IGen::store32_r64off32s_gpr32(mem_reg, store.offset, val_reg)); + } else if (store.size == 2) { + emit_instr(IGen::store16_r64off32s_gpr16(mem_reg, store.offset, val_reg)); + } else if (store.size == 1) { + emit_instr(IGen::store8_r64off32s_gpr8(mem_reg, store.offset, val_reg)); + } else { + throw std::runtime_error("unsupported load size in do_store_const_offset"); + } + emit_instr(IGen::sub_gpr64_gpr64(mem_reg, OFF_REG)); + } +} + +void x86_Emitter::do_get_ra_ptr(IR_GetReturnAddressPointer& get_ra) { + auto dst_reg = gpr_id(*get_ra.dest); + uint64_t offset = (int64_t)(stack_offset + 0); + emit_instr(IGen::mov_gpr64_u64(dst_reg, offset)); + emit_instr(IGen::add_gpr64_gpr64(dst_reg, RSP)); + emit_instr(IGen::sub_gpr64_gpr64(dst_reg, OFF_REG)); +} +//;;;;;;;;;;;;;;;;;;;;;;;;;;;; +// CODEGEN UTILITIES +//;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +/*! + * Load a uint64_t into a gpr. + */ +void x86_Emitter::load_u64_to_gpr(uint64_t value, std::shared_ptr var) { + auto as_reg = std::dynamic_pointer_cast(var); + if (as_reg) { + emit_instr(IGen::mov_gpr64_u64(gpr_id(*var), value)); + } else { + throw std::runtime_error("don't know how to load constant to this variable"); + } +} + +/*! + * Emit a move between two gpr64's if the two given gpr's aren't the same. + * If a move isn't emitted, a null instruction is emitted instead. + */ +void x86_Emitter::emit_mov_gpr64_gpr64_or_null(uint8_t dst, uint8_t src) { + if (dst == src) { + emit_instr(IGen::null()); + } else { + emit_instr(IGen::mov_gpr64_gpr64(dst, src)); + } +} + +/*! + * Emit a move between two gpr64's if the two given gpr's aren't the same. + * If a move isn't emitted, a null instruction is emitted instead. + */ +void x86_Emitter::emit_mov_gpr64_gpr64_or_null(std::shared_ptr dst, + std::shared_ptr src) { + emit_mov_gpr64_gpr64_or_null(gpr_id(*dst), gpr_id(*src)); +} + +void x86_Emitter::emit_mov_xmm32_xmm32_or_null(std::shared_ptr dst, + std::shared_ptr src) { + auto dst_id = xmm_id(*dst); + auto src_id = xmm_id(*src); + if (dst_id == src_id) { + emit_instr(IGen::null()); + } else { + emit_instr(IGen::mov_xmm32_xmm32(dst_id, src_id)); + } +} + +/*! + * Given the jump instruction, create the cmp/jmp sequence. + */ +void x86_Emitter::do_cmp_branch(IR_ConditionalBranch& br, Instruction jump_instr) { + assert(br.resolved); + if (br.cond.is_float) { + emit_instr(IGen::cmp_flt_flt(xmm_id(*br.cond.a), xmm_id(*br.cond.b))); + } else { + emit_instr(IGen::cmp_gpr64_gpr64(gpr_id(*br.cond.a), gpr_id(*br.cond.b))); + } + + emit_instr(jump_instr); + StaticJumpRecord rec; + rec.resolved = false; + rec.instr_idx = instructions[current_seg].size() - 1; + rec.offset_into_instr = instructions[current_seg].back().offset_of_imm(); + rec.type = SIGNED_32_RIP; + rec.target_ir_idx = br.label->idx; + rec.function_offset = function_offset; + static_jumps[current_seg].push_back(rec); +} + +void x86_Emitter::do_asm(IR_Asm& asm_op) { + switch (asm_op.asm_kind) { + case IR_Asm::RET: + assert(asm_op.args.empty()); + emit_instr(IGen::ret()); + break; + case IR_Asm::RET_REGISTER: + emit_instr(IGen::ret()); + break; + case IR_Asm::PUSH: + assert(asm_op.args.size() == 1); + emit_instr(IGen::push_gpr64(gpr_id(*asm_op.args.at(0)))); + break; + case IR_Asm::POP: + assert(asm_op.args.size() == 1); + emit_instr(IGen::pop_gpr64(gpr_id(*asm_op.args.at(0)))); + break; + case IR_Asm::JMP: + assert(asm_op.args.size() == 1); + emit_instr(IGen::jmp_r64(gpr_id(*asm_op.args.at(0)))); + break; + case IR_Asm::SUB: + assert(asm_op.args.size() == 2); + emit_instr(IGen::sub_gpr64_gpr64(gpr_id(*asm_op.args.at(0)), gpr_id(*asm_op.args.at(2)))); + break; + default: + throw std::runtime_error("unknown asm op in emitter"); + } +} \ No newline at end of file diff --git a/old_compiler/cpp/codegen/x86_Emitter_LinkData.cpp b/old_compiler/cpp/codegen/x86_Emitter_LinkData.cpp new file mode 100644 index 0000000000..70bafce393 --- /dev/null +++ b/old_compiler/cpp/codegen/x86_Emitter_LinkData.cpp @@ -0,0 +1,262 @@ +/*! + * @file x86_Emitter_LinkData.cpp + * Emitter for converting IR and static objects into GOAL object files for x86 - link table + * generation + */ + +#include "x86_Emitter.h" +#include "codegen_utils.h" +#include "goal/GoalEnv.h" + +/*! + * Add symbol mem access records to link table for the given segment + */ +void x86_Emitter::emit_link_table_symbol_mem_recs(CodegenOutput& out, int seg) { + // these are + // id (1 byte) + // name (n bytes) + // name-null-terminator (1 byte) + // count of links (4 bytes) + // offset into segment's code of where to patch (4 bytes * count) + for (auto& symbol_mem_rec : symbol_mem_access_recs[seg]) { + out.link_tables[seg].push_back(LINK_SYMBOL_OFFSET); + + // name + for (char c : symbol_mem_rec.first) { + out.link_tables[seg].push_back(c); + } + out.link_tables[seg].push_back(0); + + // links + push_data_to_byte_vector(symbol_mem_rec.second.size(), out.link_tables[seg]); + for (auto& r : symbol_mem_rec.second) { + assert(r.seg == seg); + push_data_to_byte_vector(r.total_offset, out.link_tables[seg]); + } + } + + for (auto& sym_rec : type_ptr_recs_in_statics[seg]) { + uint32_t sym_rec_count = 0; + for (auto& rec : sym_rec.second) { + if (rec.kind == StaticLinkRecord::SYMBOL_PTR) { + sym_rec_count++; + } + } + + if (!sym_rec_count) + continue; + + out.link_tables[seg].push_back(LINK_SYMBOL_OFFSET); + + // name + for (char c : sym_rec.first) { + out.link_tables[seg].push_back(c); + } + out.link_tables[seg].push_back(0); + + // links + push_data_to_byte_vector(sym_rec_count, out.link_tables[seg]); + for (auto& r : sym_rec.second) { + // assert(r.seg == seg); + if (r.kind == StaticLinkRecord::Kind::SYMBOL_PTR) { + push_data_to_byte_vector(r.offset, out.link_tables[seg]); + } + } + } +} + +/*! + * Add type pointer records for static data to link table for the given segment. + */ +void x86_Emitter::emit_link_table_type_ptrs(CodegenOutput& out, int seg) { + // ID, Name (null terminated), method count (u8), count of links, link table + for (auto& symbol_ptr_rec : type_ptr_recs_in_statics.at(seg)) { + uint32_t type_rec_count = 0; + for (auto& rec : symbol_ptr_rec.second) { + if (rec.kind == StaticLinkRecord::TYPE_PTR) { + type_rec_count++; + } + } + + if (!type_rec_count) + continue; + + // id + out.link_tables[seg].push_back(LINK_TYPE_PTR); + + // name + for (char c : symbol_ptr_rec.first) { + out.link_tables[seg].push_back(c); + } + out.link_tables[seg].push_back(0); + + // method count + out.link_tables[seg].push_back(0); // todo! + + // count of links + push_data_to_byte_vector(type_rec_count, out.link_tables[seg]); + + // link table + for (auto& r : symbol_ptr_rec.second) { + if (r.kind == StaticLinkRecord::Kind::TYPE_PTR) { + push_data_to_byte_vector(r.offset, out.link_tables[seg]); + } + } + } +} + +/*! + * Add type pointer records for functions to link table for the given segment. + */ +void x86_Emitter::emit_link_table_func_type_ptr(CodegenOutput& out, int seg) { + // id + out.link_tables[seg].push_back(LINK_TYPE_PTR); + std::string name = "function"; + + // name + for (char c : name) { + out.link_tables[seg].push_back(c); + } + out.link_tables[seg].push_back(0); + + // method count + out.link_tables[seg].push_back(0); // todo! + + // count of links + push_data_to_byte_vector(function_type_ptr_recs[seg].size(), out.link_tables[seg]); + + // link table + for (auto& r : function_type_ptr_recs[seg]) { + push_data_to_byte_vector(r, out.link_tables[seg]); + } +} + +/*! + * Add variable address records to the link table for the given segment + */ +void x86_Emitter::emit_link_table_var_addr(CodegenOutput& out, + std::vector& instruction_offsets, + int seg) { + // link pointers to variables. + // currently this is for both variables in your segment and in other segments + // and it is not super efficient space wise + for (auto& rec : static_var_addr_recs.at(seg)) { + // ID, target_seg, offset_into_this_seg, offset_into_target_seg, patch location + + LinkKind kind; + switch (rec.size) { + case 4: + kind = LINK_DISTANCE_TO_OTHER_SEG_32; + break; + case 8: + kind = LINK_DISTANCE_TO_OTHER_SEG_64; + break; + default: + throw std::runtime_error("unknown size in static_var_addr_recs link target!"); + } + out.link_tables[seg].push_back(kind); + + uint8_t target_segment = rec.place->object->segment; + out.link_tables[seg].push_back(target_segment); + + uint32_t offset_into_current_seg = instruction_offsets.at(rec.current_rbp_instr_idx); + push_data_to_byte_vector(offset_into_current_seg, out.link_tables[seg]); + + // statics only know their location relative to the start of statics + uint32_t offset_into_target_seg = + out.static_start.at(target_segment) + rec.place->object->offset; + push_data_to_byte_vector(offset_into_target_seg, out.link_tables[seg]); + + uint32_t patch_location = instruction_offsets.at(rec.instr_idx) + rec.offset_into_instr; + push_data_to_byte_vector(patch_location, out.link_tables[seg]); + } +} + +/*! + * Add function address records to the link table for the given segment + */ +void x86_Emitter::emit_link_table_func_addr(CodegenOutput& out, + std::vector& instruction_offsets, + int seg) { + for (auto& rec : func_addr_recs.at(seg)) { + out.link_tables[seg].push_back(LINK_DISTANCE_TO_OTHER_SEG_64); + + uint8_t target_segment = rec.place->func->segment; + out.link_tables[seg].push_back(target_segment); + + uint32_t offset_into_current_seg = instruction_offsets.at(rec.current_rbp_instr_idx); + push_data_to_byte_vector(offset_into_current_seg, out.link_tables[seg]); + + // functions know what their first instruction index is + uint32_t offset_into_target_seg = + out.instr_offsets[target_segment].at(rec.place->func->first_instruction); + push_data_to_byte_vector(offset_into_target_seg, out.link_tables[seg]); + + uint32_t patch_location = instruction_offsets.at(rec.instr_idx) + rec.offset_into_instr; + push_data_to_byte_vector(patch_location, out.link_tables[seg]); + } +} + +/*! + * Generate the link table data for a segment. + */ +void x86_Emitter::emit_link_table_data(CodegenOutput& out, + std::vector& instruction_offsets, + int seg) { + emit_link_table_symbol_mem_recs(out, seg); + emit_link_table_type_ptrs(out, seg); + emit_link_table_func_type_ptr(out, seg); + emit_link_table_var_addr(out, instruction_offsets, seg); + emit_link_table_func_addr(out, instruction_offsets, seg); + out.link_tables[seg].push_back(LINK_TABLE_END); +} + +/*! + * Generate the header data for the link data. + * This must run after all code and link table stuff is done + */ +void x86_Emitter::emit_link_table_header(CodegenOutput& out) { + // fake type tag + out.header.push_back('G'); + out.header.push_back('O'); + out.header.push_back('A'); + out.header.push_back('L'); + + uint32_t offset = 0; + offset += push_data_to_byte_vector(GOAL_VERSION_MAJOR, out.header); + offset += push_data_to_byte_vector(GOAL_VERSION_MINOR, out.header); + offset += push_data_to_byte_vector(3, out.header); + offset += push_data_to_byte_vector(N_SEG, out.header); + + offset += sizeof(uint32_t) * N_SEG * 4; + offset += 4; + int total_link_size = 0; + + struct SizeOffset { + uint32_t offset, size; + }; + + struct SizeOffsetTable { + SizeOffset link_seg[N_SEG]; + SizeOffset code_seg[N_SEG]; + }; + + SizeOffsetTable table; + + for (int i = N_SEG; i-- > 0;) { + table.link_seg[i].offset = offset; + table.link_seg[i].size = out.link_tables[i].size(); + offset += out.link_tables[i].size(); + total_link_size += out.link_tables[i].size(); + } + + offset = 0; + for (int i = N_SEG; i-- > 0;) { + table.code_seg[i].offset = offset; + table.code_seg[i].size = out.code[i].size(); + offset += out.code[i].size(); + } + + push_data_to_byte_vector(table, out.header); + push_data_to_byte_vector(64 + 4 + total_link_size, out.header); +} \ No newline at end of file diff --git a/old_compiler/cpp/goal/CMakeLists.txt b/old_compiler/cpp/goal/CMakeLists.txt new file mode 100644 index 0000000000..1c139c5bd5 --- /dev/null +++ b/old_compiler/cpp/goal/CMakeLists.txt @@ -0,0 +1,34 @@ +add_library(goal SHARED + Goal.cpp + GoalFunctionForms.cpp + GoalConditionalCompilation.cpp + GoalBlockForms.cpp + GoalCompilerControl.cpp + GoalType.cpp + GoalEnv.cpp + GoalListener.cpp + IR.cpp + GoalPlace.cpp + GoalLambda.cpp + GoalDefineForms.cpp + StaticObject.cpp + GoalIntegerMath.cpp + GoalMacroForms.cpp + GoalControlFlow.cpp + GoalDefType.cpp + GoalMethod.cpp + GoalInspector.cpp + GoalFieldAccess.cpp + GoalTypeUtil.cpp + GoalObject.cpp + GoalCompileAtoms.cpp + GoalConstProp.cpp + GoalUtil.cpp + GoalPair.cpp + GoalStaticObject.cpp + GoalNew.cpp + GoalAsm.cpp + GoalEnum.cpp + GoalBitfieldAccess.cpp) + +target_link_libraries(goal goos_old reader_old) \ No newline at end of file diff --git a/old_compiler/cpp/goal/DefaultConfig.h b/old_compiler/cpp/goal/DefaultConfig.h new file mode 100644 index 0000000000..2df7a966dd --- /dev/null +++ b/old_compiler/cpp/goal/DefaultConfig.h @@ -0,0 +1,18 @@ +/*! + * @file DefaultConfig.h + * The default configuration of the compiler. + * These configuration settings are used when the compiler is first loading. + * As goal-lib is loaded, it will override these values. + */ + +#ifndef JAK_V2_DEFAULTCONFIG_H +#define JAK_V2_DEFAULTCONFIG_H + +#include +#include + +static std::pair default_config[] = {{"debug-print-ir", "#f"}, + {"debug-print-obj", "#f"}, + {"print-asm-file-time", "#f"}}; + +#endif // JAK_V2_DEFAULTCONFIG_H diff --git a/old_compiler/cpp/goal/Goal.cpp b/old_compiler/cpp/goal/Goal.cpp new file mode 100644 index 0000000000..03e76c55d0 --- /dev/null +++ b/old_compiler/cpp/goal/Goal.cpp @@ -0,0 +1,233 @@ +/*! + * @file Goal.cpp + * The GOAL Compiler! + */ + +#include +#include +#include + +#include "Goal.h" +#include "codegen/Coloring.h" +#include "codegen/x86_Emitter.h" +#include "logger/Logger.h" +#include "util.h" + +/*! + * Initialize GOAL Compiler and load libraries + */ +Goal::Goal() { + init_logger(); + + // set up some type system stuff + types.fill_with_default_types(); + get_none()->type = get_base_typespec("none"); + + // the configuration is not loaded yet, so initialize configuration to the default: + setup_default_config(); + + // set up the global environment + global_env = std::make_shared(); + + // read the GOAL library + auto goal_lib = read_from_file("old_compiler/gc/goal-lib.gc"); + + // setup environments for compiling the library + auto library_env = std::make_shared("init-env", global_env); + auto library_f_env = std::make_shared("init-func", library_env); + + // compile the GOAL library, with no coloring or object file generation + compile_error_guard(goal_lib, library_f_env); +} + +/*! + * The REPL Loop. This is kept as simple as possible! + */ +void Goal::execute_repl() { + // read, evaluate, print loop! + while (!want_exit) { + try { + // get a line from the user + Object code = read_from_stdin_prompt(std::string("goal(") + + (listener.is_connected() ? "c" : "n") + ")"); + + // compile + auto repl_obj_file = compile_object_file("repl", code); + + // early exit if there's nothing to send + if (repl_obj_file->is_empty()) + continue; + + // color + color_object_file(repl_obj_file); + + // emit + auto data = codegen_object_file(repl_obj_file); + + // optionally print the function's IR for debugging. + if (truthy(get_config("debug-print-ir"))) { + for (auto& x : repl_obj_file->top_level_function->code) { + gLogger.log(MSG_WARN, "%s\n", x->print().c_str()); + } + } + + // send to target, if connected + if (listener.is_connected()) { + listener.send_code(data); + + // if the target died, receiver_got_ack will be false. + if (!listener.receiver_got_ack) { + gLogger.log(MSG_WARN, "Target did not respond!\n"); + } + } + + } catch (std::exception& e) { + printf("REPL Error: %s\n", e.what()); + } + } + + // repl has ended, disconnect from target. + listener.set_connected(false); +} + +/*! + * Shutdown GOAL + */ +Goal::~Goal() { + gLogger.close(); +} + +/*! + * Set logger settings. + */ +void Goal::init_logger() { + gLogger.set_file("compiler.txt"); + gLogger.config[MSG_COLOR].kind = LOG_FILE; + gLogger.config[MSG_DEBUG].kind = LOG_IGNORE; + gLogger.config[MSG_TGT].color = COLOR_GREEN; + gLogger.config[MSG_TGT_INFO].color = COLOR_BLUE; + gLogger.config[MSG_WARN].color = COLOR_RED; + gLogger.config[MSG_ICE].color = COLOR_RED; + gLogger.config[MSG_ERR].color = COLOR_RED; +} + +/*! + * Compile an object file from code. + */ +std::shared_ptr Goal::compile_object_file(const std::string& name, + const Object& code) { + auto obj_env = std::make_shared(name, global_env); + + // the top-level function is the function containing all statements in a file. + obj_env->add_top_level_function(compile_top_level_function("top-level", code, obj_env)); + + return obj_env; +} + +/*! + * Compile a function from code + */ +std::shared_ptr Goal::compile_top_level_function( + const std::string& name, + const Object& code, + std::shared_ptr object) { + auto func_env = std::make_shared(name, object); + func_env->segment = TOP_LEVEL_SEGMENT; + + // a temporary type, as we don't know the return type yet (but this is okay!) + auto return_reg = func_env->alloc_reg(get_base_typespec("none")); + + // compile, resolve to GPR, and return + auto return_ir = make_unique( + resolve_to_gpr(compile_error_guard(code, func_env), func_env), return_reg); + + // correct the type + return_reg->type = return_ir->value->type; + + // and emit the final return. + func_env->emit(std::move(return_ir)); + + // clean up any gotos (which might jump to the return statement, so we do it here) + func_env->finish(); + return func_env; +} + +/*! + * Create a valid coloring for a function, or throw an error. + */ +void Goal::color_function(const std::shared_ptr& func) { + if (!do_linear_scan_coloring(*func.get())) { + ice("Coloring failed for function " + func->print()); + } +} + +/*! + * Color all functions in an object file + */ +void Goal::color_object_file(const std::shared_ptr& obj) { + for (auto& f : obj->functions) { + color_function(f); + } +} + +/*! + * Convert an object file with colored functions into a code blob to be loaded. + */ +std::vector Goal::codegen_object_file(const std::shared_ptr& obj) { + x86_Emitter emitter; + + // give all static objects to the emitter + for (auto& static_obj : obj->statics) { + emitter.run(static_obj->object.get(), static_obj->object->segment); + } + + // give all functions to the emitter + for (auto& f : obj->functions) { + f->first_instruction = emitter.run(f.get(), f->segment); + } + + // run the emitter + auto output = emitter.write(); + + // debug print just the code sections + if (truthy(get_config("debug-print-obj"))) { + for (auto& s : output.code) { + gLogger.log(MSG_WARN, "---\n"); + for (auto x : s) { + gLogger.log(MSG_WARN, "%02x\n", x); + } + } + } + + // combine all sections + return output.to_vector(); +} + +/*! + * Compile with an error stack checkpoint. + */ +std::shared_ptr Goal::compile_error_guard(Object obj, std::shared_ptr env) { + try { + return compile(obj, env); + } catch (std::runtime_error& e) { + printf( + "------------------------------------------------------------------------------------------" + "-\n"); + auto obj_print = obj.print(); + if (obj_print.length() > 80) { + obj_print = obj_print.substr(0, 80); + obj_print += "..."; + } + printf("object: %s\nfrom : %s\n", obj_print.c_str(), goos.reader.db.get_info_for(obj).c_str()); + throw e; + } +} + +/*! + * Signal a compilation error. + */ +void Goal::throw_compile_error(Object o, const std::string& err) { + gLogger.log(MSG_ERR, "[Error] Could not compile %s!\nReason: %s\n", o.print().c_str(), + err.c_str()); + throw std::runtime_error(err); +} diff --git a/old_compiler/cpp/goal/Goal.h b/old_compiler/cpp/goal/Goal.h new file mode 100644 index 0000000000..489b815288 --- /dev/null +++ b/old_compiler/cpp/goal/Goal.h @@ -0,0 +1,520 @@ +#ifndef JAK_GOAL_H +#define JAK_GOAL_H + +#include +#include "listener/Listener.h" +#include "goos/Goos.h" +#include "GoalEnv.h" +#include "TypeContainer.h" +#include "GoalEnum.h" + +enum MathMode { MATH_INT, MATH_BINT, MATH_FLOAT, MATH_INVALID }; + +enum LogOpKind { LOGIOR, LOGAND, LOGXOR }; + +struct StructFieldDefinition { + enum PrintType { DECIMAL, HEX, DONT_PRINT, DEFAULT }; + + std::string name; //, type; + TypeSpec type; + int array_size = 0; + int offset_override = -1; + int offset_assert = -1; + bool is_inline = false; + bool is_dynamic = false; + PrintType printy_type = DEFAULT; +}; + +struct BitFieldDefinition { + std::string name; + TypeSpec type; + int offset_override = -1; + int offset_assert = -1; + int size = -1; +}; + +struct CompilerConfigEntry { + std::string name; + Object value; +}; + +constexpr int BITS_PER_BYTE = 8; + +class Goal { + public: + // GOAL + Goal(); + void execute_repl(); + ~Goal(); + + // ASM + std::shared_ptr compile_asm(const Object& form, Object rest, std::shared_ptr env); + + // BLOCK + std::shared_ptr compile_top_level(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_begin(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_block(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_return_from(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_label(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_goto(const Object& form, + Object rest, + std::shared_ptr env); + + // ATOMS + bool try_getting_macro_from_goos(Object macro_name, Object* dest); + std::shared_ptr compile_goos_macro(Object form, + Object macro, + Object rest, + std::shared_ptr env); + + // compiler control + std::shared_ptr compile_gs(const Object& form, Object rest, std::shared_ptr env); + std::shared_ptr compile_exit(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_asm_file(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_test(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_in_package(const Object& form, + Object rest, + std::shared_ptr env); + + // conditional + std::shared_ptr compile_gscond(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_seval(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_defglobalconstant(const Object& form, + Object rest, + std::shared_ptr env); + + // flow of control + GoalCondition compile_condition(Object condition, std::shared_ptr env, bool invert); + std::shared_ptr compile_condition_as_bool(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_cond(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_when_goto(const Object& form, + Object rest, + std::shared_ptr env); + + // define + std::shared_ptr compile_define(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_define_extern(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_set(const Object& form, Object rest, std::shared_ptr env); + std::shared_ptr compile_defun_extern(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_declare_method(const Object& form, + Object rest, + std::shared_ptr env); + + // deftype + std::shared_ptr compile_deftype(const Object& form, + Object rest, + std::shared_ptr env); + + // ENUM + std::shared_ptr compile_defenum(const Object& form, + Object rest, + std::shared_ptr env); + + // Field Access + std::shared_ptr compile_deref(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_addr_of(const Object& form, + Object rest, + std::shared_ptr env); + + // function + std::shared_ptr compile_inline(const Object& form, + Object rest, + std::shared_ptr env); + + std::shared_ptr compile_with_inline(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_rlet(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_declare(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_mlet(const Object& form, + Object rest, + std::shared_ptr env); + + std::shared_ptr compile_get_ra_ptr(const Object& form, + Object rest, + std::shared_ptr env); + + // pair + + std::shared_ptr compile_lambda(const Object& form, + Object rest, + std::shared_ptr env); + + std::shared_ptr compile_car(const Object& form, Object rest, std::shared_ptr env); + std::shared_ptr compile_cdr(const Object& form, Object rest, std::shared_ptr env); + + // macro + std::shared_ptr compile_print_type(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_quote(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_current_method_type(const Object& form, + Object rest, + std::shared_ptr env); + + std::shared_ptr compile_defconstant(const Object& form, + Object rest, + std::shared_ptr env); + + // compare + + // object + + std::shared_ptr compile_defmethod(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_new(const Object& form, Object rest, std::shared_ptr env); + std::shared_ptr compile_make_static_object_of_type(const Object& form, + TypeSpec& type, + Object field_defs, + std::shared_ptr env); + + std::shared_ptr compile_method(const Object& form, + Object rest, + std::shared_ptr env); + + // integer math + std::shared_ptr compile_add(const Object& form, Object rest, std::shared_ptr env); + std::shared_ptr compile_sub(const Object& form, Object rest, std::shared_ptr env); + std::shared_ptr compile_mult(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_divide(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_shlv(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_shrv(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_sarv(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_shl(const Object& form, Object rest, std::shared_ptr env); + std::shared_ptr compile_shr(const Object& form, Object rest, std::shared_ptr env); + std::shared_ptr compile_sar(const Object& form, Object rest, std::shared_ptr env); + std::shared_ptr compile_mod(const Object& form, Object rest, std::shared_ptr env); + + std::shared_ptr compile_logior(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_logand(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_logxor(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_lognot(const Object& form, + Object rest, + std::shared_ptr env); + + std::shared_ptr compile_set_config(const Object& form, + Object rest, + std::shared_ptr env); + // BUILDER + std::shared_ptr compile_builder(const Object& form, + Object rest, + std::shared_ptr env); + + std::shared_ptr compile_the(const Object& form, Object rest, std::shared_ptr env); + std::shared_ptr compile_the_as(const Object& form, + Object rest, + std::shared_ptr env); + + std::shared_ptr compile_listen_to_target(const Object& form, + Object rest, + std::shared_ptr env); + std::shared_ptr compile_reset_target(const Object& form, + Object rest, + std::shared_ptr env); + + std::shared_ptr compile_poke(const Object& form, + Object rest, + std::shared_ptr env); + + std::shared_ptr compile_send_test_data(const Object& form, + Object rest, + std::shared_ptr env); + + std::shared_ptr compile_get_method_of_type(TypeSpec type, + const std::string& name, + std::shared_ptr env); + + std::shared_ptr compile_get_method_of_object(std::shared_ptr object, + const std::string& method_name, + std::shared_ptr env); + + // UTIL + void for_each_in_list(Object list, const std::function& f); + int list_length(Object list); + Object get_constant_or_error(Object error_form, const std::string& name); + SymbolTable& get_symbol_table(); + std::shared_ptr as_string_obj(Object obj); + std::string as_string(Object obj); + std::string quoted_sym_as_string(Object obj); + std::vector as_string_list(Object obj); + std::shared_ptr as_pair_obj(Object obj); + Object pair_car(Object obj); + Object pair_cdr(Object obj); + void expect_empty_list(Object obj); + std::shared_ptr as_symbol_obj(Object obj); + std::string symbol_string(Object obj); + + bool write_to_binary_file(const std::string& name, void* data, uint32_t size); + + private: + // GOAL + static void init_logger(); + std::shared_ptr compile_object_file(const std::string& name, const Object& code); + std::shared_ptr compile_top_level_function(const std::string& name, + const Object& code, + std::shared_ptr object); + void color_function(const std::shared_ptr& func); + void color_object_file(const std::shared_ptr& obj); + std::vector codegen_object_file(const std::shared_ptr& obj); + std::shared_ptr compile_error_guard(Object obj, std::shared_ptr env); + void throw_compile_error(Object o, const std::string& err); + + // COMPILE ATOMS + std::shared_ptr compile(Object obj, std::shared_ptr env); + std::shared_ptr compile_pair(Object o, std::shared_ptr env); + std::shared_ptr compile_integer(const Object& form, std::shared_ptr env); + std::shared_ptr compile_integer(int64_t value, std::shared_ptr env); + std::shared_ptr compile_integer_to_gpr(int64_t value, std::shared_ptr env); + std::shared_ptr compile_get_sym_val(const std::string& name, std::shared_ptr env); + std::shared_ptr compile_get_sym_obj(const std::string& name, std::shared_ptr env); + bool is_local_symbol(Object obj, std::shared_ptr env); + std::shared_ptr compile_symbol(const Object& form, std::shared_ptr env); + std::shared_ptr compile_string(const Object& form, std::shared_ptr env); + std::shared_ptr compile_string(const std::string& str, std::shared_ptr env); + std::shared_ptr compile_float(const Object& form, std::shared_ptr env); + std::shared_ptr compile_float(float value, std::shared_ptr env); + + // CONST PROP + std::shared_ptr resolve_bitfield_to_gpr(std::shared_ptr in, + std::shared_ptr env); + std::shared_ptr set_bitfield(std::shared_ptr dest, + std::shared_ptr value, + std::shared_ptr env); + std::shared_ptr try_resolve_bitfield_to_gpr(std::shared_ptr in, + std::shared_ptr env); + std::shared_ptr try_resolve_static_to_gpr(std::shared_ptr in, + std::shared_ptr env); + std::shared_ptr try_resolve_lambda_to_gpr(std::shared_ptr in, + std::shared_ptr env); + std::shared_ptr try_resolve_xmm_to_gpr(std::shared_ptr in, + std::shared_ptr env); + std::shared_ptr try_resolve_mem_to_gpr(std::shared_ptr in, + std::shared_ptr env); + std::shared_ptr try_resolve_static_to_xmm(std::shared_ptr in, + std::shared_ptr env); + std::shared_ptr try_resolve_gpr_to_xmm(std::shared_ptr in, + std::shared_ptr env); + std::shared_ptr try_resolve_integer_to_gpr(std::shared_ptr in, + std::shared_ptr env); + std::shared_ptr resolve_to_gpr(std::shared_ptr in, std::shared_ptr env); + std::shared_ptr resolve_to_xmm(std::shared_ptr in, std::shared_ptr env); + std::shared_ptr resolve_to_gpr_or_xmm(std::shared_ptr in, + std::shared_ptr env); + int64_t compile_to_integer_constant(Object form, std::shared_ptr env); + bool try_converting_to_integer_constant(Place& in, int64_t* out); + std::shared_ptr addr_of(std::shared_ptr plc, std::shared_ptr env); + + // DEFTYPE + StructFieldDefinition parse_struct_field_def(const Object& def); + BitFieldDefinition parse_bit_field_def(const Object& def, std::shared_ptr env); + int get_size_in_type(GoalField& f); + std::shared_ptr deftype_structure(std::shared_ptr new_type, + Object fields, + Object options, + std::shared_ptr env); + std::shared_ptr deftype_bitfield(std::shared_ptr new_type, + Object fields, + Object options, + std::shared_ptr env); + void deftype_methods_helper(Object form, + std::shared_ptr new_type, + std::shared_ptr env); + std::shared_ptr deftype_call_new_method_of_type(Object form, + std::shared_ptr new_type, + uint64_t flags, + std::shared_ptr env); + + // Enum + std::shared_ptr compile_enum_lookup(GoalEnum& e, + Object rest, + std::shared_ptr env); + + // Function + std::shared_ptr compile_real_function_call(const Object& form, + std::shared_ptr function, + std::vector> args, + std::shared_ptr env); + std::shared_ptr compile_function_or_method_call(const Object& form, + std::shared_ptr env); + + // READER UTIL + Object read_from_file(const std::string& file_name); + Object read_from_stdin_prompt(const std::string& prompt_name); + Object read_from_string(const std::string& str); + + // CONFIG UTIL + Object get_config(const std::string& name); + void set_config(const std::string& name, const Object& value); + void setup_default_config(); + + // ERROR UTIL + void ice(const std::string& error); + + // MAIN DRIVERS + + // NUMERIC UTIL + std::shared_ptr to_integer(std::shared_ptr in, std::shared_ptr env); + std::shared_ptr to_binteger(std::shared_ptr in, std::shared_ptr env); + std::shared_ptr to_float(std::shared_ptr in, std::shared_ptr env); + bool is_signed_integer(TypeSpec& ts); + bool is_integer(TypeSpec& ts); + bool is_number(TypeSpec& ts); + bool is_binteger(TypeSpec& ts); + bool is_float(TypeSpec& ts); + std::shared_ptr to_same_numeric_type(std::shared_ptr obj, + TypeSpec numeric_type, + std::shared_ptr env); + + // TYPE UTIL + void typecheck_base_only(const Object& form, + TypeSpec& destination_type, + TypeSpec& source_type, + const std::string& error); + + void typecheck_for_set(const Object& form, + TypeSpec& destination_type, + TypeSpec& source_type, + const std::string& error); + + TypeSpec get_base_typespec(const std::string& name); + TypeSpec compile_typespec(const Object& form); + + TypeSpec get_base_of_inline_array(TypeSpec ts); + TypeSpec get_base_of_pointer(TypeSpec ts); + std::shared_ptr compile_logop(const Object& form, + Object rest, + std::shared_ptr env, + LogOpKind kind); + + bool is_basic(TypeSpec& ts); + + std::vector> get_parents(std::shared_ptr); + TypeSpec lowest_common_ancestor(TypeSpec a, TypeSpec b); + TypeSpec lowest_common_ancestor(std::vector ts); + + MathMode get_math_mode(TypeSpec& ts); + + void typecheck_full(const Object& form, + TypeSpec& destination_type, + TypeSpec& source_type, + const std::string& error); + + ColoringAssignment reg_name_to_ca(Object& name); + + bool is_none(std::shared_ptr pl) { + return std::dynamic_pointer_cast(pl) != nullptr; + } + + static bool truthy(Object o) { + if (o.type == SYMBOL && o.as_symbol()->name == "#f") + return false; + if (o.type == EMPTY_LIST) + return false; // debatable if this is ok. + return true; + } + + std::shared_ptr compile_shift(const Object& form, + Object rest, + std::shared_ptr env, + bool is_left, + bool is_arith); + std::shared_ptr compile_shift(std::shared_ptr in, + std::shared_ptr sa, + std::shared_ptr env, + bool is_left, + bool is_arith); + + std::shared_ptr compile_fixed_shift(const Object& form, + Object rest, + std::shared_ptr env, + bool is_left, + bool is_arith); + std::shared_ptr compile_fixed_shift(std::shared_ptr in, + uint8_t sa, + std::shared_ptr env, + bool is_left, + bool is_arith); + + std::shared_ptr make_rlet_env(Object defs, std::shared_ptr env); + + void set_symbol_type(const std::string& name, TypeSpec t) { + symbol_types[SymbolObject::make_new(goos.reader.symbolTable, name).as_symbol()] = t; + } + + void generate_inspector_format_call(const std::string& format, + std::vector> args, + std::shared_ptr env); + std::shared_ptr generate_inspector_for_type(std::shared_ptr type, + std::shared_ptr env); + + Goos goos; + Listener listener; + bool want_exit = false; + std::shared_ptr global_env; + + TypeContainer types; + + std::unordered_map, Object> global_constants; + std::unordered_map, TypeSpec> symbol_types; + std::unordered_map, std::shared_ptr> + inlineable_functions; + std::unordered_map enums; + std::unordered_map config_data; +}; + +#endif // JAK_GOAL_H diff --git a/old_compiler/cpp/goal/GoalAsm.cpp b/old_compiler/cpp/goal/GoalAsm.cpp new file mode 100644 index 0000000000..afaf4f7ce8 --- /dev/null +++ b/old_compiler/cpp/goal/GoalAsm.cpp @@ -0,0 +1,55 @@ +/*! + * @file GoalAsm.cpp + * GOAL Assembly forms, used to include x86 instructions directly in the program. + */ + +#include "Goal.h" +#include "util.h" + +/*! + * Helper to compile any of the assembly forms. + */ +std::shared_ptr Goal::compile_asm(const Object& form, + Object rest, + std::shared_ptr env) { + // get op and args + auto op = symbol_string(pair_car(form)); + std::vector> args; + for_each_in_list(rest, [&](Object o) { + args.push_back(resolve_to_gpr_or_xmm(compile_error_guard(o, env), env)); + }); + + auto check_arg_count = [&](size_t desired) { + if (desired != args.size()) { + throw_compile_error(form, "Assembly form got " + std::to_string(args.size()) + + " arguments, but requires " + std::to_string(desired)); + } + }; + + // check argument count and emit the correct IR + if (op == ".ret") { + check_arg_count(0); + env->emit(make_unique(IR_Asm::RET, args)); + } else if (op == ".ret-reg") { + check_arg_count(1); + env->emit(make_unique(IR_Asm::RET_REGISTER, args)); + } else if (op == ".push") { + check_arg_count(1); + env->emit(make_unique(IR_Asm::PUSH, args)); + } else if (op == ".jmp") { + check_arg_count(1); + env->emit(make_unique(IR_Asm::JMP, args)); + } else if (op == ".sub") { + check_arg_count(2); + env->emit(make_unique(IR_Asm::SUB, args)); + } else if (op == ".pop") { + check_arg_count(1); + env->emit(make_unique(IR_Asm::POP, args)); + } + + else { + ice("Goal::compile_asm encountered an unknown asm operation: " + op); + } + + return get_none(); +} \ No newline at end of file diff --git a/old_compiler/cpp/goal/GoalBitfieldAccess.cpp b/old_compiler/cpp/goal/GoalBitfieldAccess.cpp new file mode 100644 index 0000000000..facec89661 --- /dev/null +++ b/old_compiler/cpp/goal/GoalBitfieldAccess.cpp @@ -0,0 +1,53 @@ +#include "Goal.h" + +std::shared_ptr Goal::resolve_bitfield_to_gpr(std::shared_ptr in, + std::shared_ptr env) { + auto result = env->alloc_reg(in->type); + int field_offset = in->field.offset; + int field_size = in->field.size; + int field_left = 64 - (field_offset + field_size); + assert(field_left >= 0); + + result = compile_fixed_shift(in->base, field_left, env, true, false); + result = compile_fixed_shift(result, field_left + field_offset, env, false, + in->type.type->load_signed); + result->type = in->type; + return result; +} + +static uint64_t build_mask(int size, int offset) { + return ~(((1ll << size) - 1) << offset); +} + +std::shared_ptr Goal::set_bitfield(std::shared_ptr dest, + std::shared_ptr value, + std::shared_ptr env) { + value = resolve_to_gpr(value, env); + int field_offset = dest->field.offset; + int field_size = dest->field.size; + int field_left = 64 - (field_offset + field_size); + + // check for sext needed + auto& dest_type = dest->type.type; + if (dest_type->load_signed) { + ice("unsupported sext needed in set bitfield"); + } + + // kill the extra bits + value->type = get_base_typespec("integer"); + auto left_shifted = compile_fixed_shift(value, (64 - field_size), env, true, false); + // move to right spot + auto located = compile_fixed_shift(left_shifted, field_left, env, false, false); + + // get the mask to clear the destination + auto mask = compile_integer_to_gpr(build_mask(field_size, field_offset), env); + + // CLEAR + env->emit(make_unique(AND_64, dest->base, mask)); + + // WRITE + env->emit(make_unique(OR_64, dest->base, located)); + + // ??? + return dest->base; +} \ No newline at end of file diff --git a/old_compiler/cpp/goal/GoalBlockForms.cpp b/old_compiler/cpp/goal/GoalBlockForms.cpp new file mode 100644 index 0000000000..358b06bf02 --- /dev/null +++ b/old_compiler/cpp/goal/GoalBlockForms.cpp @@ -0,0 +1,186 @@ +/*! + * @file GoalBlockForms.cpp + * GOAL Compiler Forms related to blocks. + */ + +#include "Goal.h" +#include "GoalEnv.h" +#include "util.h" + +/*! + * Compile a list of forms. The top-level form is the same as a begin, but top-levels are generated + * by the reader when reading a file. + */ +std::shared_ptr Goal::compile_top_level(const Object& form, + Object rest, + std::shared_ptr env) { + return compile_begin(form, rest, env); +} + +/*! + * Compile begin statement. Just compile everything in order and return the last thing. + * If there's nothing in it, return none. + */ +std::shared_ptr Goal::compile_begin(const Object& form, + Object rest, + std::shared_ptr env) { + (void)form; + std::shared_ptr result = get_none(); + for_each_in_list(rest, [&](Object o) { result = compile_error_guard(o, env); }); + return result; +} + +/*! + * Compile a block statement. + * This pushes a block environment and is like a begin. + * It returns the last thing in the list, unless you jump to the end with a return-from. + * The type of the return-from's are checked! + */ +std::shared_ptr Goal::compile_block(const Object& form, + Object rest, + std::shared_ptr env) { + auto name = pair_car(rest); + rest = pair_cdr(rest); + + if (rest.type != PAIR) { + throw_compile_error(form, "Block form has an empty body"); + } + + // create environment + auto block_env = std::make_shared(env, symbol_string(name)); + + // we need to create a return value register, as a "return-from" statement inside the block may + // set it. for now it has a type of none, but we will set it more accurate after compiling the + // block. + block_env->return_value = env->alloc_reg(get_base_typespec("none")); + + // create label to the end of the block (we don't yet know where it is...) + block_env->end_label = std::make_shared