add the old compiler!

This commit is contained in:
water
2020-08-27 11:58:19 -04:00
parent 4ee573267f
commit 645181692f
237 changed files with 24741 additions and 8 deletions
+4 -1
View File
@@ -38,4 +38,7 @@ add_subdirectory(test)
add_subdirectory(third-party/minilzo)
# build format library
add_subdirectory(third-party/fmt)
add_subdirectory(third-party/fmt)
# for now...
add_subdirectory(old_compiler/cpp)
+6 -6
View File
@@ -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");
}
/*!
+1 -1
View File
@@ -619,7 +619,7 @@ Ptr<Type> 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;
}
+23
View File
@@ -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)
+6
View File
@@ -0,0 +1,6 @@
add_library(codegen SHARED
Coloring.cpp
CodegenOutput.cpp
x86_Emitter.cpp
x86_Emitter_LinkData.cpp
x86_Emitter_ConvertIR.cpp)
@@ -0,0 +1,21 @@
#include "CodegenOutput.h"
/*!
* Convert codegen output to a single binary blob.
*/
std::vector<uint8_t> CodegenOutput::to_vector() {
std::vector<uint8_t> 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;
}
+34
View File
@@ -0,0 +1,34 @@
#ifndef JAK_CODEGENOUTPUT_H
#define JAK_CODEGENOUTPUT_H
#include <cstdint>
#include <array>
#include <vector>
#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<std::vector<uint8_t>, N_SEG> code;
// the link data
std::array<std::vector<uint8_t>, N_SEG> link_tables;
// maps from instr_idx to offset into code
std::array<std::vector<int>, N_SEG> instr_offsets;
// the header data (goes before the link_tables to form the link section)
std::vector<uint8_t> header;
// offset into the segment's code for where the static objects start.
std::array<int, N_SEG> static_start;
// make into a single blob for loading into the runtime.
std::vector<uint8_t> to_vector();
};
#endif // JAK_CODEGENOUTPUT_H
+203
View File
@@ -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<bool> 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<bool> 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;
}
}
+14
View File
@@ -0,0 +1,14 @@
/*!
* @file Coloring.h
* High Level Interface for register coloring.
*/
#ifndef JAK_COLORING_H
#define JAK_COLORING_H
#include <memory>
class FunctionEnv;
bool do_linear_scan_coloring(FunctionEnv& func);
#endif // JAK_COLORING_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 <stdexcept>
#include <cassert>
#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<bool> is_alive;
std::vector<int> 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<ColoringAssignment> 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<BonusOp> 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
+746
View File
@@ -0,0 +1,746 @@
/*!
* @file IGen.h
* Instruction Generation for x86-64
* Generate Instruction objects
*/
#ifndef JAK_IGEN_H
#define JAK_IGEN_H
#include <cstdint>
#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
+344
View File
@@ -0,0 +1,344 @@
/*!
* @file: Instruction.h
* x86-64 Instruction encoding.
*/
#ifndef JAK_INSTRUCTION_H
#define JAK_INSTRUCTION_H
#include <cstdint>
/*!
* 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
@@ -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
+23
View File
@@ -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 <vector>
/*!
* Push a thing into a byte vector.
*/
template <typename T>
uint32_t push_data_to_byte_vector(T data, std::vector<uint8_t>& 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
+110
View File
@@ -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
+476
View File
@@ -0,0 +1,476 @@
/*!
* @file x86_Emitter.cpp
* Emitter for converting IR and static objects into GOAL object files for x86.
*/
#include <algorithm>
#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<int>& 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<IR_Return*>(x.get()));
break;
case LOAD_INTEGER:
do_constvar(*dynamic_cast<IR_LoadInteger*>(x.get()));
break;
case SET:
do_set(*dynamic_cast<IR_Set*>(x.get()));
break;
case GOTO_LABEL:
do_goto_label(*dynamic_cast<IR_Goto_Label*>(x.get()));
break;
case SET_SYMBOL_VALUE:
do_set_symbol(*dynamic_cast<IR_SetSymbolValue*>(x.get()));
break;
case GET_SYMBOL_VALUE:
do_get_symbol(*dynamic_cast<IR_GetSymbolValue*>(x.get()));
break;
case FUNCTION_CALL:
do_function_call(*dynamic_cast<IR_FunctionCall*>(x.get()));
break;
case STATIC_VAR_ADDR:
do_static_var_addr(*dynamic_cast<IR_StaticVarAddr*>(x.get()));
break;
case FUNC_ADDR:
do_function_addr(*dynamic_cast<IR_FunctionAddr*>(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<IR_IntegerMath*>(x.get()));
break;
case GET_SYMBOL_OBJ:
do_get_symbol_object(*dynamic_cast<IR_GetSymbolObj*>(x.get()));
break;
case CONDITIONAL_BRANCH:
do_cond_branch(*dynamic_cast<IR_ConditionalBranch*>(x.get()));
break;
case STATIC_VAR_32:
do_static_var_32(*dynamic_cast<IR_StaticVar32*>(x.get()));
break;
case FLOAT_MATH:
do_float_math(*dynamic_cast<IR_FloatMath*>(x.get()));
break;
case LOAD_CONST_OFFSET:
do_load_const_offset(*dynamic_cast<IR_LoadConstOffset*>(x.get()));
break;
case STORE_CONST_OFFSET:
do_store_const_offset(*dynamic_cast<IR_StoreConstOffset*>(x.get()));
break;
case FLOAT_TO_INT:
do_float_to_int(*dynamic_cast<IR_FloatToInt*>(x.get()));
break;
case INT_TO_FLOAT:
do_int_to_float(*dynamic_cast<IR_IntToFloat*>(x.get()));
break;
case GET_RETURN_ADDRESS_POINTER:
do_get_ra_ptr(*dynamic_cast<IR_GetReturnAddressPointer*>(x.get()));
break;
case ASM:
do_asm(*dynamic_cast<IR_Asm*>(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;
}
+179
View File
@@ -0,0 +1,179 @@
#ifndef JAK_X86_EMITTER_H
#define JAK_X86_EMITTER_H
#include <cstdint>
#include <stdexcept>
#include <memory>
#include <vector>
#include <unordered_map>
#include <string>
#include <array>
#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<StaticPlace> 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<LambdaPlace> 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<int>& 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<int>& instruction_offsets, int seg);
void emit_link_table_func_addr(CodegenOutput& out,
std::vector<int>& 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<int>& 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<Place> var);
void emit_mov_gpr64_gpr64_or_null(uint8_t dst, uint8_t src);
void emit_mov_gpr64_gpr64_or_null(std::shared_ptr<Place> dst, std::shared_ptr<Place> src);
void emit_mov_xmm32_xmm32_or_null(std::shared_ptr<Place> dst, std::shared_ptr<Place> 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<std::vector<Instruction>, N_SEG> instructions;
std::array<std::vector<StaticJumpRecord>, N_SEG> static_jumps;
std::array<std::vector<uint8_t>, N_SEG> static_objects;
std::array<std::vector<StaticVarAddrRecord>, N_SEG> static_var_addr_recs;
std::array<std::vector<FuncAddrRecord>, N_SEG> func_addr_recs;
int current_seg = -1;
// map of symbol name -> list of mem access recs for each segment
std::array<std::unordered_map<std::string, std::vector<SymbolMemAccessRec>>, N_SEG>
symbol_mem_access_recs;
// type pointers in statics: map of type name -> list of offsets
std::array<std::unordered_map<std::string, std::vector<StaticLinkRecord>>, 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<std::vector<int>, 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
@@ -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<NonePlace>(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<SymbolPlace>(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<StaticPlace>(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<StaticPlace>(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<StaticPlace>(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<LambdaPlace>(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<Place> var) {
auto as_reg = std::dynamic_pointer_cast<GprPlace>(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<Place> dst,
std::shared_ptr<Place> 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<Place> dst,
std::shared_ptr<Place> 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");
}
}
@@ -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<uint32_t>(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<int32_t>(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<uint32_t>(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<int32_t>(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<uint32_t>(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<int32_t>(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<uint32_t>(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<int32_t>(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<int>& 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<uint32_t>(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<uint32_t>(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<uint32_t>(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<int>& 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<uint32_t>(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<uint32_t>(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<uint32_t>(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<int>& 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<uint16_t>(GOAL_VERSION_MAJOR, out.header);
offset += push_data_to_byte_vector<uint16_t>(GOAL_VERSION_MINOR, out.header);
offset += push_data_to_byte_vector<uint32_t>(3, out.header);
offset += push_data_to_byte_vector<uint32_t>(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<SizeOffsetTable>(table, out.header);
push_data_to_byte_vector<uint32_t>(64 + 4 + total_link_size, out.header);
}
+34
View File
@@ -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)
+18
View File
@@ -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 <utility>
#include <string>
static std::pair<std::string, std::string> default_config[] = {{"debug-print-ir", "#f"},
{"debug-print-obj", "#f"},
{"print-asm-file-time", "#f"}};
#endif // JAK_V2_DEFAULTCONFIG_H
+233
View File
@@ -0,0 +1,233 @@
/*!
* @file Goal.cpp
* The GOAL Compiler!
*/
#include <memory>
#include <cstring>
#include <unordered_map>
#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<GlobalEnv>();
// 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<ObjectFileEnv>("init-env", global_env);
auto library_f_env = std::make_shared<FunctionEnv>("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<ObjectFileEnv> Goal::compile_object_file(const std::string& name,
const Object& code) {
auto obj_env = std::make_shared<ObjectFileEnv>(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<FunctionEnv> Goal::compile_top_level_function(
const std::string& name,
const Object& code,
std::shared_ptr<ObjectFileEnv> object) {
auto func_env = std::make_shared<FunctionEnv>(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<IR_Return>(
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<FunctionEnv>& 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<ObjectFileEnv>& 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<uint8_t> Goal::codegen_object_file(const std::shared_ptr<ObjectFileEnv>& 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<Place> Goal::compile_error_guard(Object obj, std::shared_ptr<GoalEnv> 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);
}
+520
View File
@@ -0,0 +1,520 @@
#ifndef JAK_GOAL_H
#define JAK_GOAL_H
#include <functional>
#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<Place> compile_asm(const Object& form, Object rest, std::shared_ptr<GoalEnv> env);
// BLOCK
std::shared_ptr<Place> compile_top_level(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_begin(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_block(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_return_from(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_label(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_goto(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
// ATOMS
bool try_getting_macro_from_goos(Object macro_name, Object* dest);
std::shared_ptr<Place> compile_goos_macro(Object form,
Object macro,
Object rest,
std::shared_ptr<GoalEnv> env);
// compiler control
std::shared_ptr<Place> compile_gs(const Object& form, Object rest, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_exit(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_asm_file(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_test(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_in_package(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
// conditional
std::shared_ptr<Place> compile_gscond(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_seval(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_defglobalconstant(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
// flow of control
GoalCondition compile_condition(Object condition, std::shared_ptr<GoalEnv> env, bool invert);
std::shared_ptr<Place> compile_condition_as_bool(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_cond(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_when_goto(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
// define
std::shared_ptr<Place> compile_define(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_define_extern(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_set(const Object& form, Object rest, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_defun_extern(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_declare_method(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
// deftype
std::shared_ptr<Place> compile_deftype(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
// ENUM
std::shared_ptr<Place> compile_defenum(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
// Field Access
std::shared_ptr<Place> compile_deref(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_addr_of(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
// function
std::shared_ptr<Place> compile_inline(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_with_inline(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_rlet(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_declare(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_mlet(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_get_ra_ptr(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
// pair
std::shared_ptr<Place> compile_lambda(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_car(const Object& form, Object rest, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_cdr(const Object& form, Object rest, std::shared_ptr<GoalEnv> env);
// macro
std::shared_ptr<Place> compile_print_type(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_quote(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_current_method_type(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_defconstant(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
// compare
// object
std::shared_ptr<Place> compile_defmethod(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_new(const Object& form, Object rest, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_make_static_object_of_type(const Object& form,
TypeSpec& type,
Object field_defs,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_method(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
// integer math
std::shared_ptr<Place> compile_add(const Object& form, Object rest, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_sub(const Object& form, Object rest, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_mult(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_divide(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_shlv(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_shrv(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_sarv(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_shl(const Object& form, Object rest, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_shr(const Object& form, Object rest, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_sar(const Object& form, Object rest, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_mod(const Object& form, Object rest, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_logior(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_logand(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_logxor(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_lognot(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_set_config(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
// BUILDER
std::shared_ptr<Place> compile_builder(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_the(const Object& form, Object rest, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_the_as(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_listen_to_target(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_reset_target(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_poke(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_send_test_data(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_get_method_of_type(TypeSpec type,
const std::string& name,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_get_method_of_object(std::shared_ptr<Place> object,
const std::string& method_name,
std::shared_ptr<GoalEnv> env);
// UTIL
void for_each_in_list(Object list, const std::function<void(Object)>& 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<StringObject> as_string_obj(Object obj);
std::string as_string(Object obj);
std::string quoted_sym_as_string(Object obj);
std::vector<std::string> as_string_list(Object obj);
std::shared_ptr<PairObject> as_pair_obj(Object obj);
Object pair_car(Object obj);
Object pair_cdr(Object obj);
void expect_empty_list(Object obj);
std::shared_ptr<SymbolObject> 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<ObjectFileEnv> compile_object_file(const std::string& name, const Object& code);
std::shared_ptr<FunctionEnv> compile_top_level_function(const std::string& name,
const Object& code,
std::shared_ptr<ObjectFileEnv> object);
void color_function(const std::shared_ptr<FunctionEnv>& func);
void color_object_file(const std::shared_ptr<ObjectFileEnv>& obj);
std::vector<uint8_t> codegen_object_file(const std::shared_ptr<ObjectFileEnv>& obj);
std::shared_ptr<Place> compile_error_guard(Object obj, std::shared_ptr<GoalEnv> env);
void throw_compile_error(Object o, const std::string& err);
// COMPILE ATOMS
std::shared_ptr<Place> compile(Object obj, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_pair(Object o, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_integer(const Object& form, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_integer(int64_t value, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_integer_to_gpr(int64_t value, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_get_sym_val(const std::string& name, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_get_sym_obj(const std::string& name, std::shared_ptr<GoalEnv> env);
bool is_local_symbol(Object obj, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_symbol(const Object& form, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_string(const Object& form, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_string(const std::string& str, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_float(const Object& form, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_float(float value, std::shared_ptr<GoalEnv> env);
// CONST PROP
std::shared_ptr<Place> resolve_bitfield_to_gpr(std::shared_ptr<BitfieldPlace> in,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> set_bitfield(std::shared_ptr<BitfieldPlace> dest,
std::shared_ptr<Place> value,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> try_resolve_bitfield_to_gpr(std::shared_ptr<Place> in,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> try_resolve_static_to_gpr(std::shared_ptr<Place> in,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> try_resolve_lambda_to_gpr(std::shared_ptr<Place> in,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> try_resolve_xmm_to_gpr(std::shared_ptr<Place> in,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> try_resolve_mem_to_gpr(std::shared_ptr<Place> in,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> try_resolve_static_to_xmm(std::shared_ptr<Place> in,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> try_resolve_gpr_to_xmm(std::shared_ptr<Place> in,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> try_resolve_integer_to_gpr(std::shared_ptr<Place> in,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> resolve_to_gpr(std::shared_ptr<Place> in, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> resolve_to_xmm(std::shared_ptr<Place> in, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> resolve_to_gpr_or_xmm(std::shared_ptr<Place> in,
std::shared_ptr<GoalEnv> env);
int64_t compile_to_integer_constant(Object form, std::shared_ptr<GoalEnv> env);
bool try_converting_to_integer_constant(Place& in, int64_t* out);
std::shared_ptr<Place> addr_of(std::shared_ptr<Place> plc, std::shared_ptr<GoalEnv> env);
// DEFTYPE
StructFieldDefinition parse_struct_field_def(const Object& def);
BitFieldDefinition parse_bit_field_def(const Object& def, std::shared_ptr<GoalEnv> env);
int get_size_in_type(GoalField& f);
std::shared_ptr<Place> deftype_structure(std::shared_ptr<StructureType> new_type,
Object fields,
Object options,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> deftype_bitfield(std::shared_ptr<BitfieldType> new_type,
Object fields,
Object options,
std::shared_ptr<GoalEnv> env);
void deftype_methods_helper(Object form,
std::shared_ptr<GoalType> new_type,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> deftype_call_new_method_of_type(Object form,
std::shared_ptr<GoalType> new_type,
uint64_t flags,
std::shared_ptr<GoalEnv> env);
// Enum
std::shared_ptr<Place> compile_enum_lookup(GoalEnum& e,
Object rest,
std::shared_ptr<GoalEnv> env);
// Function
std::shared_ptr<Place> compile_real_function_call(const Object& form,
std::shared_ptr<Place> function,
std::vector<std::shared_ptr<Place>> args,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> compile_function_or_method_call(const Object& form,
std::shared_ptr<GoalEnv> 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<Place> to_integer(std::shared_ptr<Place> in, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> to_binteger(std::shared_ptr<Place> in, std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> to_float(std::shared_ptr<Place> in, std::shared_ptr<GoalEnv> 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<Place> to_same_numeric_type(std::shared_ptr<Place> obj,
TypeSpec numeric_type,
std::shared_ptr<GoalEnv> 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<Place> compile_logop(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env,
LogOpKind kind);
bool is_basic(TypeSpec& ts);
std::vector<std::shared_ptr<GoalType>> get_parents(std::shared_ptr<GoalType>);
TypeSpec lowest_common_ancestor(TypeSpec a, TypeSpec b);
TypeSpec lowest_common_ancestor(std::vector<TypeSpec> 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<Place> pl) {
return std::dynamic_pointer_cast<NonePlace>(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<Place> compile_shift(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env,
bool is_left,
bool is_arith);
std::shared_ptr<Place> compile_shift(std::shared_ptr<Place> in,
std::shared_ptr<Place> sa,
std::shared_ptr<GoalEnv> env,
bool is_left,
bool is_arith);
std::shared_ptr<Place> compile_fixed_shift(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env,
bool is_left,
bool is_arith);
std::shared_ptr<Place> compile_fixed_shift(std::shared_ptr<Place> in,
uint8_t sa,
std::shared_ptr<GoalEnv> env,
bool is_left,
bool is_arith);
std::shared_ptr<LexicalEnv> make_rlet_env(Object defs, std::shared_ptr<GoalEnv> 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<std::shared_ptr<Place>> args,
std::shared_ptr<GoalEnv> env);
std::shared_ptr<Place> generate_inspector_for_type(std::shared_ptr<StructureType> type,
std::shared_ptr<GoalEnv> env);
Goos goos;
Listener listener;
bool want_exit = false;
std::shared_ptr<GoalEnv> global_env;
TypeContainer types;
std::unordered_map<std::shared_ptr<SymbolObject>, Object> global_constants;
std::unordered_map<std::shared_ptr<SymbolObject>, TypeSpec> symbol_types;
std::unordered_map<std::shared_ptr<SymbolObject>, std::shared_ptr<LambdaPlace>>
inlineable_functions;
std::unordered_map<std::string, GoalEnum> enums;
std::unordered_map<std::string, CompilerConfigEntry> config_data;
};
#endif // JAK_GOAL_H
+55
View File
@@ -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<Place> Goal::compile_asm(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
// get op and args
auto op = symbol_string(pair_car(form));
std::vector<std::shared_ptr<Place>> 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>(IR_Asm::RET, args));
} else if (op == ".ret-reg") {
check_arg_count(1);
env->emit(make_unique<IR_Asm>(IR_Asm::RET_REGISTER, args));
} else if (op == ".push") {
check_arg_count(1);
env->emit(make_unique<IR_Asm>(IR_Asm::PUSH, args));
} else if (op == ".jmp") {
check_arg_count(1);
env->emit(make_unique<IR_Asm>(IR_Asm::JMP, args));
} else if (op == ".sub") {
check_arg_count(2);
env->emit(make_unique<IR_Asm>(IR_Asm::SUB, args));
} else if (op == ".pop") {
check_arg_count(1);
env->emit(make_unique<IR_Asm>(IR_Asm::POP, args));
}
else {
ice("Goal::compile_asm encountered an unknown asm operation: " + op);
}
return get_none();
}
@@ -0,0 +1,53 @@
#include "Goal.h"
std::shared_ptr<Place> Goal::resolve_bitfield_to_gpr(std::shared_ptr<BitfieldPlace> in,
std::shared_ptr<GoalEnv> 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<Place> Goal::set_bitfield(std::shared_ptr<BitfieldPlace> dest,
std::shared_ptr<Place> value,
std::shared_ptr<GoalEnv> 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<IR_IntegerMath>(AND_64, dest->base, mask));
// WRITE
env->emit(make_unique<IR_IntegerMath>(OR_64, dest->base, located));
// ???
return dest->base;
}
+186
View File
@@ -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<Place> Goal::compile_top_level(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> 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<Place> Goal::compile_begin(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)form;
std::shared_ptr<Place> 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<Place> Goal::compile_block(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> 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<BlockEnv>(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<Label>(get_parent_env_of_type<FunctionEnv>(env).get());
// compile everything in the body
std::shared_ptr<Place> result = get_none();
for_each_in_list(rest, [&](Object o) { result = compile_error_guard(o, block_env); });
// if no return-from's were used, we can ignore the return_value register, and basically turn this
// into a begin. this allows a block which returns a floating point value to return the value in
// an xmm register, which is likely to eliminate a gpr->xmm move.
if (block_env->return_types.empty()) {
return result;
}
// determine return type as the lowest common ancestor of the block's last form and any
// return-from's
auto& return_types = block_env->return_types;
return_types.push_back(result->type);
auto return_type = lowest_common_ancestor(return_types);
// an IR to move the result of the block into the block's return register (if no return-from's are
// taken)
auto ir_move_rv = make_unique<IR_Set>();
ir_move_rv->dest = block_env->return_value;
ir_move_rv->dest->type = return_type;
// note - one drawback of doing this single pass is that a block always evaluates to a gpr.
// so we may have an unneeded xmm -> gpr move that could have been an xmm -> xmm that could have
// been eliminated.
ir_move_rv->src = resolve_to_gpr_or_xmm(result, env);
env->emit(std::move(ir_move_rv));
// now we know the end of the block, so we set the label index to be on whatever comes after the
// return move. functions always end with a "null" IR and "null" instruction, so this is safe.
block_env->end_label->idx = block_env->end_label->func->code.size();
return block_env->return_value;
}
/*!
* Compile a return-from statement.
* Note that doing a "return-from" will affect the return type of the block
*/
std::shared_ptr<Place> Goal::compile_return_from(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
auto block_name = symbol_string(pair_car(rest));
rest = pair_cdr(rest);
auto value_expression = pair_car(rest);
expect_empty_list(pair_cdr(rest));
// evaluate expression to return
auto result = compile_error_guard(value_expression, env);
// find block to return from
auto block = dynamic_cast<BlockEnv*>(env->find_block(block_name));
if (!block) {
throw_compile_error(form,
"The return-from form was unable to find a block named " + block_name);
}
// move result into return register
auto ir_move_rv = make_unique<IR_Set>();
ir_move_rv->dest = block->return_value;
ir_move_rv->src = resolve_to_gpr_or_xmm(result, env);
// inform block of our possible return type
block->return_types.push_back(result->type);
env->emit(std::move(ir_move_rv));
// jump to end of block
auto ir_jump = make_unique<IR_Goto_Label>();
ir_jump->label = block->end_label;
// we know this label is a real label. even though end_label doesn't know where it is, there is an
// actual label object. this means we won't try to resolve this label _by name_ later on when the
// block is done.
ir_jump->resolved = true;
env->emit(std::move(ir_jump));
// In the real GOAL, there is likely a bug here where a non-none value is returned.
return get_none();
}
/*!
* Compile a label statement
*/
std::shared_ptr<Place> Goal::compile_label(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
auto label_name = symbol_string(pair_car(rest));
expect_empty_list(pair_cdr(rest));
// make sure we don't have a label with this name already
auto& labels = env->get_label_map();
auto kv = labels.find(label_name);
if (kv != labels.end()) {
throw_compile_error(
form, "There are two labels named " + label_name + " in the same label environment");
}
// make a label pointing to the end of the current function env.
auto func_env = get_parent_env_of_type<FunctionEnv>(env);
auto new_label = std::make_shared<Label>(func_env.get(), func_env->code.size());
labels[label_name] = new_label;
return get_none();
}
/*!
* Compile a goto
*/
std::shared_ptr<Place> Goal::compile_goto(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)form;
auto label_name = symbol_string(pair_car(rest));
expect_empty_list(pair_cdr(rest));
auto ir_goto = make_unique<IR_Goto_Label>();
// this requires looking up the label by name after, as it may be a goto to a label which has not
// yet been defined.
ir_goto->resolved = false;
// add this goto to the list of gotos to resolve after the function is done.
// it's safe to have this reference, as the FunctionEnv also owns the goto.
get_parent_env_of_type<FunctionEnv>(env)->unresolved_gotos.push_back({ir_goto.get(), label_name});
env->emit(std::move(ir_goto));
return get_none();
}
+386
View File
@@ -0,0 +1,386 @@
/*!
* @file GoalCompileAtoms.cpp
* The top-level dispatch of the compilation process.
*/
#include <util.h>
#include "Goal.h"
/*!
* Main table for compiler forms
*/
static const std::unordered_map<
std::string,
std::shared_ptr<Place> (Goal::*)(const Object& form, Object rest, std::shared_ptr<GoalEnv> env)>
goal_forms = {
// inline asm
{".ret", &Goal::compile_asm},
{".push", &Goal::compile_asm},
{".pop", &Goal::compile_asm},
{".jmp", &Goal::compile_asm},
{".sub", &Goal::compile_asm},
{".ret-reg", &Goal::compile_asm},
// BLOCK FORMS
{"top-level", &Goal::compile_top_level},
{"begin", &Goal::compile_begin},
{"block", &Goal::compile_block},
{"return-from", &Goal::compile_return_from},
{"label", &Goal::compile_label},
{"goto", &Goal::compile_goto},
// COMPILER CONTROL
{"gs", &Goal::compile_gs},
{":exit", &Goal::compile_exit},
{"asm-file", &Goal::compile_asm_file},
{"test", &Goal::compile_test},
{"in-package", &Goal::compile_in_package},
// CONDITIONAL COMPILATION
{"#cond", &Goal::compile_gscond},
{"defglobalconstant", &Goal::compile_defglobalconstant},
{"seval", &Goal::compile_seval},
// CONTROL FLOW
{"cond", &Goal::compile_cond},
{"when-goto", &Goal::compile_when_goto},
// DEFINITION
{"define", &Goal::compile_define},
{"define-extern", &Goal::compile_define_extern},
{"set!", &Goal::compile_set},
{"defun-extern", &Goal::compile_defun_extern},
{"declare-method", &Goal::compile_declare_method},
// DEFTYPE
{"deftype", &Goal::compile_deftype},
// ENUM
{"defenum", &Goal::compile_defenum},
// Field Access
{"->", &Goal::compile_deref},
{"&", &Goal::compile_addr_of},
// LAMBDA
{"lambda", &Goal::compile_lambda},
{"inline", &Goal::compile_inline},
{"with-inline", &Goal::compile_with_inline},
{"rlet", &Goal::compile_rlet},
{"mlet", &Goal::compile_mlet},
{"get-ra-ptr", &Goal::compile_get_ra_ptr},
// MACRO
{"print-type", &Goal::compile_print_type},
{"quote", &Goal::compile_quote},
{"defconstant", &Goal::compile_defconstant},
{"declare", &Goal::compile_declare},
// OBJECT
{"the", &Goal::compile_the},
{"the-as", &Goal::compile_the_as},
{"defmethod", &Goal::compile_defmethod},
{"current-method-type", &Goal::compile_current_method_type},
{"new", &Goal::compile_new},
{"method", &Goal::compile_method},
// PAIR
{"car", &Goal::compile_car},
{"cdr", &Goal::compile_cdr},
// IT IS MATH
{"+", &Goal::compile_add},
{"-", &Goal::compile_sub},
{"*", &Goal::compile_mult},
{"/", &Goal::compile_divide},
{"shlv", &Goal::compile_shlv},
{"shrv", &Goal::compile_shrv},
{"sarv", &Goal::compile_sarv},
{"shl", &Goal::compile_shl},
{"shr", &Goal::compile_shr},
{"sar", &Goal::compile_sar},
{"mod", &Goal::compile_mod},
{"logior", &Goal::compile_logior},
{"logxor", &Goal::compile_logxor},
{"logand", &Goal::compile_logand},
{"lognot", &Goal::compile_lognot},
{"=", &Goal::compile_condition_as_bool},
{"!=", &Goal::compile_condition_as_bool},
{"eq?", &Goal::compile_condition_as_bool},
{"not", &Goal::compile_condition_as_bool},
{"<=", &Goal::compile_condition_as_bool},
{">=", &Goal::compile_condition_as_bool},
{"<", &Goal::compile_condition_as_bool},
{">", &Goal::compile_condition_as_bool},
// BUILDER
// {"builder", &Goal::compile_builder},
// UTIL
{"set-config!", &Goal::compile_set_config},
{"listen-to-target", &Goal::compile_listen_to_target},
{"reset-target", &Goal::compile_reset_target},
{":status", &Goal::compile_poke},
// temporary testing hacks...
{"send-test", &Goal::compile_send_test_data},
};
/*!
* Top Level dispatch for compilation of code.
*/
std::shared_ptr<Place> Goal::compile(Object obj, std::shared_ptr<GoalEnv> env) {
switch (obj.type) {
case PAIR:
return compile_pair(obj, env);
case INTEGER:
return compile_integer(obj, env);
case SYMBOL:
return compile_symbol(obj, env);
case STRING:
return compile_string(obj, env);
case FLOAT:
return compile_float(obj, env);
default:
ice("Goal::compile does not know how to compile " + obj.print());
return get_none();
}
}
/*!
* Compile function for pair object
*/
std::shared_ptr<Place> Goal::compile_pair(Object o, std::shared_ptr<GoalEnv> env) {
auto pair = o.as_pair();
Object head = pair->car;
Object rest = pair->cdr;
if (head.type == SYMBOL) {
// head is a symbol, so this may be a special form.
auto head_sym = head.as_symbol();
// first try as a goal compiler form
auto kv_gfs = goal_forms.find(head_sym->name);
if (kv_gfs != goal_forms.end()) {
return ((*this).*(kv_gfs->second))(o, rest, env);
}
// next try to find a macro defined in the goal_goos_env (containing GOOS macros to be used from
// GOAL)
Object macro_obj;
if (try_getting_macro_from_goos(head, &macro_obj)) {
return compile_goos_macro(o, macro_obj, rest, env);
}
// next try as an enum
auto enum_kv = enums.find(symbol_string(head));
if (enum_kv != enums.end()) {
return compile_enum_lookup(enum_kv->second, rest, env);
}
}
// didn't recognize it as anything special, try it as a function or method call
return compile_function_or_method_call(o, env);
}
/*!
* Compile an integer.
*/
std::shared_ptr<Place> Goal::compile_integer(const Object& form, std::shared_ptr<GoalEnv> env) {
assert(form.type == INTEGER);
int64_t value = form.integer_obj.value;
return compile_integer(value, env);
}
/*!
* Compile an integer.
*/
std::shared_ptr<Place> Goal::compile_integer(int64_t value, std::shared_ptr<GoalEnv> env) {
(void)env;
// simply return an integer constant.
return std::make_shared<IntegerConstantPlace>(get_base_typespec("integer"), value);
}
/*!
* Compile an integer, but force it to be in a GPR instead of an integer constant.
* This is can be used to save typing out resolve_to_gpr(compile_to_integer(...)) for getting
* integers in gprs.
*/
std::shared_ptr<Place> Goal::compile_integer_to_gpr(int64_t value, std::shared_ptr<GoalEnv> env) {
auto ir = make_unique<IR_LoadInteger>();
ir->s_value = value;
ir->is_signed = true;
if (value >= INT8_MIN) {
ir->size = 1;
} else if (value >= INT16_MIN) {
ir->size = 2;
} else if (value >= INT32_MIN) {
ir->size = 4;
} else {
ir->size = 8;
}
ir->value = env->alloc_reg(get_base_typespec("integer"));
auto result = ir->value;
env->emit(std::move(ir));
return result;
}
/*!
* Get the value of a global symbol by name.
*/
std::shared_ptr<Place> Goal::compile_get_sym_val(const std::string& name,
std::shared_ptr<GoalEnv> env) {
auto existing_symbol =
symbol_types.find(SymbolObject::make_new(goos.reader.symbolTable, name).as_symbol());
if (existing_symbol == symbol_types.end()) {
throw std::runtime_error("The symbol " + name + " was not defined");
}
auto ir = make_unique<IR_GetSymbolValue>();
TypeSpec symbol_type = get_base_typespec("symbol");
ir->symbol = std::make_shared<SymbolPlace>(name, symbol_type);
// make sure to type the result correctly.
ir->dest = env->alloc_reg(existing_symbol->second);
if (is_signed_integer(existing_symbol->second)) {
ir->sext = true;
}
auto result = ir->dest;
env->emit(std::move(ir));
return result;
}
/*!
* Get a global symbol object by name.
*/
std::shared_ptr<Place> Goal::compile_get_sym_obj(const std::string& name,
std::shared_ptr<GoalEnv> env) {
auto ir = make_unique<IR_GetSymbolObj>();
TypeSpec symbol_type = get_base_typespec("symbol");
ir->sym = std::make_shared<SymbolPlace>(name, symbol_type);
ir->dest = env->alloc_reg(symbol_type);
auto dest = ir->dest;
env->emit(std::move(ir));
return dest;
}
/*!
* Does some local environment (mlet, lexical, or global constants) provide a value for this symbol?
* This is used to determine if a function/method call should be tried as a method call.
* Method names win over global symbols, but lose to everything else.
*/
bool Goal::is_local_symbol(Object obj, std::shared_ptr<GoalEnv> env) {
// check in the symbol macro env.
auto mlet_env = get_parent_env_of_type<SymbolMacroEnv>(env);
while (mlet_env) {
if (mlet_env->macros.find(as_symbol_obj(obj)) != mlet_env->macros.end()) {
return true;
}
mlet_env = get_parent_env_of_type<SymbolMacroEnv>(mlet_env->parent);
}
// check lexical
if (env->lexical_lookup(obj))
return true;
// check global constants
if (global_constants.find(as_symbol_obj(obj)) != global_constants.end())
return true;
return false;
}
/*!
* Compile a symbol
*/
std::shared_ptr<Place> Goal::compile_symbol(const Object& form, std::shared_ptr<GoalEnv> env) {
// special case for the none symbol.
if (form.as_symbol()->name == "none") {
return get_none();
}
// first try as a constant defined in a macro env (mlet)
auto mlet_env = get_parent_env_of_type<SymbolMacroEnv>(env);
while (mlet_env) {
auto mlkv = mlet_env->macros.find(form.as_symbol());
if (mlkv != mlet_env->macros.end()) {
return compile_error_guard(mlkv->second, env);
}
mlet_env = get_parent_env_of_type<SymbolMacroEnv>(mlet_env->parent);
}
// try lexical lookup
auto lexical = env->lexical_lookup(form);
if (lexical) {
return lexical;
}
// try as a global constant (defglobalconstant), but make sure we don't have a symbol with the
// same name
auto global_constant = global_constants.find(form.as_symbol());
auto existing_symbol = symbol_types.find(form.as_symbol());
if (global_constant != global_constants.end()) {
// check there is no symbol with the same name
if (existing_symbol != symbol_types.end()) {
throw_compile_error(form,
"symbol is both a runtime symbol and a global constant. Something is "
"likely very wrong.");
}
// got a global constant
return compile_error_guard(global_constant->second, env);
}
// no global constant, make sure we got a global symbol
if (existing_symbol == symbol_types.end()) {
throw_compile_error(form, "The symbol " + symbol_string(form) + " was not defined");
}
// finally as a global symbol
return compile_get_sym_val(form.as_symbol()->name, env);
}
/*!
* Compile a string
*/
std::shared_ptr<Place> Goal::compile_string(const Object& form, std::shared_ptr<GoalEnv> env) {
return compile_string(as_string(form), env);
}
/*!
* Compile a string
*/
std::shared_ptr<Place> Goal::compile_string(const std::string& in, std::shared_ptr<GoalEnv> env) {
auto str = std::make_shared<StaticString>();
str->data = in;
str->segment = get_parent_env_of_type<FunctionEnv>(env)->segment;
auto result = std::make_shared<StaticPlace>(get_base_typespec("string"), str);
env->get_statics().push_back(result);
return result;
}
/*!
* Compile a float
*/
std::shared_ptr<Place> Goal::compile_float(float value, std::shared_ptr<GoalEnv> env) {
auto flt = std::make_shared<StaticFloat>();
flt->as_float = value;
flt->segment = get_parent_env_of_type<FunctionEnv>(env)->segment;
auto result = std::make_shared<StaticPlace>(get_base_typespec("float"), flt);
env->get_statics().push_back(result);
return result;
}
/*!
* Compile a float
*/
std::shared_ptr<Place> Goal::compile_float(const Object& form, std::shared_ptr<GoalEnv> env) {
return compile_float(form.float_obj.value, env);
}
@@ -0,0 +1,285 @@
/*!
* @file GoalCompileControl.cpp
* GOAL Compiler Forms related to controlling the compiler.
*/
#include <unistd.h>
#include <string.h>
#include "Goal.h"
#include "Timer.h"
/*!
* Enter an interactive GOOS REPL
*/
std::shared_ptr<Place> Goal::compile_gs(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)env;
if (rest.type != EMPTY_LIST) {
throw_compile_error(form, "gs form should have no argumetns");
}
goos.execute_repl();
return get_none();
}
/*!
* Exit the compiler when finished compiling the current thing.
*/
std::shared_ptr<Place> Goal::compile_exit(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)env;
if (rest.type != EMPTY_LIST) {
throw_compile_error(form, ":exit form should have no arguments");
}
// so DECI2 doesn't freak out when the connection is dropped.
if (listener.is_connected()) {
listener.send_reset();
}
want_exit = true;
return get_none();
}
/*!
* Compile a file.
*/
std::shared_ptr<Place> Goal::compile_asm_file(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)env;
int i = 0;
std::string filename;
bool load = false;
bool color = false;
bool write = false;
std::vector<std::pair<std::string, float>> timing;
Timer total_timer;
for_each_in_list(rest, [&](Object o) {
if (i == 0) {
filename = as_string(o);
} else {
auto setting = symbol_string(o);
if (setting == ":load") {
load = true;
} else if (setting == ":color") {
color = true;
} else if (setting == ":write") {
write = true;
} else {
throw_compile_error(form, "invalid option " + setting + " in asm-file form");
}
}
i++;
});
Timer reader_timer;
auto code = goos.reader.read_from_file(filename);
timing.emplace_back("read", reader_timer.getMs());
Timer compile_timer;
std::string obj_file_name = basename(filename.c_str());
obj_file_name = obj_file_name.substr(0, obj_file_name.find_last_of('.'));
auto obj_file = compile_object_file(obj_file_name, code);
timing.emplace_back("compile", compile_timer.getMs());
if (color) {
Timer color_timer;
color_object_file(obj_file);
timing.emplace_back("color", color_timer.getMs());
Timer codegen_timer;
auto data = codegen_object_file(obj_file);
timing.emplace_back("codegen", codegen_timer.getMs());
if (load) {
if (listener.is_connected()) {
listener.send_code(data);
} else {
printf("WARNING - couldn't load because listener isn't connected\n");
}
}
if (write) {
auto output_dir = as_string(get_constant_or_error(form, "*compiler-output-path*"));
auto output_name = output_dir + obj_file_name + ".go";
if (!write_to_binary_file(output_name, (void*)data.data(), data.size())) {
printf("WARNING - failed to write output file!\n");
}
}
} else {
if (load) {
printf("WARNING - couldn't load because coloring is not enabled\n");
}
if (write) {
printf("WARNING - couldn't write because coloring is not enabled\n");
}
}
if (truthy(get_config("print-asm-file-time"))) {
for (auto& e : timing) {
printf(" %12s %4.2f\n", e.first.c_str(), e.second);
}
}
return get_none();
}
struct TestResult {
std::string name, note, expected, actual;
bool pass;
};
/*!
* Run the built-in compiler tests
*/
std::shared_ptr<Place> Goal::compile_test(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)form;
(void)rest;
(void)env;
std::vector<TestResult> results;
Timer all_test_timer;
std::string test_prefix = as_string(get_constant_or_error(form, "*goal-test-prefix*"));
auto test_list = get_constant_or_error(form, "*goal-test-files*");
std::vector<std::string> tests;
for_each_in_list(test_list, [&](Object o) { tests.push_back(as_string(o)); });
for (auto& test : tests) {
printf("Run test file %s\n", test.c_str());
listener.clear_pending_incoming();
Timer total_timer;
auto test_file = test_prefix + test;
TestResult result;
result.name = test;
Timer read_timer;
auto test_code = goos.reader.read_from_file(test_file);
auto read_time = read_timer.getMs();
Timer compile_timer;
auto test_ofe = compile_object_file(test_file, test_code);
auto compile_time = compile_timer.getMs();
Timer color_timer;
color_object_file(test_ofe);
auto color_time = color_timer.getMs();
Timer codegen_timer;
auto data = codegen_object_file(test_ofe);
auto codegen_time = codegen_timer.getMs();
Timer listener_send_timer;
if (listener.is_connected()) {
listener.send_code(data);
} else {
result.note = "Listener wasn't connected, so test didn't run";
result.pass = false;
}
auto listener_send_time = listener_send_timer.getMs();
Timer listener_wait_timer;
int retry_count = 0;
while (!listener.has_pending()) {
usleep(1000);
retry_count++;
if (retry_count > 1000 && !listener.is_connected()) {
printf("failed to get a result after a long wait, test has failed.\n");
return get_none();
}
}
auto listener_wait_time = listener_wait_timer.getMs();
Timer check_timer;
auto target_result = listener.pop_pending();
result.actual = target_result;
auto newline_pos = result.actual.find('\n');
if (newline_pos != std::string::npos) {
result.actual = result.actual.substr(0, newline_pos);
}
auto expected = goos.get_object_by_name("*test-expected*");
result.expected = expected.print() + "\n";
if (expected.type == SYMBOL && expected.as_symbol()->name == "automatic-pass") {
result.pass = true;
} else {
int quote_count = 0;
for (uint32_t i = 0; i < result.actual.size(); i++) {
if (result.actual[i] == '"') {
quote_count++;
}
if (quote_count == 2) {
result.actual = result.actual.substr(0, i + 1);
break;
}
}
if (result.actual.back() != '\n')
result.actual.push_back('\n');
result.pass = result.actual == result.expected;
}
results.push_back(result);
auto check_time = check_timer.getMs();
printf("Time Summary:\n");
printf(" read: %.3f ms\n", read_time);
printf(" compile: %.3f ms\n", compile_time);
printf(" color: %.3f ms\n", color_time);
printf(" codegen: %.3f ms\n", codegen_time);
printf(" send: %.3f ms\n", listener_send_time);
printf(" run: %.3f ms\n", listener_wait_time);
printf(" check: %.3f ms\n", check_time);
printf(" total: %.3f ms\n", total_timer.getMs());
}
bool all_pass = true;
for (auto& result : results) {
printf("TEST %s\n", result.name.c_str());
printf(" expected (%02ld): %s", result.expected.size(), result.expected.c_str());
printf(" actual (%02ld): %s", result.actual.size(), result.actual.c_str());
if (!result.note.empty()) {
printf(" note: %s\n", result.note.c_str());
}
if (result.pass) {
printf(" OK\n");
} else {
printf(" NG\n");
all_pass = false;
}
}
if (all_pass) {
printf("All %ld tests pass in %.4f seconds!\n", tests.size(), all_test_timer.getSeconds());
} else {
printf("Failed tests:\n");
for (auto& x : results) {
if (!x.pass)
printf(" %s\n", x.name.c_str());
}
}
return get_none();
}
/*!
* This ignores (in-package goal) at the top of GOAL files. Real GOAL did this, so we do too.
*/
std::shared_ptr<Place> Goal::compile_in_package(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)form;
(void)rest;
(void)env;
return get_none();
}
@@ -0,0 +1,93 @@
/*!
* @file GoalConditionalCompilation.cpp
*
* Compiler forms to omit certain forms from compilation in certain cases.
*/
#include "Goal.h"
/*!
* A "cond" form evaluated at compile time using GOOS conditions.
*/
std::shared_ptr<Place> Goal::compile_gscond(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
if (rest.type != PAIR)
throw_compile_error(form, "#cond must have at least one clause, which must be a form");
Object lst = rest;
for (;;) {
if (lst.type == PAIR) {
Object current_case = lst.as_pair()->car;
if (current_case.type != PAIR)
throw_compile_error(lst, "Bad case in #cond");
// check condition:
Object condition_result =
goos.eval_with_rewind(current_case.as_pair()->car, goos.global_environment.as_env());
if (truthy(condition_result)) {
if (current_case.as_pair()->cdr.type == EMPTY_LIST) {
return get_none();
}
// got a match!
auto result = get_none();
for_each_in_list(current_case.as_pair()->cdr,
[&](Object o) { result = compile_error_guard(o, env); });
return result;
} else {
// no match, continue.
lst = lst.as_pair()->cdr;
}
} else if (lst.type == EMPTY_LIST) {
return get_none();
} else {
throw_compile_error(form, "malformed #cond");
}
}
}
/*!
* Evaluate GOOS at compile time.
*/
std::shared_ptr<Place> Goal::compile_seval(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)env;
try {
for_each_in_list(rest,
[&](Object o) { goos.eval_with_rewind(o, goos.global_environment.as_env()); });
} catch (std::runtime_error& e) {
throw_compile_error(form, std::string("seval error: ") + e.what());
}
return get_none();
}
/*!
* Define a GOAL and GOOS constant.
*/
std::shared_ptr<Place> Goal::compile_defglobalconstant(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)env;
if (rest.type != PAIR) {
throw_compile_error(form, "invalid defglobalconstant");
}
auto sym = as_symbol_obj(pair_car(rest));
rest = pair_cdr(rest);
auto value = pair_car(rest);
rest = rest.as_pair()->cdr;
if (rest.type != EMPTY_LIST) {
throw_compile_error(form, "invalid defglobalconstant");
}
// GOAL constant
global_constants[sym] = value;
// GOOS constant
goos.global_environment.as_env()->vars[sym] = value;
return get_none();
}
+455
View File
@@ -0,0 +1,455 @@
/*!
* @file GoalConstProp.cpp
* Utility functions to deal with constant propagation, including integers, memory addresses,
* address of, and resolving various optimized const-prop Places into actual register Places.
*/
#include <util.h>
#include "Goal.h"
/*!
* Convert a BitField place into a gpr.
* Return nullptr if this cannot be done.
*/
std::shared_ptr<Place> Goal::try_resolve_bitfield_to_gpr(std::shared_ptr<Place> in,
std::shared_ptr<GoalEnv> env) {
auto as_bitfield = std::dynamic_pointer_cast<BitfieldPlace>(in);
if (as_bitfield) {
return resolve_bitfield_to_gpr(as_bitfield, env);
}
return nullptr;
}
/*!
* Convert a static (or address of a static) into a GPR.
* Return nullptr if this cannot be done, or if "in" is not a static.
*/
std::shared_ptr<Place> Goal::try_resolve_static_to_gpr(std::shared_ptr<Place> in,
std::shared_ptr<GoalEnv> env) {
auto as_static = std::dynamic_pointer_cast<StaticPlace>(in);
if (as_static) {
if (as_static->object->load_size() == -1) {
// not a value type
auto ir = make_unique<IR_StaticVarAddr>();
ir->dest = env->alloc_reg(as_static->type);
ir->src = as_static;
auto dest = ir->dest;
env->emit(std::move(ir));
auto result_as_gpr = std::dynamic_pointer_cast<GprPlace>(dest);
assert(result_as_gpr);
return result_as_gpr;
} else if (as_static->object->load_size() == 4) {
// is a value type of size 4.
auto ir = make_unique<IR_StaticVar32>();
ir->dest = env->alloc_reg(as_static->type);
ir->src = as_static;
auto dest = ir->dest;
env->emit(std::move(ir));
auto result_as_gpr = std::dynamic_pointer_cast<GprPlace>(dest);
assert(result_as_gpr);
return result_as_gpr;
} else {
// unhandled value type size - TODO!
ice("unhandled value type size in Goal::try_resolve_static_to_gpr");
assert(false);
return nullptr;
}
} else {
return nullptr;
}
}
/*!
* Convert a lambda into a GPR containing the function address.
* Return nullptr if this cannot be done, or if "in" is not a lambda.
*/
std::shared_ptr<Place> Goal::try_resolve_lambda_to_gpr(std::shared_ptr<Place> in,
std::shared_ptr<GoalEnv> env) {
auto as_func = std::dynamic_pointer_cast<LambdaPlace>(in);
if (as_func) {
auto ir = make_unique<IR_FunctionAddr>();
ir->dest = env->alloc_reg(as_func->type);
ir->src = as_func;
auto dest = ir->dest;
env->emit(std::move(ir));
auto result_as_gpr = std::dynamic_pointer_cast<GprPlace>(dest);
assert(result_as_gpr);
return result_as_gpr;
} else {
return nullptr;
}
}
/*!
* Convert an XMM register into a GPR.
* Return nullptr if this cannot be done, or if "in" is not an xmm register.
*/
std::shared_ptr<Place> Goal::try_resolve_xmm_to_gpr(std::shared_ptr<Place> in,
std::shared_ptr<GoalEnv> env) {
auto as_xmm = std::dynamic_pointer_cast<XmmPlace>(in);
if (as_xmm) {
auto ir = make_unique<IR_Set>();
ir->dest = env->alloc_reg(as_xmm->type);
ir->src = as_xmm;
auto dest = ir->dest;
env->emit(std::move(ir));
auto result_as_gpr = std::dynamic_pointer_cast<GprPlace>(dest);
assert(result_as_gpr);
return result_as_gpr;
} else {
return nullptr;
}
}
/*!
* Convert a memory place into a GPR containing the value.
* Return nullptr if this cannot be done, or if "in" is not a memory place.
*/
std::shared_ptr<Place> Goal::try_resolve_mem_to_gpr(std::shared_ptr<Place> in,
std::shared_ptr<GoalEnv> env) {
auto as_mem_deref = std::dynamic_pointer_cast<MemoryDerefPlace>(in);
if (as_mem_deref) {
auto ptr = as_mem_deref->base;
// optimization!
auto ptr_as_mem_c_offset = std::dynamic_pointer_cast<MemoryOffsetConstPlace>(ptr);
if (ptr_as_mem_c_offset) {
auto ir = make_unique<IR_LoadConstOffset>(
env->alloc_reg(as_mem_deref->type), resolve_to_gpr(ptr_as_mem_c_offset->base, env),
ptr_as_mem_c_offset->offset, as_mem_deref->type.type->load_size,
as_mem_deref->type.type->load_signed);
auto dest = ir->dst;
env->emit(std::move(ir));
return dest;
}
// no optimization is implemented yet.
ptr = resolve_to_gpr(ptr, env);
auto ir = make_unique<IR_LoadConstOffset>(env->alloc_reg(as_mem_deref->type), ptr,
0, // todo, optimize for this case
as_mem_deref->type.type->load_size,
as_mem_deref->type.type->load_signed);
auto dest = ir->dst;
env->emit(std::move(ir));
return dest;
}
auto as_pair_ref = std::dynamic_pointer_cast<PairPlace>(in);
if (as_pair_ref) {
auto ptr = as_pair_ref->base;
ptr = resolve_to_gpr(ptr, env);
auto ir = make_unique<IR_LoadConstOffset>(
env->alloc_reg(as_pair_ref->type), ptr, as_pair_ref->is_car ? -2 : 2, 4,
false); // todo, do we really want a signed load here?
auto dest = ir->dst;
env->emit(std::move(ir));
return dest;
}
auto as_mem_c_offset = std::dynamic_pointer_cast<MemoryOffsetConstPlace>(in);
if (as_mem_c_offset) {
auto base = resolve_to_gpr(as_mem_c_offset->base, env);
auto result = compile_integer_to_gpr(as_mem_c_offset->offset, env);
env->emit(make_unique<IR_IntegerMath>(ADD_64, result, base));
result->type = in->type;
return result;
}
auto as_mem_v_offset = std::dynamic_pointer_cast<MemoryOffsetVarPlace>(in);
if (as_mem_v_offset) {
auto base = resolve_to_gpr(as_mem_v_offset->base, env);
auto offset = resolve_to_gpr(as_mem_v_offset->offset, env);
auto result = env->alloc_reg(in->type);
env->emit(make_unique<IR_Set>(result, base));
env->emit(make_unique<IR_IntegerMath>(ADD_64, result, offset));
result->type = in->type;
return result;
}
return nullptr;
}
/*!
* Convert a static to an xmm register.
* Return nullptr if this cannot be done, or if "in" is not a static.
*/
std::shared_ptr<Place> Goal::try_resolve_static_to_xmm(std::shared_ptr<Place> in,
std::shared_ptr<GoalEnv> env) {
auto as_static = std::dynamic_pointer_cast<StaticPlace>(in);
if (as_static) {
if (as_static->object->load_size() == 4) {
auto fenv = get_parent_env_of_type<FunctionEnv>(env);
auto ir = make_unique<IR_StaticVar32>();
ir->dest = fenv->alloc_xmm_reg(as_static->type);
ir->src = as_static;
auto dest = ir->dest;
env->emit(std::move(ir));
auto result_as_xmm = std::dynamic_pointer_cast<XmmPlace>(dest);
assert(result_as_xmm);
return result_as_xmm;
} else {
assert(false);
return nullptr;
}
} else {
return nullptr;
}
}
/*!
* Convert a gpr to an xmm register.
* Return nullptr if this cannot be done, or if "in" is not a gpr.
*/
std::shared_ptr<Place> Goal::try_resolve_gpr_to_xmm(std::shared_ptr<Place> in,
std::shared_ptr<GoalEnv> env) {
auto as_gpr = std::dynamic_pointer_cast<GprPlace>(in);
if (as_gpr) {
auto fenv = get_parent_env_of_type<FunctionEnv>(env);
auto ir = make_unique<IR_Set>();
ir->dest = fenv->alloc_xmm_reg(as_gpr->type);
ir->src = as_gpr;
auto dest = ir->dest;
env->emit(std::move(ir));
auto result_as_xmm = std::dynamic_pointer_cast<XmmPlace>(dest);
assert(result_as_xmm);
return result_as_xmm;
} else {
return nullptr;
}
}
/*!
* Convert an integer constant to gpr, if possible.
*/
std::shared_ptr<Place> Goal::try_resolve_integer_to_gpr(std::shared_ptr<Place> in,
std::shared_ptr<GoalEnv> env) {
auto as_integer = std::dynamic_pointer_cast<IntegerConstantPlace>(in);
if (as_integer) {
auto result = compile_integer_to_gpr(as_integer->value, env);
result->type = as_integer->type;
return result;
}
return nullptr;
}
/*!
* Convert a Place to a GPR place.
* Except for NonePlace, which remains as none.
* Error if cannot be converted to a gpr place.
*/
std::shared_ptr<Place> Goal::resolve_to_gpr(std::shared_ptr<Place> in,
std::shared_ptr<GoalEnv> env) {
// is it an alias, but maybe not a GPR alias?
auto as_alias = std::dynamic_pointer_cast<AliasPlace>(in);
if (as_alias) {
auto result = resolve_to_gpr(as_alias->base, env);
return std::make_shared<GprAliasPlace>(result, as_alias->type);
}
// is it an alias
auto as_gpr_alias = std::dynamic_pointer_cast<GprAliasPlace>(in);
if (as_gpr_alias) {
return as_gpr_alias;
}
auto as_xmm_alias = std::dynamic_pointer_cast<XmmAliasPlace>(in);
if (as_xmm_alias) {
auto result = resolve_to_gpr(as_xmm_alias->parent, env);
return std::make_shared<GprAliasPlace>(result, as_xmm_alias->type);
}
// is it already a GPR?
auto as_gpr = std::dynamic_pointer_cast<GprPlace>(in);
if (as_gpr)
return as_gpr;
// try as a static
auto as_static = try_resolve_static_to_gpr(in, env);
if (as_static)
return as_static;
// try as a lambda
auto as_lambda = try_resolve_lambda_to_gpr(in, env);
if (as_lambda)
return as_lambda;
// try as xmm register
auto as_xmm = try_resolve_xmm_to_gpr(in, env);
if (as_xmm)
return as_xmm;
// try as memory
auto as_mem = try_resolve_mem_to_gpr(in, env);
if (as_mem)
return as_mem;
// try as none
auto as_none = std::dynamic_pointer_cast<NonePlace>(in);
if (as_none)
return as_none;
// try as int
auto as_int = try_resolve_integer_to_gpr(in, env);
if (as_int)
return as_int;
auto as_bitfield = try_resolve_bitfield_to_gpr(in, env);
if (as_bitfield) {
return as_bitfield;
}
throw std::runtime_error("unable to resolve " + in->print() + " to gpr");
}
/*!
* Convert a place to an XMM place.
* If it is not possible, throw compiler error./
*/
std::shared_ptr<Place> Goal::resolve_to_xmm(std::shared_ptr<Place> in,
std::shared_ptr<GoalEnv> env) {
auto as_alias = std::dynamic_pointer_cast<AliasPlace>(in);
if (as_alias) {
auto result = resolve_to_xmm(as_alias->base, env);
return std::make_shared<XmmAliasPlace>(result, as_alias->type);
}
auto as_xmm_alias = std::dynamic_pointer_cast<XmmAliasPlace>(in);
if (as_xmm_alias) {
return as_xmm_alias;
}
// is it already an xmm?
auto as_xmm = std::dynamic_pointer_cast<XmmPlace>(in);
if (as_xmm)
return as_xmm;
// try as a static
auto as_static = try_resolve_static_to_xmm(in, env);
if (as_static)
return as_static;
// try as a gpr
auto as_gpr = try_resolve_gpr_to_xmm(in, env);
if (as_gpr)
return as_gpr;
auto conv_gpr = resolve_to_gpr(in, env);
if (conv_gpr) {
as_gpr = try_resolve_gpr_to_xmm(conv_gpr, env);
if (as_gpr)
return as_gpr;
}
throw std::runtime_error("unable to resolve " + in->print() + " to xmm");
}
/*!
* Convert a place to an XMM or a GPR.
* This is designed for the case where you don't yet know which one you want.
* It prefers doing nothing over converting
* It prefers loading functions into GPRs
* It will prefer statics which mark themselves as prefer xmm as loading into xmms
* It will prefer GPRs over XMMs in cases where there is no clear winner.
* Memory loads are always into gprs currently.
*/
std::shared_ptr<Place> Goal::resolve_to_gpr_or_xmm(std::shared_ptr<Place> in,
std::shared_ptr<GoalEnv> env) {
auto as_alias = std::dynamic_pointer_cast<AliasPlace>(in);
if (as_alias) {
auto result = resolve_to_gpr_or_xmm(as_alias->base, env);
if (std::dynamic_pointer_cast<XmmPlace>(result) ||
std::dynamic_pointer_cast<XmmAliasPlace>(result)) {
return std::make_shared<XmmAliasPlace>(result, as_alias->type);
} else {
return std::make_shared<GprAliasPlace>(result, as_alias->type);
}
// result->type = as_alias->type;
return result;
}
auto as_xmm = std::dynamic_pointer_cast<XmmPlace>(in);
if (as_xmm) {
return as_xmm;
}
auto as_gpr = std::dynamic_pointer_cast<GprPlace>(in);
if (as_gpr)
return as_gpr;
auto as_lambda = try_resolve_lambda_to_gpr(in, env);
if (as_lambda)
return as_lambda;
// for statics, we can do either gpr or xmm:
if (in->type.type->load_xmm_32_prefer) {
auto as_static_xmm = try_resolve_static_to_xmm(in, env);
if (as_static_xmm)
return as_static_xmm;
// todo try resolve memory to xmm.
}
// try as a static
auto as_static = try_resolve_static_to_gpr(in, env);
if (as_static)
return as_static;
// try as memory
auto as_mem = try_resolve_mem_to_gpr(in, env);
if (as_mem)
return as_mem;
// try as integer
auto as_int = try_resolve_integer_to_gpr(in, env);
if (as_int)
return as_int;
auto as_bitfield = try_resolve_bitfield_to_gpr(in, env);
if (as_bitfield)
return as_bitfield;
throw std::runtime_error("unable to resolve " + in->print() + " to gpr/xmm");
}
/*!
* Try to take the address of a place. Only works if its a MemoryDerefPlace
*/
std::shared_ptr<Place> Goal::addr_of(std::shared_ptr<Place> plc, std::shared_ptr<GoalEnv> env) {
(void)env;
auto x = std::dynamic_pointer_cast<MemoryDerefPlace>(plc);
if (!x) {
throw std::runtime_error("cannot take the address of " + plc->print());
}
return x->base;
}
/*!
* Attempt to compile an expression into an integer constant.
* Errors if it doesn't get an integer constant, or if the comilation requires code to be executed.
*/
int64_t Goal::compile_to_integer_constant(Object form, std::shared_ptr<GoalEnv> env) {
auto new_env = std::make_shared<NoEmitEnv>();
new_env->parent = env;
auto result = compile_error_guard(form, new_env);
auto as_int_constant = std::dynamic_pointer_cast<IntegerConstantPlace>(result);
if (!as_int_constant) {
throw_compile_error(form, "unable to convert to integer constant!");
}
return as_int_constant->value;
}
/*!
* Try to convert a place to an integer constant. If successful, return true and set out.
* Otherwise return false.
*/
bool Goal::try_converting_to_integer_constant(Place& in, int64_t* out) {
auto as_integer_constant = dynamic_cast<IntegerConstantPlace*>(&in);
if (as_integer_constant) {
*out = as_integer_constant->value;
return true;
} else {
return false;
}
}
+258
View File
@@ -0,0 +1,258 @@
/*!
* @file GoalControlFlow.cpp
* Branching control flow implementation.
* Contains "cond", the only control flow structure known to the compiler, and branch condition
* optimizations.
*/
#include "Goal.h"
#include "util.h"
/*!
* Convert a condition expression into a GoalCondition for use in a conditional branch.
* The reason for this design is to allow an optimization for
* (if (< a b) ...) to be compiled without actually computing a true/false value for the (< a b)
* expression. Instead, it will generate a cmp + jle sequence of instructions, which is much faster.
*
* This can be applied to _any_ GOAL form, and will return a GoalCondition which can be used with a
* Branch IR to branch if the condition is true/false. When possible it applies the optimization
* mentioned above, but will be fine in other cases too. I believe the original GOAL compiler had a
* similar system.
*
* Will branch if the condition is true and the invert flag is false.
* Will branch if the condition is false and the invert flag is true.
*/
GoalCondition Goal::compile_condition(Object condition, std::shared_ptr<GoalEnv> env, bool invert) {
GoalCondition gc;
// These are special conditions that can be optimized into a cmp + jxx instruction.
const std::unordered_map<std::string, ConditionKind> conditions_inverted = {
{"!=", ConditionKind::EQUAL_64}, {"eq?", ConditionKind::NOT_EQUAL_64},
{"neq?", ConditionKind::EQUAL_64}, {"=", ConditionKind::NOT_EQUAL_64},
{">", ConditionKind::LEQ_64}, {"<", ConditionKind::GEQ_64},
{">=", ConditionKind::LT_64}, {"<=", ConditionKind::GT_64}};
const std::unordered_map<std::string, ConditionKind> conditions_normal = {
{"!=", ConditionKind::NOT_EQUAL_64}, {"eq?", ConditionKind::EQUAL_64},
{"neq?", ConditionKind::NOT_EQUAL_64}, {"=", ConditionKind::EQUAL_64},
{">", ConditionKind::GT_64}, {"<", ConditionKind::LT_64},
{">=", ConditionKind::GEQ_64}, {"<=", ConditionKind::LEQ_64}};
// possibly a form with an optimizable condition?
if (condition.type == PAIR) {
auto first = pair_car(condition);
auto rest = pair_cdr(condition);
if (first.type == SYMBOL) {
auto fas = first.as_symbol();
// if there's a not, we can just try again to get an optimization with the invert flipped.
if (fas->name == "not") {
auto arg = pair_car(rest);
if (pair_cdr(rest).type != EMPTY_LIST) {
throw_compile_error(condition, "A condition with \"not\" can have only one argument");
}
return compile_condition(arg, env, !invert);
}
auto& conditions = invert ? conditions_inverted : conditions_normal;
auto nc_kv = conditions.find(fas->name);
if (nc_kv != conditions.end()) {
// it is an optimizable condition!
gc.kind = nc_kv->second;
// get args...
auto args = goos.get_uneval_args_no_rest(rest, rest, 2);
if (!args.named_args.empty() || args.unnamed_args.size() != 2) {
throw_compile_error(rest, "invalid arguments to " + nc_kv->first);
}
auto first_arg = compile_error_guard(args.unnamed_args.at(0), env);
auto second_arg = compile_error_guard(args.unnamed_args.at(1), env);
if (is_number(first_arg->type)) {
// it's a numeric comparison, so we may need to coerce.
// there is no support for comparing bintegers, so we turn the binteger comparison into an
// integer.
if (is_binteger(first_arg->type)) {
first_arg = to_integer(first_arg, env);
}
// convert second one to appropriate type as needed
if (is_number(second_arg->type)) {
second_arg = to_same_numeric_type(second_arg, first_arg->type, env);
}
}
// use signed comparison only if first argument is a signed integer (or coerced binteger)
// (floating point ignores this)
gc.is_signed = is_signed_integer(first_arg->type);
// pick between a floating point and an integer comparison.
if (is_float(first_arg->type)) {
gc.a = resolve_to_xmm(first_arg, env);
gc.b = resolve_to_xmm(second_arg, env);
gc.is_float = true;
} else {
gc.a = resolve_to_gpr(first_arg, env);
gc.b = resolve_to_gpr(second_arg, env);
}
return gc;
}
}
}
// not something we can optimize. Just check if we get false.
// todo - it's possible to optimize a false comparison because the false offset is zero
gc.kind = invert ? EQUAL_64 : NOT_EQUAL_64;
gc.a = resolve_to_gpr(compile_error_guard(condition, env), env);
gc.b = compile_get_sym_obj("#f", env);
return gc;
}
/*!
* In the event that we have an expression like (< 1 2) that's _not_ a branch condition,
* we can reuse the logic of the above comparison, and just set up an (if cond #t #f)-like program.
*/
std::shared_ptr<Place> Goal::compile_condition_as_bool(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)rest;
auto c = compile_condition(form, env, true);
auto result = compile_get_sym_obj("#f", env); // todo - can be optimized.
auto branch_ir = make_unique<IR_ConditionalBranch>();
auto branch_ir_ref = branch_ir.get();
branch_ir->cond = c;
branch_ir->label = std::make_shared<Label>();
branch_ir->label->func = get_parent_env_of_type<FunctionEnv>(env).get();
branch_ir->label->idx = -5; // placeholder
branch_ir->resolved = true;
env->emit(std::move(branch_ir));
// move true
env->emit(make_unique<IR_Set>(result, compile_get_sym_obj("#t", env)));
branch_ir_ref->label->idx = branch_ir_ref->label->func->code.size();
return result;
}
std::shared_ptr<Place> Goal::compile_when_goto(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)form;
auto condition_code = pair_car(rest);
rest = pair_cdr(rest);
auto label = symbol_string(pair_car(rest));
expect_empty_list(pair_cdr(rest));
// compile as condition (will set flags register with a cmp instruction)
auto condition = compile_condition(condition_code, env, false);
auto branch = make_unique<IR_ConditionalBranch>();
branch->cond = condition;
branch->label = nullptr; // will be resolved later
branch->resolved = false;
get_parent_env_of_type<FunctionEnv>(env)->unresolved_cond_gotos.push_back({branch.get(), label});
env->emit(std::move(branch));
return get_none();
}
/*!
* The scheme "cond" statement.
*/
std::shared_ptr<Place> Goal::compile_cond(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
auto result = env->alloc_reg(get_base_typespec("object"));
auto end_label = std::make_shared<Label>();
auto fenv = get_parent_env_of_type<FunctionEnv>(env).get();
end_label->func = fenv;
end_label->idx = -3; // placeholder
bool got_else = false;
std::vector<TypeSpec> case_result_types;
for_each_in_list(rest, [&](Object o) {
auto test = pair_car(o);
auto clauses = pair_cdr(o);
if (got_else) {
throw_compile_error(form, "cannot have anything after an else in a cond");
}
if (test.type == SYMBOL && symbol_string(test) == "else") {
got_else = true;
}
if (got_else) {
// just set the output to this.
auto case_result = get_none();
for_each_in_list(clauses,
[&](Object clause) { case_result = compile_error_guard(clause, env); });
case_result_types.push_back(case_result->type);
// optimization - if we get junk, don't bother moving it, just leave junk in return.
if (!is_none(case_result)) {
env->emit(make_unique<IR_Set>(result, resolve_to_gpr(case_result, env)));
}
} else {
// CONDITION CHECK
auto condition = compile_condition(test, env, true);
// BRANCH FWD
auto branch_ir = make_unique<IR_ConditionalBranch>();
auto branch_ir_ref = branch_ir.get();
branch_ir->cond = condition;
branch_ir->label = std::make_shared<Label>();
branch_ir->label->func = fenv;
branch_ir->label->idx = -2; // temporary placeholder
branch_ir->resolved = true;
env->emit(std::move(branch_ir));
// CODE
auto case_result = get_none();
for_each_in_list(clauses,
[&](Object clause) { case_result = compile_error_guard(clause, env); });
case_result_types.push_back(case_result->type);
if (!is_none(case_result)) {
env->emit(make_unique<IR_Set>(result, resolve_to_gpr(case_result, env)));
}
// GO TO END
auto ir_goto_end = make_unique<IR_Goto_Label>();
ir_goto_end->resolved = true;
ir_goto_end->label = end_label;
env->emit(std::move(ir_goto_end));
// PATCH BRANCH FWD
branch_ir_ref->label->idx = fenv->code.size();
}
});
if (!got_else) {
// if no else, clause, return #f. But don't retype. I don't know how I feel about this typing
// setup.
auto get_false = make_unique<IR_GetSymbolObj>();
auto sym_ts = get_base_typespec("symbol");
get_false->sym = std::make_shared<SymbolPlace>("#f", sym_ts);
get_false->dest = result;
env->emit(std::move(get_false));
}
result->type = lowest_common_ancestor(case_result_types);
// PATCH END
end_label->idx = fenv->code.size();
return result;
}
// TODO - move optimized and/ors into the compiler?
+615
View File
@@ -0,0 +1,615 @@
#include "logger/Logger.h"
#include "Goal.h"
#include "util.h"
/*!
* Helper to get parent type.
*/
static std::string deftype_parent_list(Object& list) {
if (list.type != PAIR) {
throw std::runtime_error("invalid parent list in deftype");
}
auto parent = list.as_pair()->car;
auto rest = list.as_pair()->cdr;
if (rest.type != EMPTY_LIST) {
throw std::runtime_error("invalid parent list in deftype - can only have one parent");
}
if (parent.type != SYMBOL) {
throw std::runtime_error("invalid parent in deftype parent list");
}
return parent.as_symbol()->name;
}
/*!
* Compile type definition.
*/
std::shared_ptr<Place> Goal::compile_deftype(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
auto args = goos.get_uneval_args(form, rest, 3);
if (args.unnamed_args.size() != 3 || !args.named_args.empty()) {
throw_compile_error(form, "invalid deftype");
}
auto type_name_obj = args.unnamed_args.at(0);
auto parent_list_obj = args.unnamed_args.at(1);
auto field_list_obj = args.unnamed_args.at(2);
auto options_obj = args.rest;
if (type_name_obj.type != SYMBOL) {
throw_compile_error(form, "invalid deftype type name");
}
auto name = type_name_obj.as_symbol()->name;
auto parent_type = get_base_typespec(deftype_parent_list(parent_list_obj));
// first - determine if we are a structure, basic, or bitfield
if (get_base_typespec("basic").typecheck_base_only(parent_type, types)) {
///////////////////////
///// BASIC
//////////////////////
std::shared_ptr<BasicType> new_type;
// if we already have this type defined, get the old one and warn.
auto existing_type_kv = types.types.find(name);
if (existing_type_kv != types.types.end()) {
if (name != "thread") { ////////////////// HACK!
// to silence the warning on the redefinition of the "thread" type, which is a "default"
// type.
gLogger.log(MSG_WARN,
"[Warning] type %s has been redefined and may have changed data layout\n",
name.c_str());
// todo - check if a change actually occurs and silence the warning if no change.
}
new_type = std::dynamic_pointer_cast<BasicType>(existing_type_kv->second);
assert(new_type);
} else {
new_type =
std::make_shared<BasicType>(-1, parent_type.type, name, get_base_typespec("type").type);
}
auto parent_as_structure = std::dynamic_pointer_cast<StructureType>(parent_type.type);
assert(parent_as_structure);
new_type->inherit_fields(parent_as_structure);
types.inherit_methods(parent_type.type->get_name(), name);
return deftype_structure(new_type, field_list_obj, options_obj, env);
} else if (get_base_typespec("structure").typecheck_base_only(parent_type, types)) {
///////////////////////
///// STRUCTURE
//////////////////////
std::shared_ptr<StructureType> new_type;
auto existing_type_kv = types.types.find(name);
if (existing_type_kv != types.types.end()) {
new_type = std::dynamic_pointer_cast<StructureType>(existing_type_kv->second);
assert(new_type);
if (name != "connectable") { //////// HACK!
gLogger.log(MSG_WARN,
"[Warning] type %s has been redefined and may have changed data layout\n",
name.c_str());
// todo - check if a change actually occurs and silence the warning if no change.
}
} else {
new_type = std::make_shared<StructureType>(-1, parent_type.type, name);
}
auto parent_as_structure = std::dynamic_pointer_cast<StructureType>(parent_type.type);
assert(parent_as_structure);
new_type->inherit_fields(parent_as_structure);
types.inherit_methods(parent_type.type->get_name(), name);
return deftype_structure(new_type, field_list_obj, options_obj, env);
} else if (get_base_typespec("integer").typecheck_base_only(parent_type, types)) {
///////////////////////
///// BITFIELD
//////////////////////
assert(parent_type.type->is_value_type);
assert(parent_type.type->load_size == parent_type.type->size);
if (parent_type.type->size <= 8) {
std::shared_ptr<BitfieldType> new_type;
auto existing_type_kv = types.types.find(name);
if (existing_type_kv != types.types.end()) {
printf("existing bitfield type\n");
new_type = std::dynamic_pointer_cast<BitfieldType>(existing_type_kv->second);
if (!new_type) {
throw_compile_error(form,
"invalid attempt to change type " + name + " to a bitfield type!");
}
} else {
new_type = std::make_shared<BitfieldType>(parent_type.type, name);
}
types.inherit_methods(parent_type.type->get_name(), name);
return deftype_bitfield(new_type, field_list_obj, options_obj, env);
} else {
ice("large bitfields not supported yet");
}
} else {
throw_compile_error(form, "unknown parent type kind " + parent_type.print());
}
return get_none();
}
/*!
* Parse the definition of a field for a bit field.
* NOTE: this needs the "env" in case the user sets the size of a bit field to a _CONSTANT_
* (name type [:size sz] [:offset off] [:offset-assert assert])
* @param def
* @return
*/
BitFieldDefinition Goal::parse_bit_field_def(const Object& def, std::shared_ptr<GoalEnv> env) {
BitFieldDefinition bfd;
Object rest = def;
// name
bfd.name = symbol_string(pair_car(rest));
rest = pair_cdr(rest);
// type
bfd.type = compile_typespec(pair_car(rest));
rest = pair_cdr(rest);
// options
while (rest.type != EMPTY_LIST) {
auto option_name = symbol_string(pair_car(rest));
rest = pair_cdr(rest);
if (option_name == ":offset") {
bfd.offset_override = compile_to_integer_constant(pair_car(rest), env);
rest = pair_cdr(rest);
} else if (option_name == ":offset-assert") {
bfd.offset_assert = compile_to_integer_constant(pair_car(rest), env);
rest = pair_cdr(rest);
} else if (option_name == ":size") {
bfd.size = compile_to_integer_constant(pair_car(rest), env);
rest = pair_cdr(rest);
} else {
throw_compile_error(def, "unknown option in bit field definition: " + option_name);
}
}
// if user didn't set the size, we should set it ourself.
if (bfd.size == -1) {
bfd.size = bfd.type.type->get_size_in_non_inline_array() * BITS_PER_BYTE;
}
return bfd;
}
/*!
* Parse the definition of a field for a struct/basic field.
*/
StructFieldDefinition Goal::parse_struct_field_def(const Object& def) {
StructFieldDefinition sfd;
Object rest = def;
// first is name
sfd.name = symbol_string(pair_car(rest));
rest = pair_cdr(rest);
// next is type
sfd.type = compile_typespec(pair_car(rest));
rest = pair_cdr(rest);
if (rest.type == EMPTY_LIST)
return sfd;
// is it array size?
if (pair_car(rest).type == INTEGER) {
sfd.array_size = pair_car(rest).integer_obj.value;
rest = pair_cdr(rest);
}
while (rest.type != EMPTY_LIST) {
auto name = symbol_string(pair_car(rest));
rest = pair_cdr(rest);
if (name == ":inline") {
auto val = symbol_string(pair_car(rest));
rest = pair_cdr(rest);
if (val == "#t") {
sfd.is_inline = true;
} else if (val == "#f") {
sfd.is_inline = false;
} else {
throw_compile_error(def, "inline value must be #t or #f");
}
} else if (name == ":offset-assert") {
if (pair_car(rest).type != INTEGER) {
throw_compile_error(def, "offset assert must be an integer");
}
sfd.offset_assert = pair_car(rest).integer_obj.value;
rest = pair_cdr(rest);
} else if (name == ":offset") {
if (pair_car(rest).type != INTEGER) {
throw_compile_error(def, "offset must be an integer");
}
sfd.offset_override = pair_car(rest).integer_obj.value;
rest = pair_cdr(rest);
} else if (name == ":dynamic") {
sfd.is_dynamic = true;
}
else {
throw_compile_error(def, "unknown field spec " + name);
}
}
return sfd;
}
/*!
* Get the size of a field in a structure type.
* For arrays, it will return the full size of the array, including padding in between elements.
*/
int Goal::get_size_in_type(GoalField& f) {
if (f.is_dynamic) {
return 0;
}
if (f.is_array) {
if (f.is_inline) {
// inline array
assert(!f.type.type->is_value_type); // not valid, arrays of value type are always "inline"
return f.type.type->get_size_in_inline_array() * f.array_size;
} else {
return f.type.type->get_size_in_non_inline_array() * f.array_size;
}
} else {
// not an array
if (f.type.type->is_value_type) {
// can't be inline
assert(!f.is_inline);
return f.type.type->size;
} else {
// is a reference type
if (f.is_inline) {
// but is inline
return f.type.type->size;
} else {
// but is a reference
return PTR_SIZE;
}
}
}
}
/*!
* Get the required alignment of a type as a field.
*/
static int get_alignment_in_type(TypeSpec& ts, bool in_inline) {
if (in_inline || ts.type->is_value_type) {
return ts.type->minimum_alignment;
}
// otherwise it's a reference.
return PTR_SIZE;
}
// packed type flags for the runtime.
struct TypeFlags {
union {
uint64_t flag;
struct {
uint16_t size;
uint16_t heap_base;
uint16_t methods;
uint16_t pad;
};
};
};
/*!
* Helper to process the :method option to forward declare methods.
* Shared between the bitfield and structure type definitions.
*/
void Goal::deftype_methods_helper(Object form,
std::shared_ptr<GoalType> new_type,
std::shared_ptr<GoalEnv> env) {
for_each_in_list(form, [&](Object obj) {
// (name args return-type [id])
auto method_name = symbol_string(pair_car(obj));
obj = pair_cdr(obj);
auto args = pair_car(obj);
obj = pair_cdr(obj);
auto return_type = (pair_car(obj));
obj = pair_cdr(obj);
int id = -1;
if (obj.type != EMPTY_LIST) {
auto id_obj = pair_car(obj);
id = compile_to_integer_constant(id_obj, env);
expect_empty_list(pair_cdr(obj));
}
TypeSpec ts = get_base_typespec("function");
ts.ts_args.emplace_back(return_type, types);
for_each_in_list(args, [&](Object o) {
if (o.type == SYMBOL) {
ts.ts_args.push_back(get_base_typespec("object"));
} else {
auto param_args = goos.get_uneval_args(o, o, 3);
if (param_args.unnamed_args.size() >= 3 || param_args.unnamed_args.size() < 1 ||
param_args.has_rest || !param_args.named_args.empty()) {
throw_compile_error(o, "invalid method parameter");
}
TypeSpec parm_type;
if (param_args.unnamed_args.size() >= 2) {
parm_type = TypeSpec(param_args.unnamed_args[1], types); // todo improve
} else {
parm_type = get_base_typespec("object");
}
ts.ts_args.push_back(parm_type);
}
});
// check that we don't modify an existing type definition
MethodType existing;
if (types.try_get_method_info(new_type->get_name(), method_name, &existing)) {
if (existing.type != ts) {
gLogger.log(MSG_WARN,
"deftype :methods section has redefined method %s %s\nold: %s\nnew: %s\n",
new_type->get_name().c_str(), method_name.c_str(),
existing.type.print().c_str(), ts.print().c_str());
}
}
// declare the method
int assigned_id = types.add_method(new_type->get_name(), method_name, ts);
// check the method assert
if (id != -1) {
// method id assert!
if (id != assigned_id) {
printf("WARNING - ID assert failed on method %s of type %s (wanted %d got %d)\n",
method_name.c_str(), new_type->get_name().c_str(), id, assigned_id);
for (auto& method_info : types.type_method_types[new_type->get_name()]) {
printf(" [%02d] is %s\n", method_info.id, method_info.method_name.c_str());
}
throw_compile_error(form, "method id assert failed");
}
}
});
}
/*!
* Helper to call the new method of type to generate the type object at runtime.
*/
std::shared_ptr<Place> Goal::deftype_call_new_method_of_type(Object form,
std::shared_ptr<GoalType> new_type,
uint64_t flags,
std::shared_ptr<GoalEnv> env) {
auto ir = make_unique<IR_LoadInteger>();
ir->is_signed = false;
ir->size = 8;
ir->us_value = flags;
ir->value = env->alloc_reg(get_base_typespec("integer"));
auto new_type_method = compile_get_method_of_type(get_base_typespec("type"), "new", env);
auto new_type_symbol = compile_get_sym_obj(new_type->get_name(), env);
auto parent_type = compile_get_sym_val(new_type->parent, env);
auto new_type_flags = ir->value;
env->emit(std::move(ir));
// this new type method call will set a symbol with the name of a type. check that we don't
// redefine an existing symbol...
auto st_kv = symbol_types.find(
SymbolObject::make_new(goos.reader.symbolTable, new_type->get_name()).as_symbol());
if (st_kv != symbol_types.end() && st_kv->second != get_base_typespec("type")) {
gLogger.log(MSG_WARN, "deftype redefines symbol %s from type %s to a type\n",
new_type->get_name().c_str(), st_kv->second.print().c_str());
}
set_symbol_type(new_type->get_name(), get_base_typespec("type"));
// do the function call!
return compile_real_function_call(form, new_type_method,
{new_type_symbol, parent_type, new_type_flags}, env);
}
/*!
* Deftype for a structure.
* The field placer is extremely basic - just places each field in order, putting it as close to the
* end of the last placed field as possible. I think the real GOAL one is smarter, it'll reorder
* your fields if it can sneak a small field into unused padding. However, by ordering the fields
* in the order they actually occur, this placer should have the same result.
*/
std::shared_ptr<Place> Goal::deftype_structure(std::shared_ptr<StructureType> new_type,
Object fields,
Object options,
std::shared_ptr<GoalEnv> env) {
// start by adding us to the types, so our fields can refer to our own type.
types.types[new_type->name] = new_type;
// parse all fields.
std::vector<StructFieldDefinition> field_defs;
for_each_in_list(fields, [&](Object o) { field_defs.emplace_back(parse_struct_field_def(o)); });
// initialize the offset. The offset is from the beginning of the type, and will _NOT_ include
// the basic offset.
int current_offset = 0;
if (!new_type->fields.empty()) {
// already have fields from parent, initialize to the back of that.
current_offset = new_type->fields.back().offset + get_size_in_type(new_type->fields.back());
}
int basic_offset = 0;
if (std::dynamic_pointer_cast<BasicType>(new_type))
basic_offset = BASIC_OFFSET;
// loop to place all fields
for (auto& def : field_defs) {
auto field_type = def.type;
int offset_to_use = current_offset;
if (def.offset_override == -1) {
// auto place
offset_to_use = align(current_offset, get_alignment_in_type(field_type, def.is_inline),
field_type.type->alignment_offset);
} else {
// the manual offset needs the basic offset applied, if its a basic. So the first
// user-defined field of a basic would go at a user offset of 0, but a real offset of 4.
offset_to_use = def.offset_override + basic_offset;
}
// create the field at the calculated offset
GoalField field(field_type, def.name, offset_to_use, def.is_inline, def.is_dynamic,
def.array_size);
// if requested, check that the offset is correct
if (def.offset_assert != -1) {
if (def.offset_assert != offset_to_use - basic_offset) {
throw_compile_error(fields, "offset assert failed on field " + def.name + ": got " +
std::to_string(offset_to_use - basic_offset) +
" expected " + std::to_string(def.offset_assert));
}
}
// take the max, which will allow overlayed types to not screw up the auto-placer.
current_offset = std::max(offset_to_use + get_size_in_type(field), current_offset);
new_type->fields.push_back(field);
}
// size and padded size of the entire type.
new_type->size = current_offset;
// todo - heap-base assert
// todo - size assert
// process options for the entire type
while (options.type != EMPTY_LIST) {
auto current = pair_car(options);
if (current.type == PAIR) {
auto first = symbol_string(pair_car(current));
if (first == ":methods") {
deftype_methods_helper(pair_cdr(current), new_type, env);
} else if (first == ":pack") {
// todo - warn if packing violates minimum alignment.
new_type->pack_structure_type = true;
} else if (first == ":no-pack") {
new_type->pack_structure_type = false;
} else {
throw_compile_error(current, "invalid option to deftype");
}
} else {
throw_compile_error(current, "invalid option to deftype");
}
options = pair_cdr(options);
}
// generate code to call the (new) method of type
TypeFlags flags;
flags.size = new_type->size;
flags.heap_base = 0; // ??
flags.methods = 64; // todo - not this!
if (new_type->name == "thread")
flags.methods = 12;
if (new_type->name == "connectable")
flags.methods = 9;
flags.pad = 0;
auto new_type_obj = deftype_call_new_method_of_type(fields, new_type, flags.flag, env);
// create an inspect function...
auto inspector = generate_inspector_for_type(new_type, env);
// and set it
auto ir2 = make_unique<IR_LoadInteger>();
ir2->is_signed = false;
ir2->size = 8;
ir2->us_value = 3;
ir2->value = env->alloc_reg(get_base_typespec("integer"));
auto value = ir2->value;
env->emit(std::move(ir2));
compile_real_function_call(fields, compile_get_sym_val("method-set!", env),
{new_type_obj, value, inspector}, env);
return get_none();
}
/*!
* Deftype to build a new bitfield type
*/
std::shared_ptr<Place> Goal::deftype_bitfield(std::shared_ptr<BitfieldType> new_type,
Object fields,
Object options,
std::shared_ptr<GoalEnv> env) {
new_type->fields.clear();
// parse all fields.
std::vector<BitFieldDefinition> field_defs;
for_each_in_list(fields, [&](Object o) { field_defs.emplace_back(parse_bit_field_def(o, env)); });
int current_offset = 0;
// place all fields
for (auto& def : field_defs) {
int offset_to_use = current_offset;
if (def.offset_override != -1) {
offset_to_use = def.offset_override;
}
if (offset_to_use + def.size > new_type->size * BITS_PER_BYTE) {
throw_compile_error(fields, "these field exceed the size of the bitfield type");
}
GoalBitField field(def.type, def.name, offset_to_use, def.size);
if (def.offset_assert != -1) {
if (def.offset_assert != offset_to_use) {
throw_compile_error(fields, "offset assert failed on filed " + def.name + ": got " +
std::to_string(offset_to_use) + "expected " +
std::to_string(def.offset_assert));
}
}
assert(def.size != -1);
assert(def.size != 0);
current_offset = std::max(offset_to_use + def.size, current_offset);
new_type->fields.push_back(field);
}
// do this near the end so we can't refer to ourself in fields, but :methods can.
types.types[new_type->name] = new_type;
// process options for the entire type
while (options.type != EMPTY_LIST) {
auto current = pair_car(options);
if (current.type == PAIR) {
auto first = symbol_string(pair_car(current));
if (first == ":methods") {
deftype_methods_helper(pair_cdr(current), new_type, env);
} else {
throw_compile_error(current, "invalid option to deftype");
}
} else {
throw_compile_error(current, "invalid option to deftype");
}
options = pair_cdr(options);
}
// generate code to call the (new) method of type
TypeFlags flags;
flags.size = new_type->size;
flags.heap_base = 0; // ??
flags.methods = 64; // todo - not this!
if (new_type->name == "thread")
flags.methods = 12;
if (new_type->name == "connectable")
flags.methods = 9;
flags.pad = 0;
auto new_type_obj = deftype_call_new_method_of_type(fields, new_type, flags.flag, env);
// printf("new bitfield type:\n%s\n", new_type->print().c_str());
return get_none();
}
+325
View File
@@ -0,0 +1,325 @@
/*!
* @file GoalDefineForms.cpp
* GOAL Compiler forms related to defining and setting things.
*/
#include <logger/Logger.h>
#include "Goal.h"
#include "util.h"
/*!
* Set a global place
*/
std::shared_ptr<Place> Goal::compile_define(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
auto args = goos.get_uneval_args(form, rest, 3);
if (!args.check_count(2) || !args.named_args.empty()) {
throw_compile_error(form, "invalid define form");
}
auto& sym = args.unnamed_args.at(0);
auto& value = args.unnamed_args.at(1);
if (sym.type != SYMBOL) {
throw_compile_error(form, "define must act on a symbol");
}
auto global_constant = global_constants.find(sym.as_symbol());
if (global_constant != global_constants.end()) {
throw_compile_error(
form, "it is illegal to define a GOAL symbol with the same name as a GOAL global constant");
}
auto ir = make_unique<IR_SetSymbolValue>();
auto compiled_value = compile_error_guard(value, env);
// special case to handle defining a function so that it can be inline later on.
auto as_lambda_place = std::dynamic_pointer_cast<LambdaPlace>(compiled_value);
if (as_lambda_place) {
// there are two cases in which we save a function body that is passed to a define:
// 1. It generated code [so went through the compiler] and the allow_inline flag is set.
// 2. It didn't generate code [so explicitly with :inline-only lambdas]
// The third case - immediate lambdas - don't get passed to a define,
// so this won't cause those to live for longer than they should
if ((as_lambda_place->func && as_lambda_place->func->settings.allow_inline) ||
!as_lambda_place->func) {
inlineable_functions[sym.as_symbol()] = as_lambda_place;
}
}
ir->value = resolve_to_gpr(compiled_value, env);
// typecheck, or define the type of the symbol.
auto existing_symbol_type = symbol_types.find(sym.as_symbol());
if (existing_symbol_type == symbol_types.end()) {
symbol_types[sym.as_symbol()] = ir->value->type;
} else {
typecheck_base_only(form, existing_symbol_type->second, ir->value->type,
"define on existing symbol");
}
TypeSpec symbol_type = get_base_typespec("symbol");
ir->dest = std::make_shared<SymbolPlace>(sym.as_symbol()->name, symbol_type);
auto result = ir->value;
env->emit(std::move(ir));
return result;
}
/*!
* Set the type of a global symbol
*/
std::shared_ptr<Place> Goal::compile_define_extern(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)env;
auto args = goos.get_uneval_args(form, rest, 3);
if (!args.check_count(2) || !args.named_args.empty()) {
throw_compile_error(form, "invalid define-extern form");
}
auto& sym = args.unnamed_args.at(0);
auto& typespec = args.unnamed_args.at(1);
if (sym.type != SYMBOL) {
throw_compile_error(form, "define-extern must act on a symbol");
}
// we should print a warning if we change the type.
// it's probably fine if the user is doing this at the REPL, but pretty bad if this happens in
// code.
auto new_type = compile_typespec(typespec);
auto existing_symbol_type = symbol_types.find(sym.as_symbol());
if (existing_symbol_type != symbol_types.end() && existing_symbol_type->second != new_type) {
gLogger.log(
MSG_WARN,
"[Warning] define-extern has redefined the type of symbol %s\npreviously: %s\nnow: %s\n",
symbol_string(sym).c_str(), existing_symbol_type->second.print().c_str(),
new_type.print().c_str());
}
symbol_types[sym.as_symbol()] = new_type;
return get_none();
}
/*!
* Set a global, lexical, or field
*/
std::shared_ptr<Place> Goal::compile_set(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
auto args = goos.get_uneval_args_no_rest(form, rest, 2);
if (!args.check_count(2) || !args.named_args.empty()) {
throw_compile_error(form, "invalid set! form");
}
auto& destination = args.unnamed_args.at(0);
auto source = resolve_to_gpr_or_xmm(compile_error_guard(args.unnamed_args.at(1), env), env);
if (destination.type == SYMBOL) {
// destination is just a symbol, so it's either a lexical variable or a global.
// first, attempt a lexical set:
auto lex_place = env->lexical_lookup(destination);
if (lex_place) {
// typecheck and set!
typecheck_base_only(form, lex_place->type, source->type, "set! lexical variable");
env->emit(make_unique<IR_Set>(lex_place, source));
return source;
} else {
// try to set symbol
auto existing = symbol_types.find(destination.as_symbol());
if (existing == symbol_types.end()) {
throw_compile_error(
form, "could not find something called " + symbol_string(destination) + " to set!");
} else {
typecheck_base_only(form, existing->second, source->type, "set! global symbol");
auto ir = make_unique<IR_SetSymbolValue>();
ir->value = resolve_to_gpr(source, env);
ir->dest = std::make_shared<SymbolPlace>(destination.as_symbol()->name, existing->second);
auto result = ir->value;
env->emit(std::move(ir));
return result;
}
}
} else {
// destination is something more complicated, like a (-> obj field) or a (car obj)
auto dest = compile_error_guard(destination, env);
auto dest_as_mem_deref = std::dynamic_pointer_cast<MemoryDerefPlace>(dest);
auto dest_as_pair = std::dynamic_pointer_cast<PairPlace>(dest);
auto dest_as_bitfield = std::dynamic_pointer_cast<BitfieldPlace>(dest);
if (dest_as_mem_deref) {
// special typecheck to handle the interger type weirdness (all registers are
// integer/uinteger, all fields are sized integers)
typecheck_for_set(form, dest_as_mem_deref->type, source->type, "set! mem deref");
// get the address of the thing we're setting:
auto base = dest_as_mem_deref->base;
// if the pointer is a const offset from another pointer, we can do an optimization
// this is useful to do someting like (set! (-> obj field) x), which field is a compile-time
// known offset from obj and we can use fancy x86 addressing to do this in a single
// instruction.
auto base_as_mem_c_offset = std::dynamic_pointer_cast<MemoryOffsetConstPlace>(base);
if (base_as_mem_c_offset) {
auto ir = make_unique<IR_StoreConstOffset>(
resolve_to_gpr(base_as_mem_c_offset->base, env), resolve_to_gpr(source, env),
base_as_mem_c_offset->offset, dest_as_mem_deref->type.type->load_size,
dest_as_mem_deref->type.type->load_signed);
auto result = ir->val;
env->emit(std::move(ir));
return result;
} else {
// the offset is not constant, so we need to compute the address and put it in a gpr first.
// this is done in the case of (set! (-> obj my-array x) y) where the x-th element of
// my-array is not at a known address at compile time.
auto addr = resolve_to_gpr(addr_of(dest_as_mem_deref, env), env);
// then do a store with 0 offset.
auto ir = make_unique<IR_StoreConstOffset>(addr, resolve_to_gpr(source, env), 0,
dest_as_mem_deref->type.type->load_size,
dest_as_mem_deref->type.type->load_signed);
auto result = ir->val;
env->emit(std::move(ir));
return result;
}
} else if (dest_as_pair) {
// store into the car/cdr of a pair.
auto ptr = resolve_to_gpr(dest_as_pair->base, env);
auto ir = make_unique<IR_StoreConstOffset>(ptr, resolve_to_gpr(source, env),
dest_as_pair->is_car ? -2 : 2, 4, false);
auto result = ir->val;
env->emit(std::move(ir));
return result;
} else if (dest_as_bitfield) {
typecheck_for_set(form, dest_as_bitfield->type, source->type, "set! bitfield");
return set_bitfield(dest_as_bitfield, source, env);
}
else {
throw_compile_error(form, "unknown set! target: " + dest->print());
}
}
throw_compile_error(form, "unreachable in compile_set");
return get_none();
}
/*!
* Declare a thing to be a function of the given type.
*/
std::shared_ptr<Place> Goal::compile_defun_extern(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)env;
auto args = goos.get_uneval_args_no_rest(form, rest, 3);
if (!args.check_count(3) || !args.named_args.empty()) {
throw_compile_error(form, "invalid defun-extern form");
}
auto function_name = args.unnamed_args.at(0);
auto param_list = args.unnamed_args.at(1);
auto return_type = args.unnamed_args.at(2);
TypeSpec ts = get_base_typespec("function");
ts.ts_args.emplace_back(return_type, types);
for_each_in_list(param_list, [&](Object o) {
if (o.type == SYMBOL) {
ts.ts_args.push_back(get_base_typespec("object"));
} else {
auto param_args = goos.get_uneval_args(o, o, 3);
if (param_args.unnamed_args.size() >= 3 || param_args.unnamed_args.size() < 1 ||
param_args.has_rest || !param_args.named_args.empty()) {
throw_compile_error(o, "invalid defun-extern parameter");
}
TypeSpec parm_type;
if (param_args.unnamed_args.size() >= 2) {
parm_type = compile_typespec(param_args.unnamed_args[1]);
} else {
parm_type = get_base_typespec("object");
}
ts.ts_args.push_back(parm_type);
}
});
auto st_kv = symbol_types.find(function_name.as_symbol());
if (st_kv == symbol_types.end()) {
symbol_types[function_name.as_symbol()] = ts;
} else {
if (ts != st_kv->second) {
// could be a warning?
throw_compile_error(form, "defun-extern changes the type of " + function_name.print() +
" from " + st_kv->second.print() + " to " + ts.print());
}
symbol_types[function_name.as_symbol()] = ts;
}
return get_none();
}
/*!
* Declare a method. Use of this form is discouraged - when possible put method defs in the type
* definition itself.
*/
std::shared_ptr<Place> Goal::compile_declare_method(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)env;
auto args = goos.get_uneval_args_no_rest(form, rest, 4);
if (!args.check_count(4) || !args.named_args.empty()) {
throw_compile_error(form, "invalid declare-method form");
}
auto type_name = args.unnamed_args.at(0);
auto method_name = symbol_string(args.unnamed_args.at(1));
auto param_list = args.unnamed_args.at(2);
auto return_type = args.unnamed_args.at(3);
TypeSpec ts = get_base_typespec("function");
ts.ts_args.emplace_back(return_type, types);
for_each_in_list(param_list, [&](Object o) {
if (o.type == SYMBOL) {
ts.ts_args.push_back(get_base_typespec("object"));
} else {
auto param_args = goos.get_uneval_args(o, o, 3);
if (param_args.unnamed_args.size() >= 3 || param_args.unnamed_args.size() < 1 ||
param_args.has_rest || !param_args.named_args.empty()) {
throw_compile_error(o, "invalid declare-method parameter");
}
TypeSpec parm_type;
if (param_args.unnamed_args.size() >= 2) {
parm_type = TypeSpec(param_args.unnamed_args[1], types); // todo improve
} else {
parm_type = get_base_typespec("object");
}
ts.ts_args.push_back(parm_type);
}
});
MethodType existing;
if (types.try_get_method_info(symbol_string(type_name), method_name, &existing)) {
if (existing.type != ts) {
gLogger.log(MSG_WARN,
"[Warning] declare-method changes the type of method %s %s\nold: %s\nnew: %s\n",
symbol_string(type_name).c_str(), method_name.c_str(),
existing.type.print().c_str(), ts.print().c_str());
}
}
types.add_method(symbol_string(type_name), method_name, ts);
return get_none();
}
+121
View File
@@ -0,0 +1,121 @@
/*!
* @file GoalEnum.cpp
* Implementation of GOAL Enums
*/
#include "logger/Logger.h"
#include "GoalEnum.h"
#include "Goal.h"
/*!
* Compile a enum definition.
*/
std::shared_ptr<Place> Goal::compile_defenum(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
// format is (defenum name [options] [entries])
(void)env;
// name
auto enum_name = symbol_string(pair_car(rest));
rest = pair_cdr(rest);
// default enum type will be int32.
auto enum_type = get_base_typespec("int32");
bool is_bitfield = false;
auto current = pair_car(rest);
while (current.type == SYMBOL && symbol_string(current).at(0) == ':') {
auto option_name = symbol_string(current);
rest = pair_cdr(rest);
auto option_value = pair_car(rest);
rest = pair_cdr(rest);
current = pair_car(rest);
if (option_name == ":type") {
enum_type = compile_typespec(option_value);
} else if (option_name == ":bitfield") {
if (symbol_string(option_value) == "#t") {
is_bitfield = true;
} else if (symbol_string(option_value) == "#f") {
is_bitfield = false;
} else {
throw_compile_error(form, "invalid option to :bitfield option");
}
} else {
throw_compile_error(form, "unknown option for defenum: " + option_name);
}
}
GoalEnum new_enum;
new_enum.ts = enum_type;
new_enum.is_bitfield = is_bitfield;
while (rest.type != EMPTY_LIST) {
auto def = pair_car(rest);
auto name = symbol_string(pair_car(def));
def = pair_cdr(def);
auto value = pair_car(def);
if (value.type != INTEGER) {
throw_compile_error(def, "expect integer");
}
def = pair_cdr(def);
if (def.type != EMPTY_LIST) {
throw_compile_error(def, "too many values in enum value definition");
}
new_enum.entries[name] = value.integer_obj.value;
rest = pair_cdr(rest);
}
auto existing_kv = enums.find(enum_name);
if (existing_kv != enums.end() && existing_kv->second != new_enum) {
gLogger.log(MSG_WARN, "defenum changes the definition of existing enum %s", enum_name.c_str());
}
enums[enum_name] = new_enum;
return get_none();
}
/*!
* Look up a value from an enum.
*/
std::shared_ptr<Place> Goal::compile_enum_lookup(GoalEnum& e,
Object rest,
std::shared_ptr<GoalEnv> env) {
if (e.is_bitfield) {
int64_t value = 0;
for_each_in_list(rest, [&](Object o) {
auto kv = e.entries.find(symbol_string(o));
if (kv == e.entries.end()) {
throw_compile_error(o, "value " + symbol_string(o) + " not found in enum");
}
value |= (1 << kv->second);
});
auto result = compile_integer(value, env);
result->type = e.ts;
return result;
} else {
throw_compile_error(rest, "enum non-bitfield lookup nyi");
}
return get_none();
}
/*!
* Are two enums identical?
*/
bool GoalEnum::operator==(const GoalEnum& other) {
return (ts == other.ts) && (is_bitfield == other.is_bitfield) && (entries == other.entries);
}
/*!
* Are two enums different?
*/
bool GoalEnum::operator!=(const GoalEnum& other) {
return !(*this == other);
}
+20
View File
@@ -0,0 +1,20 @@
/*!
* @file GoalEnum.h
* Implementation of GOAL Enums
*/
#ifndef JAK_V2_GOALENUM_H
#define JAK_V2_GOALENUM_H
#include "GoalType.h"
struct GoalEnum {
TypeSpec ts;
bool is_bitfield;
std::unordered_map<std::string, int64_t> entries;
bool operator==(const GoalEnum& other);
bool operator!=(const GoalEnum& other);
};
#endif // JAK_V2_GOALENUM_H
+394
View File
@@ -0,0 +1,394 @@
#include "shared_config.h"
#include "logger/Logger.h"
#include "GoalEnv.h"
///////////////////
// GoalEnv
///////////////////
// These just pass on their requests to the parent env, and are used as a default if the given
// env type doesn't support the request.
/*!
* Emit IR into the function currently being compiled.
*/
void GoalEnv::emit(std::unique_ptr<IR> ir) {
// by default, we don't know how, so pass it up and hope for the best.
parent->emit(std::move(ir));
}
/*!
* Allocate a General Purpose Register with the given type.
*/
std::shared_ptr<Place> GoalEnv::alloc_reg(TypeSpec ct) {
// we don't know how, so pass it up and hope for the best.
return parent->alloc_reg(ct);
}
/*!
* Apply a register constraint to the current function.
*/
void GoalEnv::constrain_reg(RegConstraint constraint) {
// we don't know how, so pass it up and hope for the best.
parent->constrain_reg(constraint);
}
/*!
* Lookup the given object as a lexical variable.
*/
std::shared_ptr<Place> GoalEnv::lexical_lookup(Object sym) {
// we don't know how, so pass it up and hope for the best.
return parent->lexical_lookup(sym);
}
/*!
* Find a block with the given name. Or return nullptr if not found.
*/
GoalEnv* GoalEnv::find_block(const std::string& block) {
// we don't know how, so pass it up and hope for the best.
return parent->find_block(block);
}
/*!
* Get the label map for the current function.
*/
std::unordered_map<std::string, std::shared_ptr<Label>>& GoalEnv::get_label_map() {
// we don't know how, so pass it up and hope for the best.
return parent->get_label_map();
}
/*!
* Get the static objects in the object file.
*/
std::vector<std::shared_ptr<StaticPlace>>& GoalEnv::get_statics() {
// we don't know how, so pass it up and hope for the best.
return parent->get_statics();
}
///////////////////
// GlobalEnv
///////////////////
// Because this is the top of the environment chain, all these end the parent calls and provide
// errors, or return that the items were not found.
/*!
* Print a global env.
*/
std::string GlobalEnv::print() {
return "GLOBAL";
}
/*!
* Emit into a global env, which is invalid.
*/
void GlobalEnv::emit(std::unique_ptr<IR> ir) {
(void)ir;
throw std::runtime_error("cannot emit to GlobalEnv");
}
/*!
* Allocate register in global env, which is invalid.
*/
std::shared_ptr<Place> GlobalEnv::alloc_reg(TypeSpec ct) {
(void)ct;
throw std::runtime_error("cannot alloc reg in GlobalEnv");
}
/*!
* Constraint register in global env, which is invalid.
*/
void GlobalEnv::constrain_reg(RegConstraint constraint) {
(void)constraint;
throw std::runtime_error("cannot constrain reg in GlobalEnv");
}
/*!
* Find block got to the top, and didn't find anything.
*/
GoalEnv* GlobalEnv::find_block(const std::string& block) {
(void)block;
return nullptr;
}
/*!
* Lexical lookup got to the top and didn't find anything.
*/
std::shared_ptr<Place> GlobalEnv::lexical_lookup(Object sym) {
(void)sym;
return nullptr;
}
/*!
* Get static objects, which is invalid in the global env.
*/
std::vector<std::shared_ptr<StaticPlace>>& GlobalEnv::get_statics() {
throw std::runtime_error("cannot get static list");
}
/*!
* Get label map, which is invalid in the global env.
*/
std::unordered_map<std::string, std::shared_ptr<Label>>& GlobalEnv::get_label_map() {
throw std::runtime_error("cannot get label map");
}
///////////////////
// NoEmitEnv
///////////////////
/*!
* Get the name of a NoEmitEnv
*/
std::string NoEmitEnv::print() {
return "no-emit-env";
}
/*!
* Emit - which is invalid - into a NoEmitEnv and throw an exception.
*/
void NoEmitEnv::emit(std::unique_ptr<IR> ir) {
(void)ir;
throw std::runtime_error("emit into a no-emit env!");
}
///////////////////
// ObjectFileEnv
///////////////////
ObjectFileEnv::ObjectFileEnv(std::string s) : name(std::move(s)) {}
ObjectFileEnv::ObjectFileEnv(std::string _name, std::shared_ptr<GoalEnv>& _parent)
: GoalEnv(_parent), name(std::move(_name)) {}
/*!
* Print the name of the object file env.
*/
std::string ObjectFileEnv::print() {
return name;
}
/*!
* Get the static objects of this object file.
*/
std::vector<std::shared_ptr<StaticPlace>>& ObjectFileEnv::get_statics() {
return statics;
}
/*!
* Add the given function as the top level function of this object file.
*/
void ObjectFileEnv::add_top_level_function(std::shared_ptr<FunctionEnv> f) {
top_level_function = f;
f->segment = TOP_LEVEL_SEGMENT;
functions.push_back(f); // todo, do we actually want this here?
}
/*!
* Is this object file empty? Used to determine if a REPL command should not be sent to the target
* because it would do nothing.
*/
bool ObjectFileEnv::is_empty() {
// emptiness is when we are a single IR, which is return none.
if (functions.size() == 1) {
auto& ir = functions.front()->code;
if (ir.size() == 1) {
auto ir_as_return = dynamic_cast<IR_Return*>(ir.front().get());
if (ir_as_return) {
auto value_as_none = std::dynamic_pointer_cast<NonePlace>(ir_as_return->value);
if (value_as_none) {
return true;
}
} else {
throw std::runtime_error("invalid function without return");
}
}
}
return false;
}
///////////////////
// DeclareEnv
///////////////////
DeclareEnv::DeclareEnv(std::shared_ptr<GoalEnv> _parent) : GoalEnv(_parent) {}
///////////////////
// FunctionEnv
///////////////////
FunctionEnv::FunctionEnv(std::string s) : name(std::move(s)) {}
FunctionEnv::FunctionEnv(std::string _name, std::shared_ptr<GoalEnv> _parent)
: DeclareEnv(_parent), name(std::move(_name)) {}
/*!
* Print the name of a function env.
*/
std::string FunctionEnv::print() {
return "function-" + name;
}
/*!
* Emit IR into a function env.
*/
void FunctionEnv::emit(std::unique_ptr<IR> ir) {
code.emplace_back(std::move(ir));
}
/*!
* Allocate a register in a function env.
*/
std::shared_ptr<Place> FunctionEnv::alloc_reg(TypeSpec ct) {
vars.push_back(std::make_shared<GprPlace>(vars.size(), ct));
return vars.back();
}
/*!
* Constraint a register in a function env.
*/
void FunctionEnv::constrain_reg(RegConstraint constraint) {
register_constraints.push_back(constraint);
}
/*!
* Lexical lookup in a function env.
*/
std::shared_ptr<Place> FunctionEnv::lexical_lookup(Object sym) {
if (sym.type != SYMBOL) {
throw std::runtime_error("invalid symbol in lexical_lookup");
}
// look at the arguments to the function.
auto kv = params.find(sym.as_symbol()->name);
if (kv == params.end()) {
// if not, see if an enclosing env. has these - this allows a lambda to look at variables
// outside itself. but this is dangerous.
return parent->lexical_lookup(sym);
}
return kv->second;
}
/*!
* Get the label map of this function.
*/
std::unordered_map<std::string, std::shared_ptr<Label>>& FunctionEnv::get_label_map() {
return labels;
}
/*!
* Allocate an xmm register in this function.
*/
std::shared_ptr<Place> FunctionEnv::alloc_xmm_reg(TypeSpec ct) {
vars.push_back(std::make_shared<XmmPlace>(vars.size(), ct));
return vars.back();
}
/*!
* Resolve gotos by name
*/
void FunctionEnv::resolve_gotos() {
for (auto& gt : unresolved_gotos) {
auto kv_label = labels.find(gt.label_name);
if (kv_label == labels.end()) {
throw std::runtime_error("Invalid goto " + gt.label_name);
}
gt.ir->label = kv_label->second;
gt.ir->resolved = true;
}
for (auto& gt : unresolved_cond_gotos) {
auto kv_label = labels.find(gt.label_name);
if (kv_label == labels.end()) {
throw std::runtime_error("invalid when-goto destination " + gt.label_name);
}
gt.ir->label = kv_label->second;
gt.ir->resolved = true;
}
}
/*!
* Do any post-processing passes needed on a function.
*/
void FunctionEnv::finish() {
resolve_gotos();
}
///////////////////
// BlockEnv
///////////////////
BlockEnv::BlockEnv(std::shared_ptr<GoalEnv> p, const std::string& _name) {
parent = p;
name = _name;
}
std::string BlockEnv::print() {
return "block-" + name;
}
GoalEnv* BlockEnv::find_block(const std::string& block) {
if (name == block)
return this;
return parent->find_block(block);
}
///////////////////
// LexicalEnv
///////////////////
std::shared_ptr<Place> LexicalEnv::lexical_lookup(Object sym) {
if (sym.type != SYMBOL) {
throw std::runtime_error("invalid symbol in lexical_lookup");
}
auto kv = vars.find(sym.as_symbol()->name);
if (kv == vars.end()) {
return parent->lexical_lookup(sym);
}
return kv->second;
}
std::string LexicalEnv::print() {
std::string result = "lexical env:\n";
for (const auto& kv : vars) {
result += "(" + kv.first + " " + kv.second->print() + ")";
}
return result;
}
///////////////////
// LabelEnv
///////////////////
LabelEnv::LabelEnv(std::shared_ptr<GoalEnv> p) {
parent = p;
}
std::string LabelEnv::print() {
return "label-env";
}
std::unordered_map<std::string, std::shared_ptr<Label>>& LabelEnv::get_label_map() {
return labels;
}
///////////////////
// WithInlineEnv
///////////////////
WithInlineEnv::WithInlineEnv(bool preference) : inline_preference(preference) {}
std::string WithInlineEnv::print() {
return "inline-env: " + std::to_string((int)inline_preference);
}
///////////////////
// SymbolMacroEnv
///////////////////
SymbolMacroEnv::SymbolMacroEnv(std::shared_ptr<GoalEnv>& p) {
parent = p;
}
std::string SymbolMacroEnv::print() {
return "symbol-macro-env";
}
+200
View File
@@ -0,0 +1,200 @@
/*!
* @file GoalEnv.h
* GOAL Environment. Provides the current context for compilation.
*/
#ifndef JAK_GOALENV_H
#define JAK_GOALENV_H
#include <memory>
#include <string>
#include <vector>
#include <stdexcept>
#include <unordered_map>
#include "codegen/x86.h"
#include "IR.h"
#include "Label.h"
#include "codegen/ColoringAssignment.h"
#include "GoalPlace.h"
class DeclareEnv;
// Parent Class of all Environment Classes
class GoalEnv {
public:
std::shared_ptr<GoalEnv> parent = nullptr;
GoalEnv() = default;
explicit GoalEnv(std::shared_ptr<GoalEnv>& _parent) : parent(_parent) {}
// a handful of commonly used environment functions which all environments are required to support
virtual std::string print() = 0;
virtual void emit(std::unique_ptr<IR> ir);
virtual std::shared_ptr<Place> alloc_reg(TypeSpec ct);
virtual void constrain_reg(RegConstraint constraint);
virtual std::shared_ptr<Place> lexical_lookup(Object sym);
virtual GoalEnv* find_block(const std::string& block);
virtual std::unordered_map<std::string, std::shared_ptr<Label>>& get_label_map();
virtual std::vector<std::shared_ptr<StaticPlace>>& get_statics();
};
// Highest level environment. This is just a way to catch environment calls which go up too high so
// they end somewhere.
class GlobalEnv : public GoalEnv {
public:
std::string print() override;
void emit(std::unique_ptr<IR> ir) override;
std::shared_ptr<Place> alloc_reg(TypeSpec ct) override;
void constrain_reg(RegConstraint constraint) override;
GoalEnv* find_block(const std::string& block) override;
std::shared_ptr<Place> lexical_lookup(Object sym) override;
std::vector<std::shared_ptr<StaticPlace>>& get_statics() override;
std::unordered_map<std::string, std::shared_ptr<Label>>& get_label_map() override;
};
// An environment which you cannot emit code into. Useful to make sure that compilation does not
// require code, for instance to compute a constant for a static field.
class NoEmitEnv : public GoalEnv {
public:
std::string print() override;
void emit(std::unique_ptr<IR> ir) override;
};
class FunctionEnv;
// An environment representing an object file/translation unit.
class ObjectFileEnv : public GoalEnv {
public:
std::string name; // name of object file / translation unit
std::vector<std::shared_ptr<FunctionEnv>> functions; // all functions in the file
std::vector<std::shared_ptr<StaticPlace>> statics; // all static objects in the file
std::shared_ptr<FunctionEnv> top_level_function = nullptr; // the top-level function of the file
explicit ObjectFileEnv(std::string s);
ObjectFileEnv(std::string _name, std::shared_ptr<GoalEnv>& _parent);
std::string print() override;
std::vector<std::shared_ptr<StaticPlace>>& get_statics() override;
void add_top_level_function(std::shared_ptr<FunctionEnv> f);
bool is_empty();
};
// An environment which can receive a (declare ...) statement.
class DeclareEnv : public GoalEnv {
public:
DeclareEnv() = default;
explicit DeclareEnv(std::shared_ptr<GoalEnv> _parent);
virtual std::string print() = 0;
struct DeclareSettings {
bool is_set = false;
bool inline_by_default = false;
bool save_code = true;
bool allow_inline = false;
} settings;
};
// An environment representing a function
class FunctionEnv : public DeclareEnv {
public:
std::string name; // function name
std::vector<std::unique_ptr<IR>> code; // all IR of the function
std::vector<std::shared_ptr<Place>> vars; // variables of the function (to color)
std::vector<RegConstraint> register_constraints; // constraints for coloring
std::vector<UnresolvedGoto> unresolved_gotos; // goto's by name that are not resolved
std::vector<UnresolvedConditionalGoto> unresolved_cond_gotos;
bool coloring_done = false; // is the coloring done?
bool requires_aligned_stack = false; // does our stack need to be aligned?
bool is_asm_func = false; // is this an asm function?
int segment = -1; // which segment do we go in?
int first_instruction; // index of our first instruction when emitted
std::vector<LiveRange> coloring; // coloring assignment after coloring
bool uses_saved_reg[SAVED_REG_COUNT] = {}; // which saved regs we use
bool uses_rbp = false; // do we need the rbp register?
std::vector<RegAllocBonusInstruction> bonus_instructions; // spill instructions
std::unordered_map<std::string, std::shared_ptr<Label>> labels; // labels defined in the function
std::unordered_map<std::string, std::shared_ptr<Place>> params; // function arguments
std::string method_of_type_name =
"#f"; // if we're a method, the name of the type, or #f otherwise
int stack_slots = 0; // how many stack slots do we need?
explicit FunctionEnv(std::string s);
FunctionEnv(std::string _name, std::shared_ptr<GoalEnv> _parent);
std::string print() override;
void emit(std::unique_ptr<IR> ir) override;
std::shared_ptr<Place> alloc_reg(TypeSpec ct) override;
void constrain_reg(RegConstraint constraint) override;
std::shared_ptr<Place> lexical_lookup(Object sym) override;
std::unordered_map<std::string, std::shared_ptr<Label>>& get_label_map() override;
std::shared_ptr<Place> alloc_xmm_reg(TypeSpec ct);
void finish();
void resolve_gotos();
};
// An environment for a block
class BlockEnv : public GoalEnv {
public:
std::string name; // block name
std::shared_ptr<Label> end_label; // label to jump to end of block
std::shared_ptr<Place> return_value; // the register holding the result of the block
std::vector<TypeSpec> return_types; // the possible types of return_value.
BlockEnv(std::shared_ptr<GoalEnv> p, const std::string& _name);
std::string print() override;
GoalEnv* find_block(const std::string& block) override;
};
// An environment for holding variables. Can also receive declares.
class LexicalEnv : public DeclareEnv {
public:
LexicalEnv() = default;
std::shared_ptr<Place> lexical_lookup(Object sym) override;
std::string print() override;
std::unordered_map<std::string, std::shared_ptr<Place>> vars;
};
// A label name space
class LabelEnv : public GoalEnv {
public:
explicit LabelEnv(std::shared_ptr<GoalEnv> p);
std::string print() override;
std::unordered_map<std::string, std::shared_ptr<Label>>& get_label_map() override;
std::unordered_map<std::string, std::shared_ptr<Label>> labels;
};
// An env representing a preference for inlining.
class WithInlineEnv : public GoalEnv {
public:
bool inline_preference = false;
WithInlineEnv(bool preference);
std::string print() override;
};
// An env for a symbol macro namespace
class SymbolMacroEnv : public GoalEnv {
public:
std::unordered_map<std::shared_ptr<SymbolObject>, Object> macros;
explicit SymbolMacroEnv(std::shared_ptr<GoalEnv>& p);
std::string print() override;
};
/*!
* Search up the environment chain until we find a parent env of the given type.
*/
template <typename T>
std::shared_ptr<T> get_parent_env_of_type(std::shared_ptr<GoalEnv> in) {
for (;;) {
auto attempt = std::dynamic_pointer_cast<T>(in);
if (attempt)
return attempt;
if (std::dynamic_pointer_cast<GlobalEnv>(in)) {
return nullptr;
}
in = in->parent;
}
}
#endif // JAK_GOALENV_H
+161
View File
@@ -0,0 +1,161 @@
/*!
* @file GoalFieldAccess.cpp
* Access field of types implementation.
*/
#include "Goal.h"
#include "util.h"
/*!
* Compile the -> form
*/
std::shared_ptr<Place> Goal::compile_deref(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
if (rest.type == EMPTY_LIST) {
throw_compile_error(form, "-> must get at least one argument!");
}
auto first_arg = rest.as_pair()->car;
rest = rest.as_pair()->cdr;
// start by evaluating the first thing.
auto result = compile_error_guard(first_arg, env);
if (rest.type == EMPTY_LIST) {
// if there is only one argument, try to dereference it as a pointer.
auto ptr_ts = get_base_typespec("pointer");
typecheck_base_only(form, ptr_ts, result->type, "-> with one argument was not given a pointer");
result = std::make_shared<MemoryDerefPlace>(get_base_of_pointer(result->type),
result->type.type->load_size,
result->type.type->load_signed, result);
return result;
}
// loop through field names...
while (rest.type != EMPTY_LIST) {
// the current field to deref
auto field_obj = rest.as_pair()->car;
rest = rest.as_pair()->cdr;
// if its a symbol, lets try it as a field name...
// this may fail and fall through below to the integer array stuff.
if (field_obj.type == SYMBOL) {
auto field_name = field_obj.as_symbol()->name;
auto struct_type = std::dynamic_pointer_cast<StructureType>(result->type.type);
if (struct_type) {
bool is_basic = (std::dynamic_pointer_cast<BasicType>(struct_type) != nullptr);
bool found_field = false;
int offset = is_basic ? -4 : 0;
TypeSpec field_ts;
GoalField t_field;
// lookup!
for (auto& field : struct_type->fields) {
if (field.name == field_name) {
// got it!
found_field = true;
offset += field.offset;
field_ts = field.type;
t_field = field;
break;
}
}
// note - so that we can later take the address of this field, we must express this field in
// a way where we can "go one dereference back" to get where it's located.
if (found_field) {
auto result_type = field_ts;
if (t_field.is_array || t_field.is_dynamic) {
// array-like
auto array_type = t_field.is_inline ? "inline-array" : "pointer";
result_type = TypeSpec(get_base_typespec(array_type).type, {result_type});
result = std::make_shared<MemoryOffsetConstPlace>(result_type, offset, result);
} else {
if (t_field.is_inline) {
// inline field, so it's just an offset in memory from the base.
result = std::make_shared<MemoryOffsetConstPlace>(result_type, offset, result);
} else {
// field is a reference.
auto field_loc_type = TypeSpec(get_base_typespec("pointer").type, {result_type});
auto field_loc =
std::make_shared<MemoryOffsetConstPlace>(field_loc_type, offset, result);
result = std::make_shared<MemoryDerefPlace>(result_type, field_ts.type->load_size,
field_ts.type->load_signed, field_loc);
}
}
// success, go on to the next field.
continue;
} else {
// couldn't find the field.
throw_compile_error(form, "invalid -> form - couldn't find the field named " +
field_name + " in type " + struct_type->name);
}
}
auto bitfield_type = std::dynamic_pointer_cast<BitfieldType>(result->type.type);
if (bitfield_type) {
GoalBitField t_field;
if (bitfield_type->find_field(field_name, &t_field)) {
result = std::make_shared<BitfieldPlace>(t_field, result);
// success, go on to the next thing
continue;
}
}
}
// try to get an integer:
auto index_value = resolve_to_gpr(compile_error_guard(field_obj, env), env);
if (!is_integer(index_value->type)) {
throw_compile_error(form, "couldn't figure out how to -> with " + field_obj.print());
}
// array access!
// todo - can remove the multiplication for constant integers!
if (result->type.type->get_name() == "inline-array") {
auto base_type = get_base_of_inline_array(result->type);
// offset uses size_in_array because we are inline
auto scaled_offset =
compile_integer_to_gpr((base_type.type->get_size_in_inline_array()), env);
env->emit(make_unique<IR_IntegerMath>(IMUL_32, scaled_offset, index_value));
// because we are inline, we don't need to dereference
auto loc = std::make_shared<MemoryOffsetVarPlace>(base_type, scaled_offset, result);
result = loc;
} else if (result->type.type->get_name() == "pointer") {
auto base_type = get_base_of_pointer(result->type);
auto scaled_offset =
compile_integer_to_gpr(base_type.type->get_size_in_non_inline_array(), env);
env->emit(make_unique<IR_IntegerMath>(IMUL_32, scaled_offset, index_value));
auto loc = std::make_shared<MemoryOffsetVarPlace>(result->type, scaled_offset, result);
result = std::make_shared<MemoryDerefPlace>(base_type, base_type.type->load_size,
base_type.type->load_signed, loc);
} else {
throw_compile_error(form, "can't access array of type " + result->type.type->get_name());
}
}
return result;
}
/*!
* Compile the address of form.
*/
std::shared_ptr<Place> Goal::compile_addr_of(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
auto arg = compile_error_guard(pair_car(rest), env);
expect_empty_list(pair_cdr(rest));
auto arg_as_mem_deref = std::dynamic_pointer_cast<MemoryDerefPlace>(arg);
if (!arg_as_mem_deref) {
throw_compile_error(form, "could not take the address of " + arg->print());
}
// todo - static and stack variables
return arg_as_mem_deref->base;
}
+783
View File
@@ -0,0 +1,783 @@
/*!
* @file GoalFunctionForms.cpp
* Utilities related to functions.
*/
#include "Goal.h"
#include "GoalLambda.h"
#include "util.h"
#include "logger/Logger.h"
/*!
* Compile "inline", a form which makes a function call inline if possible, and errors otherwise.
*/
std::shared_ptr<Place> Goal::compile_inline(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)env;
auto args = goos.get_uneval_args(form, rest, 2);
if (args.has_rest || args.unnamed_args.size() < 1 || !args.named_args.empty()) {
throw_compile_error(form, "invalid inline");
}
auto function_name = args.unnamed_args.front();
if (function_name.type != SYMBOL) {
throw_compile_error(form, "invalid inline, must give a symbol");
}
auto kv = inlineable_functions.find(function_name.as_symbol());
if (kv == inlineable_functions.end()) {
throw_compile_error(form, "couldn't find definition to inline");
}
if (kv->second->func && !kv->second->func->settings.allow_inline) {
throw_compile_error(form, "not allowed to inline");
}
return kv->second;
}
/*!
* Get the preference to inline in the given environment - return false if no preference set.
*/
static bool get_inline_preference(std::shared_ptr<GoalEnv> env) {
auto inline_env = get_parent_env_of_type<WithInlineEnv>(env);
if (inline_env) {
return inline_env->inline_preference;
} else {
return false;
}
}
/*!
* Compile a real x86 function call helper,
*/
std::shared_ptr<Place> Goal::compile_real_function_call(const Object& form,
std::shared_ptr<Place> function,
std::vector<std::shared_ptr<Place>> args,
std::shared_ptr<GoalEnv> env) {
TypeSpec return_ts;
if (function->type.ts_args.empty()) {
// if the type system doesn't know what the function will return, just make it object.
// the user is responsible for getting this right.
return_ts = get_base_typespec("object");
// gLogger.log(MSG_WARN, "[Warning] Function call could not determine return type: %s\n",
// const_cast<Object&>(form).print().c_str());
// todo, should this be a warning? not a great thing if we don't know what a function will
// return?
} else {
return_ts = function->type.ts_args.front();
}
auto return_reg = env->alloc_reg(return_ts);
for (auto& arg : args) {
// note: this has to be done in here, because we might want to const prop across lexical envs.
arg = resolve_to_gpr(arg, env);
}
// check arg count:
if (!function->type.ts_args.empty()) {
if (function->type.ts_args.size() - 1 != args.size()) {
throw_compile_error(form, "invalid number of arguments to function call: got " +
std::to_string(args.size()) + " and expected " +
std::to_string(function->type.ts_args.size() - 1));
}
for (uint32_t i = 0; i < args.size(); i++) {
typecheck_base_only(form, function->type.ts_args.at(i + 1), args.at(i)->type,
"function argument");
}
}
// set args (introducing a move here makes coloring more likely to be possible)
std::vector<std::shared_ptr<Place>> arg_outs;
for (auto& arg : args) {
arg_outs.push_back(env->alloc_reg(arg->type));
env->emit(make_unique<IR_Set>(arg_outs.back(), arg));
}
env->emit(
make_unique<IR_FunctionCall>(env->alloc_reg(function->type), function, return_reg, arg_outs));
return return_reg;
}
/*!
* Compile a function or method call. This includes real function calls, inline function calls,
* automatic inline function calls, immediate application of lambda, method calls of basics, and
* method calls of structures.
*/
std::shared_ptr<Place> Goal::compile_function_or_method_call(const Object& form,
std::shared_ptr<GoalEnv> env) {
Object f = form;
// get args in a list
auto args = goos.get_uneval_args_no_rest(form, form, 9); // 8 args + function max
auto uneval_head = args.unnamed_args.front();
auto head = get_none(); // will hold function object to call
// determine if this call should be automatically inlined.
// this logic will not trigger for a manually inlined call [using the (inline func) form]
bool auto_inline = false;
if (uneval_head.type == SYMBOL) {
// we can only auto-inline the function if its name is explicit.
// look it up:
auto kv = inlineable_functions.find(as_symbol_obj(uneval_head));
if (kv != inlineable_functions.end()) {
// it's inlinable. However, we do not always inline an inlinable function by default
if (kv->second->func ==
nullptr || // only-inline, we must inline it as there is no code generated for it
kv->second->func->settings
.inline_by_default || // inline when possible, so we should inline
(kv->second->func->settings.allow_inline &&
get_inline_preference(env))) { // inline is allowed, and we prefer it locally
auto_inline = true;
head = kv->second;
}
}
}
bool is_method_call = false;
if (!auto_inline) {
// if auto-inlining failed, we must get the thing to call in a different way.
if (uneval_head.type == SYMBOL) {
if (is_local_symbol(uneval_head, env) ||
symbol_types.find(as_symbol_obj(uneval_head)) != symbol_types.end()) {
// the local environment (mlets, lexicals, constants, globals) defines this symbol.
// this will "win" over a method name lookup, so we should compile as normal
head = compile_error_guard(args.unnamed_args.front(), env);
} else {
// we don't think compiling the head give us a function, so it's either a method or an error
is_method_call = true;
}
} else {
// the head is some expression. Could be something like (inline my-func) or (-> obj
// func-ptr-field) in either case, compile it - and it can't be a method call.
head = compile_error_guard(args.unnamed_args.front(), env);
}
}
if (!is_method_call) {
// typecheck that we got a function
auto f_type = get_base_typespec("function");
if (!head->type.typecheck_base_only(f_type, types)) {
throw_compile_error(
form, "function call head does not evaluate to a function! " + head->type.print());
}
}
// compile arguments
std::vector<std::shared_ptr<Place>> eval_args;
for (uint32_t i = 1; i < args.unnamed_args.size(); i++) {
auto intermediate = compile_error_guard(args.unnamed_args.at(i), env);
eval_args.push_back(resolve_to_gpr_or_xmm(intermediate, env));
}
// see if its an "immediate" application. This happens in three cases:
// 1). the user directly puts a (lambda ...) form in the head (like with a (let) macro)
// 2). the user used a (inline my-func) to grab the LambdaPlace of the function.
// 3). the auto-inlining above looked up the LambdaPlace of an inlinable_function.
// note that an inlineable function looked up by symbol or other way WILL NOT cast to a
// LambdaPlace! so this cast will only succeed if the auto-inliner succeeded, or the user has
// passed use explicitly a lambda either with the lambda form, or with the (inline ...) form.
std::shared_ptr<LambdaPlace> head_as_lambda = nullptr;
if (!is_method_call) {
head_as_lambda = std::dynamic_pointer_cast<LambdaPlace>(head);
}
if (head_as_lambda) {
// inline the function!
// check args are ok
if (head_as_lambda->lambda.params.size() != eval_args.size()) {
throw_compile_error(form, "invalid argument count");
}
// construct a lexical environment
auto lexical_env = std::make_shared<LexicalEnv>();
lexical_env->parent = env;
std::shared_ptr<GoalEnv> compile_env = lexical_env;
// if needed create a label env.
// we don't want a separate label env with lets, but we do in other cases.
if (auto_inline) {
// TODO - this misses the case of (inline func)!
compile_env = std::make_shared<LabelEnv>(lexical_env);
}
// check arg types
if (!head->type.ts_args.empty()) {
if (head->type.ts_args.size() - 1 != eval_args.size()) {
throw_compile_error(form, "invalid number of arguments to function call (inline)");
}
for (uint32_t i = 0; i < eval_args.size(); i++) {
typecheck_base_only(form, head->type.ts_args.at(i + 1), eval_args.at(i)->type,
"function (inline) argument");
}
}
// copy args...
for (uint32_t i = 0; i < eval_args.size(); i++) {
auto copy = env->alloc_reg(eval_args.at(i)->type);
env->emit(make_unique<IR_Set>(copy, eval_args.at(i)));
lexical_env->vars[head_as_lambda->lambda.params.at(i).name] = copy;
}
// compile inline!
bool first_thing = true;
std::shared_ptr<Place> result = get_none();
for_each_in_list(head_as_lambda->lambda.body, [&](Object o) {
result = compile_error_guard(o, compile_env);
if (first_thing) {
first_thing = false;
lexical_env->settings.is_set = true;
}
});
// this doesn't require a return type.
return result;
} else {
// not an inline call
if (is_method_call) {
// determine the method to call by looking at the type of first argument
if (eval_args.empty()) {
throw_compile_error(form, "0 argument method call is impossible to figure out");
}
head = compile_get_method_of_object(eval_args.front(), symbol_string(uneval_head), env);
}
// convert the head to a GPR
auto head_as_gpr = std::dynamic_pointer_cast<GprPlace>(resolve_to_gpr(head, env));
if (head_as_gpr) {
return compile_real_function_call(form, head_as_gpr, eval_args, env);
} else {
throw_compile_error(form, "can't figure out this function call!");
}
}
throw_compile_error(form, "call_function_or_method unreachable");
return get_none();
}
std::shared_ptr<Place> Goal::compile_defmethod(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
auto args = goos.get_uneval_args(form, rest, 3);
if (!args.named_args.empty() || args.unnamed_args.size() != 3) {
throw_compile_error(form, "invalid defmethod");
}
TypeSpec lambda_ts = get_base_typespec("function");
// temp return typespec
lambda_ts.ts_args.push_back(get_base_typespec("none"));
// temp for now
auto place = std::make_shared<LambdaPlace>(get_none()->type);
// Build Lambda Object
GoalLambda& lambda = place->lambda;
// todo get the correct function type
auto arg_name = args.unnamed_args.at(0);
auto arg_type = args.unnamed_args.at(1);
if (arg_name.type != SYMBOL) {
throw_compile_error(form, "defmethod method name must be a symbol");
}
if (arg_type.type != SYMBOL) {
throw_compile_error(form, "defmethod type name must be a symbol");
}
auto body = args.unnamed_args.at(2);
if (body.type == EMPTY_LIST) {
throw_compile_error(form, "defmethod had an empty body!");
}
for_each_in_list(body, [&](Object o) {
if (o.type == SYMBOL) {
lambda.params.emplace_back(o.as_symbol()->name, get_base_typespec("object"));
lambda_ts.ts_args.push_back(get_base_typespec("object"));
} else {
auto param_args = goos.get_uneval_args(o, o, 3);
if (param_args.unnamed_args.size() >= 3 || param_args.unnamed_args.size() < 1 ||
param_args.has_rest || !param_args.named_args.empty()) {
throw_compile_error(o, "invalid lambda parameter");
}
GoalLambdaParam parm;
if (param_args.unnamed_args.front().type != SYMBOL) {
throw_compile_error(o, "invalid lambda parameter");
}
parm.name = param_args.unnamed_args.front().as_symbol()->name;
if (param_args.unnamed_args.size() >= 2) {
parm.type = TypeSpec(param_args.unnamed_args[1], types); // todo improve
} else {
parm.type = get_base_typespec("object");
}
// printf("set arg type to %s\n", parm.type.print().c_str());
if (param_args.unnamed_args.size() >= 3) {
parm.default_value = param_args.unnamed_args[2];
parm.has_default = true;
}
lambda.params.push_back(parm);
lambda_ts.ts_args.push_back(parm.type);
}
});
assert(lambda.params.size() + 1 == lambda_ts.ts_args.size());
if (!args.has_rest) {
throw_compile_error(form, "lambda must have a body");
}
// skip docstring
if (args.rest.as_pair()->car.type == STRING && args.rest.as_pair()->cdr.type != EMPTY_LIST) {
args.rest = args.rest.as_pair()->cdr;
}
lambda.body = args.rest;
place->func = nullptr;
auto new_func_env = std::make_shared<FunctionEnv>(place->print());
new_func_env->method_of_type_name = arg_type.as_symbol()->name;
new_func_env->parent = env;
new_func_env->segment = MAIN_SEGMENT; // todo not this
// set up arguments
assert(lambda.params.size() < 8); // todo, this should be more graceful
for (uint32_t i = 0; i < lambda.params.size(); i++) {
RegConstraint constr;
constr.instr_id = 0;
constr.var_id = new_func_env->vars.size();
constr.ass.kind = REGISTER;
constr.ass.reg_id = ARG_REGS[i];
new_func_env->params[lambda.params.at(i).name] =
new_func_env->alloc_reg(lambda.params.at(i).type);
new_func_env->constrain_reg(constr);
}
place->func = new_func_env;
new_func_env->emit(make_unique<IR_FunctionBegin>(place));
auto return_reg = new_func_env->alloc_reg(get_none()->type);
auto func_block_env = std::make_shared<BlockEnv>(new_func_env, "#f");
func_block_env->return_value = return_reg;
auto label = std::make_shared<Label>(new_func_env.get());
func_block_env->end_label = label;
// auto return_ir = std::make_shared<IR_Return>(compile_error_guard(body_with_begin,
// new_func_env), return_reg);
std::shared_ptr<Place> result = get_none();
bool first_thing = true;
for_each_in_list(lambda.body, [&](Object o) {
result = compile_error_guard(o, func_block_env);
if (first_thing) {
first_thing = false;
new_func_env->settings.is_set = true;
}
});
auto return_ir = make_unique<IR_Return>(resolve_to_gpr(result, func_block_env), return_reg);
return_reg->type = return_ir->value->type;
lambda_ts.ts_args.at(0) = return_ir->value->type;
new_func_env->emit(std::move(return_ir));
func_block_env->end_label->idx = new_func_env->code.size();
new_func_env->emit(make_unique<IR_Null>());
new_func_env->finish();
auto obj_env = get_parent_env_of_type<ObjectFileEnv>(new_func_env);
assert(obj_env);
if (new_func_env->settings.save_code) {
obj_env->functions.push_back(new_func_env);
}
place->type = lambda_ts;
auto id = types.add_method(arg_type.as_symbol()->name, arg_name.as_symbol()->name, lambda_ts);
return compile_real_function_call(form, compile_get_sym_val("method-set!", env),
{compile_get_sym_val(arg_type.as_symbol()->name, env),
compile_integer_to_gpr(id, env), resolve_to_gpr(place, env)},
env);
}
std::shared_ptr<Place> Goal::compile_lambda(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)env;
// Get Args
auto args = goos.get_uneval_args(form, rest, 1);
std::unordered_set<std::string> keywords = {"name", "inline-only"}; // also class, type, friends
if (!args.check_count(1) || !args.check_keywords(keywords)) {
throw_compile_error(form, "invalid lambda arguments, bad keyword or count");
}
TypeSpec lambda_ts = get_base_typespec("function");
// temp return typespec
lambda_ts.ts_args.push_back(get_base_typespec("none"));
// temp for now
auto place = std::make_shared<LambdaPlace>(get_none()->type);
// Build Lambda Object
GoalLambda& lambda = place->lambda;
// todo get the correct function type
for_each_in_list(args.unnamed_args.front(), [&](Object o) {
if (o.type == SYMBOL) {
lambda.params.emplace_back(o.as_symbol()->name, get_base_typespec("object"));
lambda_ts.ts_args.push_back(get_base_typespec("object"));
} else {
auto param_args = goos.get_uneval_args(o, o, 3);
if (param_args.unnamed_args.size() >= 3 || param_args.unnamed_args.size() < 1 ||
param_args.has_rest || !param_args.named_args.empty()) {
printf("bad %d %d %d %d\n", param_args.unnamed_args.size() >= 3,
param_args.unnamed_args.size() < 1, param_args.has_rest,
!param_args.named_args.empty());
printf("args %s\n", param_args.print().c_str());
throw_compile_error(o, "invalid lambda parameter 2");
}
GoalLambdaParam parm;
if (param_args.unnamed_args.front().type != SYMBOL) {
throw_compile_error(o, "invalid lambda parameter 3");
}
parm.name = param_args.unnamed_args.front().as_symbol()->name;
if (param_args.unnamed_args.size() >= 2) {
// parm.type = TypeSpec(param_args.unnamed_args[1], types); // todo improve
parm.type = compile_typespec(param_args.unnamed_args[1]);
} else {
parm.type = get_base_typespec("object");
}
if (param_args.unnamed_args.size() >= 3) {
parm.default_value = param_args.unnamed_args[2];
parm.has_default = true;
}
lambda.params.push_back(parm);
lambda_ts.ts_args.push_back(parm.type);
}
});
assert(lambda.params.size() + 1 == lambda_ts.ts_args.size());
auto name_kv = args.named_args.find("name");
if (name_kv != args.named_args.end()) {
if (name_kv->second.type != SYMBOL) {
throw_compile_error(form, "lambda name must be a symbol");
}
lambda.name = name_kv->second.as_symbol()->name;
}
if (!args.has_rest) {
throw_compile_error(form, "lambda must have a body");
}
lambda.body = args.rest;
place->func = nullptr;
bool inline_only = false;
auto inline_only_kv = args.named_args.find("inline-only");
if (inline_only_kv != args.named_args.end() && inline_only_kv->second.type == SYMBOL &&
inline_only_kv->second.as_symbol()->name == "#t") {
inline_only = true;
}
// Compile lambda
if (!inline_only) {
// printf("COMPILE LAMBDA WITH BODY %s\n", lambda.body.print().c_str());
// Object body_with_begin = PairObject::make_new(SymbolObject::make_new(goos.reader.symbolTable,
// "begin"), lambda.body);
auto new_func_env = std::make_shared<FunctionEnv>(place->print());
new_func_env->parent = env;
new_func_env->segment = DEBUG_SEGMENT; // todo not this
// set up arguments
assert(lambda.params.size() < 8); // todo, this should be more graceful
for (uint32_t i = 0; i < lambda.params.size(); i++) {
RegConstraint constr;
constr.instr_id = 0;
constr.var_id = new_func_env->vars.size();
constr.ass.kind = REGISTER;
constr.ass.reg_id = ARG_REGS[i];
new_func_env->params[lambda.params.at(i).name] =
new_func_env->alloc_reg(lambda.params.at(i).type);
// printf("add lc\n");
new_func_env->constrain_reg(constr);
}
place->func = new_func_env;
new_func_env->emit(make_unique<IR_FunctionBegin>(place));
auto return_reg = new_func_env->alloc_reg(get_none()->type);
// create a block env so we can use "return-from #f" to return from the function
auto func_block_env = std::make_shared<BlockEnv>(new_func_env, "#f");
func_block_env->return_value = return_reg;
auto label = std::make_shared<Label>(new_func_env.get());
func_block_env->end_label = label;
// auto return_ir = std::make_shared<IR_Return>(compile_error_guard(body_with_begin,
// new_func_env), return_reg);
std::shared_ptr<Place> result = get_none();
bool first_thing = true;
for_each_in_list(lambda.body, [&](Object o) {
result = compile_error_guard(o, func_block_env);
if (first_thing) {
first_thing = false;
new_func_env->settings.is_set = true;
}
});
auto return_ir = make_unique<IR_Return>(resolve_to_gpr(result, func_block_env), return_reg);
return_reg->type = return_ir->value->type;
lambda_ts.ts_args.at(0) = return_ir->value->type;
new_func_env->emit(std::move(return_ir));
func_block_env->end_label->idx = new_func_env->code.size();
new_func_env->emit(make_unique<IR_Null>());
new_func_env->finish();
auto obj_env = get_parent_env_of_type<ObjectFileEnv>(new_func_env);
assert(obj_env);
if (new_func_env->settings.save_code) {
obj_env->functions.push_back(new_func_env);
}
// printf("FUNCTION:\n");
// for(auto& c : new_func_env->code) {
// printf("%s\n", c->print().c_str());
// }
}
place->type = lambda_ts;
return place;
}
std::shared_ptr<Place> Goal::compile_declare(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
auto& settings = get_parent_env_of_type<DeclareEnv>(env)->settings;
if (settings.is_set) {
throw_compile_error(form, "function has multiple declares");
}
settings.is_set = true;
for_each_in_list(rest, [&](Object o) {
if (o.type != PAIR) {
throw_compile_error(o, "invalid declare specification");
}
auto first = o.as_pair()->car;
auto rrest = o.as_pair()->cdr;
if (first.type != SYMBOL) {
throw_compile_error(first, "invalid declare specification, expected a symbol");
}
if (first.as_symbol()->name == "inline") {
if (rrest.type != EMPTY_LIST) {
throw_compile_error(first, "invalid inline declare");
}
settings.allow_inline = true;
settings.inline_by_default = true;
settings.save_code = true;
} else if (first.as_symbol()->name == "allow-inline") {
if (rrest.type != EMPTY_LIST) {
throw_compile_error(first, "invalid allow-inline declare");
}
settings.allow_inline = true;
settings.inline_by_default = false;
settings.save_code = true;
} else if (first.as_symbol()->name == "asm-func") {
get_parent_env_of_type<FunctionEnv>(env)->is_asm_func = true;
}
else {
throw_compile_error(first, "unrecognized declare statement");
}
});
return get_none();
}
std::shared_ptr<Place> Goal::compile_with_inline(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
auto args = goos.get_uneval_args(form, rest, 1);
if (!args.has_rest || args.unnamed_args.size() < 1 || !args.named_args.empty()) {
throw_compile_error(form, "invalid with-inline form");
}
auto setting = args.unnamed_args.front();
if (setting.type != SYMBOL) {
throw_compile_error(form, "with-inline invalid setting");
}
bool inline_preference = false;
if (setting.as_symbol()->name == "#t") {
inline_preference = true;
} else if (setting.as_symbol()->name == "#f") {
inline_preference = false;
} else {
throw_compile_error(form, "with-inline can only be set to #t or #f");
}
auto new_env = std::make_shared<WithInlineEnv>(inline_preference);
new_env->parent = env;
auto result = get_none();
for_each_in_list(args.rest, [&](Object o) { result = compile_error_guard(o, new_env); });
return result;
}
static std::string reg_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",
};
ColoringAssignment Goal::reg_name_to_ca(Object& name) {
if (name.type != SYMBOL) {
throw_compile_error(name, "invalid register name");
}
auto nas = name.as_symbol();
for (int i = 0; i < 32; i++) {
if (nas->name == reg_names[i]) {
ColoringAssignment ca;
ca.kind = AssignmentKind::REGISTER;
ca.reg_id = i;
return ca;
}
}
throw_compile_error(name, "unknown register name");
return {};
}
std::shared_ptr<Place> Goal::compile_rlet(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
auto args = goos.get_uneval_args(form, rest, 1);
if (!args.has_rest || args.unnamed_args.size() < 1 || !args.named_args.empty()) {
throw_compile_error(form, "invalid rlet form");
}
auto defs = args.unnamed_args.front();
auto body = args.rest;
auto lenv = std::make_shared<LexicalEnv>();
lenv->parent = env;
auto fenv = get_parent_env_of_type<FunctionEnv>(env);
std::unordered_set<std::string> allowed_args = {"reg", "type"};
std::vector<RegConstraint> constraints;
uint32_t start_idx = fenv->code.size();
for_each_in_list(defs, [&](Object o) {
// (new-place [:reg old-place] [:type type-spec] [:class reg-type] [:bind #f|lexical|lambda])
auto def_args = goos.get_uneval_args_no_rest(o, o, 1);
if (def_args.unnamed_args.size() != 1 || !def_args.check_keywords(allowed_args)) {
throw_compile_error(o, "invalid rleg def");
}
// get the name of the new place
auto new_place_name = def_args.unnamed_args.front();
if (new_place_name.type != SYMBOL)
throw_compile_error(new_place_name, "invalid place name");
// get the type of the new place
TypeSpec ts = get_base_typespec("object");
auto type_kv = def_args.named_args.find("type");
if (type_kv != def_args.named_args.end()) {
ts = compile_typespec(type_kv->second);
}
// alloc a gpr:
auto new_place_reg = env->alloc_reg(ts);
auto reg_kv = def_args.named_args.find("reg");
if (reg_kv != def_args.named_args.end()) {
RegConstraint constraint;
// constraint.var_id = fenv->vars.size() - 1;
constraint.var_id = std::dynamic_pointer_cast<GprPlace>(new_place_reg)->identity;
constraint.ass = reg_name_to_ca(reg_kv->second);
constraint.instr_id = -1; // to be set later.
constraints.push_back(constraint);
}
lenv->vars[new_place_name.as_symbol()->name] = new_place_reg;
});
auto result = get_none();
for_each_in_list(args.rest, [&](Object o) { result = compile_error_guard(o, lenv); });
// for(uint32_t i = start_idx; i < fenv->code.size(); i++) {
// for(auto c : constraints) {
// c.instr_id = i;
// fenv->constrain_reg(c);
// }
// }
for (auto c : constraints) {
c.instr_id = start_idx;
// printf("add rlc\n");
fenv->constrain_reg(c);
}
return result;
}
std::shared_ptr<Place> Goal::compile_mlet(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
auto args = goos.get_uneval_args(form, rest, 1);
if (!args.has_rest || args.unnamed_args.size() < 1 || !args.named_args.empty()) {
throw_compile_error(form, "invalid mlet form");
}
// CREATE ENV
auto menv = std::make_shared<SymbolMacroEnv>(env);
auto defs = args.unnamed_args.front();
for_each_in_list(defs, [&](Object o) {
auto def_args = goos.get_uneval_args_no_rest(o, o, 2);
if (def_args.unnamed_args.size() != 2 || !def_args.named_args.empty()) {
throw_compile_error(o, "invalid symbol macro definition");
}
if (def_args.unnamed_args[0].type != SYMBOL) {
throw_compile_error(o, "invalid name for symbol macro");
}
menv->macros[def_args.unnamed_args[0].as_symbol()] = def_args.unnamed_args[1];
});
auto result = get_none();
for_each_in_list(args.rest, [&](Object o) { result = compile_error_guard(o, menv); });
return result;
}
std::shared_ptr<Place> Goal::compile_get_ra_ptr(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)form;
expect_empty_list(rest);
auto result =
env->alloc_reg(TypeSpec(get_base_typespec("pointer").type, {get_base_typespec("uint64")}));
env->emit(make_unique<IR_GetReturnAddressPointer>(result));
return result;
}
+116
View File
@@ -0,0 +1,116 @@
#include <util.h>
#include "Goal.h"
void Goal::generate_inspector_format_call(const std::string& format,
std::vector<std::shared_ptr<Place>> args,
std::shared_ptr<GoalEnv> env) {
// get the format object
auto format_func = compile_get_sym_val("format", env);
auto true_sym = compile_get_sym_obj("#t", env);
auto format_string = compile_string(format, env);
auto el_hack = EmptyListObject::make_new();
std::vector<std::shared_ptr<Place>> arg_regs = {true_sym, format_string};
for (auto& a : args) {
arg_regs.push_back(a);
}
compile_real_function_call(el_hack, format_func, arg_regs, env);
}
std::shared_ptr<Place> Goal::generate_inspector_for_type(std::shared_ptr<StructureType> type,
std::shared_ptr<GoalEnv> env) {
TypeSpec this_typespec(get_base_typespec(type->get_name()));
TypeSpec lambda_ts = get_base_typespec("function");
// return and arg
lambda_ts.ts_args.push_back(this_typespec);
lambda_ts.ts_args.push_back(this_typespec);
auto place = std::make_shared<LambdaPlace>(lambda_ts);
GoalLambda& lambda = place->lambda;
lambda.params.emplace_back("obj", this_typespec);
auto new_func_env = std::make_shared<FunctionEnv>(type->get_name() + "-inspector-autogen");
new_func_env->parent = env;
new_func_env->segment = DEBUG_SEGMENT; // todo not this
RegConstraint constr;
constr.instr_id = 0;
constr.var_id = new_func_env->vars.size();
constr.ass.kind = REGISTER;
constr.ass.reg_id = ARG_REGS[0];
auto obj = new_func_env->alloc_reg(get_base_typespec(type->get_name()));
new_func_env->params["obj"] = obj;
new_func_env->constrain_reg(constr);
place->func = new_func_env;
new_func_env->emit(make_unique<IR_FunctionBegin>(place));
auto return_reg = new_func_env->alloc_reg(get_base_typespec(type->get_name()));
bool is_basic = (std::dynamic_pointer_cast<BasicType>(type) != nullptr);
bool is_structure = (std::dynamic_pointer_cast<StructureType>(type) != nullptr);
int offset = 0;
if (is_basic) {
// (format #t "[~8x] ~A~%" obj (->4 obj -4))
offset = -4;
auto type_ptr_ir = make_unique<IR_LoadConstOffset>(
new_func_env->alloc_reg(get_base_typespec("type")), obj, -4, 4, false);
auto dst = type_ptr_ir->dst;
new_func_env->emit(std::move(type_ptr_ir));
generate_inspector_format_call("[~8x] ~A~%", {obj, dst}, new_func_env);
} else {
generate_inspector_format_call("[~8x] " + type->get_name() + "~%", {obj}, new_func_env);
}
for (auto& field : type->fields) {
if (is_basic && field.name == "type")
continue;
std::string format_string = "~T" + field.name + ": ";
// the char
if (field.type.type->is_boxed) {
format_string += "~A";
} else {
auto ts = TypeSpec(field.type);
if (field.type.type->get_name() == "float") {
format_string += "~f";
} else if (is_integer(ts)) {
format_string += "~d";
} else if (field.type.type->get_name() == "pointer") {
format_string += "#x~X";
}
else if (is_structure) {
format_string += "#<" + field.type.type->get_name() + " @ #x~X>";
}
else {
throw std::runtime_error("don't know how to generate an inspector for field of type " +
field.type.type->get_name());
}
}
format_string += "~%";
if (!field.is_inline) {
auto get_val_ir = make_unique<IR_LoadConstOffset>(
new_func_env->alloc_reg(field.type.type), obj, offset + field.offset,
field.type.type->load_size, field.type.type->load_signed);
auto dst = get_val_ir->dst;
new_func_env->emit(std::move(get_val_ir));
generate_inspector_format_call(format_string, {dst}, new_func_env);
} else {
auto dest_reg = compile_integer_to_gpr(offset + field.offset, new_func_env);
auto get_val_ir = make_unique<IR_IntegerMath>(ADD_64, dest_reg, obj);
new_func_env->emit(std::move(get_val_ir));
generate_inspector_format_call(format_string, {dest_reg}, new_func_env);
}
}
auto return_ir = make_unique<IR_Return>(resolve_to_gpr(obj, new_func_env), return_reg);
return_reg->type = return_ir->value->type;
new_func_env->emit(std::move(return_ir));
new_func_env->finish();
auto obj_env = get_parent_env_of_type<ObjectFileEnv>(new_func_env);
obj_env->functions.push_back(new_func_env);
return place;
}
+586
View File
@@ -0,0 +1,586 @@
#include "Goal.h"
#include "util.h"
MathMode Goal::get_math_mode(TypeSpec& ts) {
TypeSpec bint_ts = get_base_typespec("binteger");
TypeSpec int_ts = get_base_typespec("integer");
TypeSpec float_ts = get_base_typespec("float");
if (bint_ts.typecheck_base_only(ts, types)) {
return MATH_BINT;
} else if (float_ts.typecheck_base_only(ts, types)) {
return MATH_FLOAT;
} else if (int_ts.typecheck_base_only(ts, types)) {
return MATH_INT;
} else {
return MATH_INVALID;
}
}
std::shared_ptr<Place> Goal::to_integer(std::shared_ptr<Place> in, std::shared_ptr<GoalEnv> env) {
(void)env; // we'll need this later on to emit conversion instructions.
TypeSpec int_ts = get_base_typespec("integer");
if (is_binteger(in->type)) {
auto result = env->alloc_reg(get_base_typespec("integer"));
env->emit(make_unique<IR_Set>(result, resolve_to_gpr(in, env)));
return compile_shift(result, compile_integer_to_gpr(3, env), env, false, true);
} else if (int_ts.typecheck_base_only(in->type, types)) {
return in;
}
if (is_float(in->type)) {
auto result = env->alloc_reg(get_base_typespec("integer"));
env->emit(make_unique<IR_FloatToInt>(result, resolve_to_xmm(in, env)));
return result;
} else {
// todo, fail with a slightly better error message.
throw std::runtime_error("can't convert value " + in->print() + " to an integer!");
}
}
std::shared_ptr<Place> Goal::to_binteger(std::shared_ptr<Place> in, std::shared_ptr<GoalEnv> env) {
if (is_binteger(in->type)) {
return in;
} else if (is_integer(in->type)) {
in = resolve_to_gpr(in, env);
auto result = compile_shift(in, compile_integer_to_gpr(3, env), env, true, false);
result->type = get_base_typespec("binteger");
return result;
} else {
throw std::runtime_error("can't convert value " + in->print() + " to an binteger!");
}
}
std::shared_ptr<Place> Goal::to_float(std::shared_ptr<Place> in, std::shared_ptr<GoalEnv> env) {
(void)env;
if (is_float(in->type)) {
return in;
}
if (is_integer(in->type)) {
auto result =
get_parent_env_of_type<FunctionEnv>(env)->alloc_xmm_reg(get_base_typespec("float"));
env->emit(make_unique<IR_IntToFloat>(result, resolve_to_gpr(in, env)));
return result;
} else {
throw std::runtime_error("can't convert value " + in->print() + " to an float!");
}
}
bool Goal::is_number(TypeSpec& ts) {
return get_base_typespec("number").typecheck_base_only(ts, types);
}
bool Goal::is_float(TypeSpec& ts) {
return get_base_typespec("float").typecheck_base_only(ts, types);
}
bool Goal::is_binteger(TypeSpec& ts) {
return get_base_typespec("binteger").typecheck_base_only(ts, types);
}
bool Goal::is_integer(TypeSpec& ts) {
return get_base_typespec("integer").typecheck_base_only(ts, types);
}
bool Goal::is_signed_integer(TypeSpec& ts) {
return get_base_typespec("integer").typecheck_base_only(ts, types) &&
!get_base_typespec("uinteger").typecheck_base_only(ts, types);
}
std::shared_ptr<Place> Goal::to_same_numeric_type(std::shared_ptr<Place> obj,
TypeSpec numeric_type,
std::shared_ptr<GoalEnv> env) {
if (is_float(numeric_type)) {
return to_float(obj, env);
} else if (is_integer(numeric_type)) {
return to_integer(obj, env);
} else if (is_binteger(numeric_type)) {
return to_binteger(obj, env);
}
throw std::runtime_error("couldn't convert to same numeric type: " + numeric_type.print());
}
std::shared_ptr<Place> Goal::compile_add(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
if (rest.type != PAIR) {
throw_compile_error(form, "+ must get at least one argument!");
}
auto first = rest.as_pair()->car;
rest = rest.as_pair()->cdr;
auto first_thing = resolve_to_gpr_or_xmm(compile_error_guard(first, env), env);
auto first_type = first_thing->type;
auto math_type = get_math_mode(first_type);
if (math_type == MATH_INT) {
auto result = env->alloc_reg(first_type);
env->emit(make_unique<IR_Set>(result, first_thing));
for_each_in_list(rest, [&](Object o) {
// todo - constant prop!
env->emit(make_unique<IR_IntegerMath>(
ADD_64, result, resolve_to_gpr(to_integer(compile_error_guard(o, env), env), env)));
});
return result;
} else if (math_type == MATH_FLOAT) {
auto result = get_parent_env_of_type<FunctionEnv>(env)->alloc_xmm_reg(first_type);
env->emit(make_unique<IR_Set>(result, first_thing));
for_each_in_list(rest, [&](Object o) {
env->emit(make_unique<IR_FloatMath>(
ADD_SS, result, resolve_to_xmm(to_float(compile_error_guard(o, env), env), env)));
});
return result;
} else if (math_type == MATH_BINT) {
auto result = env->alloc_reg(get_base_typespec("integer"));
env->emit(make_unique<IR_Set>(result, to_integer(first_thing, env)));
for_each_in_list(rest, [&](Object o) {
// todo - constant prop!
env->emit(make_unique<IR_IntegerMath>(
ADD_64, result, resolve_to_gpr(to_integer(compile_error_guard(o, env), env), env)));
});
result = to_binteger(result, env);
return result;
}
else {
throw_compile_error(form, "invalid math mode");
}
return get_none();
}
std::shared_ptr<Place> Goal::compile_logop(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env,
LogOpKind kind) {
if (rest.type != PAIR) {
throw_compile_error(form, "LOGOP must get at least one argument!");
}
auto first = rest.as_pair()->car;
rest = rest.as_pair()->cdr;
auto first_thing = resolve_to_gpr(compile_error_guard(first, env), env);
auto first_type = first_thing->type;
auto result = env->alloc_reg(first_type);
env->emit(make_unique<IR_Set>(result, first_thing));
auto math_type = get_math_mode(first_type);
if (math_type == MATH_INVALID) {
throw_compile_error(form, "invalid math mode");
}
IntegerMathKind math_kind;
switch (kind) {
case LOGXOR:
math_kind = XOR_64;
break;
case LOGAND:
math_kind = AND_64;
break;
case LOGIOR:
math_kind = OR_64;
break;
default:
throw std::runtime_error("unknown logop kind");
}
for_each_in_list(rest, [&](Object o) {
// todo - constant prop!
switch (math_type) {
case MATH_INT:
env->emit(make_unique<IR_IntegerMath>(
math_kind, result, resolve_to_gpr(to_integer(compile_error_guard(o, env), env), env)));
break;
default:
throw_compile_error(form, "unhandled math for " + o.print());
}
});
return result;
}
std::shared_ptr<Place> Goal::compile_logand(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
return compile_logop(form, rest, env, LOGAND);
}
std::shared_ptr<Place> Goal::compile_logior(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
return compile_logop(form, rest, env, LOGIOR);
}
std::shared_ptr<Place> Goal::compile_logxor(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
return compile_logop(form, rest, env, LOGXOR);
}
std::shared_ptr<Place> Goal::compile_lognot(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
if (rest.type != PAIR) {
throw_compile_error(form, "lognot must get 1 argument");
}
if (rest.as_pair()->cdr.type != EMPTY_LIST) {
throw_compile_error(form, "lognot must get 1 argument");
}
auto val = resolve_to_gpr(compile_error_guard(rest.as_pair()->car, env), env);
auto type = val->type;
auto math_type = get_math_mode(type);
auto result = env->alloc_reg(type);
if (math_type != MATH_INT) {
throw_compile_error(form, "invalid math mode for lognot");
}
env->emit(make_unique<IR_Set>(result, val));
env->emit(make_unique<IR_IntegerMath>(NOT_64, result, result));
return result;
}
std::shared_ptr<Place> Goal::compile_sub(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
if (rest.type != PAIR) {
throw_compile_error(form, "- must get at least one argument!");
}
auto first = rest.as_pair()->car;
rest = rest.as_pair()->cdr;
// auto result = compile_error_guard(first, env);
auto first_thing = compile_error_guard(first, env);
auto first_type = first_thing->type;
auto math_type = get_math_mode(first_type);
if (math_type == MATH_INT) {
auto result = env->alloc_reg(first_type);
if (rest.type == EMPTY_LIST) {
// just negate the argument!
auto zero_ir = make_unique<IR_LoadInteger>();
zero_ir->size = 1;
zero_ir->is_signed = true;
zero_ir->s_value = 0;
zero_ir->value = result;
env->emit(std::move(zero_ir));
env->emit(make_unique<IR_IntegerMath>(SUB_64, result, resolve_to_gpr(first_thing, env)));
return result;
} else {
env->emit(make_unique<IR_Set>(result, resolve_to_gpr(first_thing, env)));
for_each_in_list(rest, [&](Object o) {
// todo - constant prop!
env->emit(make_unique<IR_IntegerMath>(
SUB_64, result, resolve_to_gpr(to_integer(compile_error_guard(o, env), env), env)));
});
return result;
}
} else if (math_type == MATH_FLOAT) {
auto result = get_parent_env_of_type<FunctionEnv>(env)->alloc_xmm_reg(first_type);
if (rest.type == EMPTY_LIST) {
// just negate argument
auto zero = compile_float(0.f, env);
env->emit(make_unique<IR_Set>(result, resolve_to_xmm(zero, env)));
env->emit(make_unique<IR_FloatMath>(SUB_SS, result, resolve_to_xmm(first_thing, env)));
return result;
} else {
env->emit(make_unique<IR_Set>(result, resolve_to_xmm(first_thing, env)));
for_each_in_list(rest, [&](Object o) {
// todo - constant prop!
env->emit(make_unique<IR_FloatMath>(
SUB_SS, result, resolve_to_xmm(to_float(compile_error_guard(o, env), env), env)));
});
return result;
}
} else {
throw_compile_error(form, "unhandled math for " + first.print());
}
return get_none();
}
std::shared_ptr<Place> Goal::compile_mult(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
if (rest.type != PAIR) {
throw_compile_error(form, "* must get at least one argument!");
}
auto first = rest.as_pair()->car;
rest = rest.as_pair()->cdr;
auto first_thing = compile_error_guard(first, env);
auto first_type = first_thing->type;
auto math_type = get_math_mode(first_type);
if (math_type == MATH_INVALID) {
throw_compile_error(form, "invalid math mode");
}
switch (math_type) {
case MATH_INT: {
auto result = env->alloc_reg(first_type);
env->emit(make_unique<IR_Set>(result, resolve_to_gpr(first_thing, env)));
for_each_in_list(rest, [&](Object o) {
// todo - constant prop!
env->emit(make_unique<IR_IntegerMath>(
IMUL_32, result, resolve_to_gpr(to_integer(compile_error_guard(o, env), env), env)));
});
return result;
} break;
case MATH_FLOAT: {
auto fenv = get_parent_env_of_type<FunctionEnv>(env);
auto result = fenv->alloc_xmm_reg(first_type);
env->emit(make_unique<IR_Set>(result, resolve_to_xmm(first_thing, env)));
for_each_in_list(rest, [&](Object o) {
// todo - constant prop!
env->emit(make_unique<IR_FloatMath>(
MUL_SS, result, resolve_to_xmm(to_float(compile_error_guard(o, env), env), env)));
});
return result;
} break;
default:
throw_compile_error(form, "unhandled math for mult" + first_thing->print());
}
return get_none();
}
std::shared_ptr<Place> Goal::compile_divide(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
auto args = goos.get_uneval_args_no_rest(form, rest, 2);
if (args.unnamed_args.size() != 2 || !args.named_args.empty()) {
throw_compile_error(form, "invalid / form");
}
auto first_thing = compile_error_guard(args.unnamed_args.at(0), env);
auto second_thing = compile_error_guard(args.unnamed_args.at(1), env);
auto math_type = get_math_mode(first_thing->type);
auto fenv = get_parent_env_of_type<FunctionEnv>(env);
switch (math_type) {
case MATH_INT: {
first_thing = resolve_to_gpr(first_thing, env);
auto result = env->alloc_reg(first_thing->type);
env->emit(make_unique<IR_Set>(result, first_thing));
RegConstraint result_rax_constraint;
result_rax_constraint.instr_id = fenv->code.size() - 1;
result_rax_constraint.var_id = fenv->vars.size() - 1;
result_rax_constraint.ass.kind = AssignmentKind::REGISTER;
result_rax_constraint.ass.reg_id = RAX;
env->constrain_reg(result_rax_constraint);
env->emit(make_unique<IR_IntegerMath>(IDIV_32, result,
resolve_to_gpr(to_integer(second_thing, env), env)));
return result;
} break;
case MATH_FLOAT: {
auto result = fenv->alloc_xmm_reg(first_thing->type);
env->emit(make_unique<IR_Set>(result, resolve_to_xmm(first_thing, env)));
// todo - constant prop!
env->emit(make_unique<IR_FloatMath>(DIV_SS, result,
resolve_to_xmm(to_float(second_thing, env), env)));
return result;
} break;
default:
throw_compile_error(form, "unhandled math type in divide");
}
return get_none();
}
static IntegerMathKind get_shift_kind(bool is_left, bool is_arith, bool is_variable) {
if (is_left) {
return is_variable ? SHLV_64 : SHL_64;
} else if (is_arith) {
return is_variable ? SARV_64 : SAR_64;
} else {
return is_variable ? SHRV_64 : SHR_64;
}
}
std::shared_ptr<Place> Goal::compile_shift(std::shared_ptr<Place> in,
std::shared_ptr<Place> sa,
std::shared_ptr<GoalEnv> env,
bool is_left,
bool is_arith) {
auto result = env->alloc_reg(in->type);
env->emit(make_unique<IR_Set>(result, resolve_to_gpr(in, env)));
auto sa_reg = env->alloc_reg(sa->type);
env->emit(make_unique<IR_Set>(sa_reg, resolve_to_gpr(sa, env)));
auto fenv = get_parent_env_of_type<FunctionEnv>(env);
RegConstraint sa_con;
sa_con.instr_id = fenv->code.size() - 1;
sa_con.var_id = fenv->vars.size() - 1;
sa_con.ass.kind = AssignmentKind::REGISTER;
sa_con.ass.reg_id = RCX;
env->constrain_reg(sa_con);
auto mathkind = get_shift_kind(is_left, is_arith, true);
auto math_type = get_math_mode(in->type);
switch (math_type) {
case MATH_INT:
env->emit(make_unique<IR_IntegerMath>(mathkind, result, sa_reg));
break;
default:
throw std::runtime_error("unhandled math type in compile_shift");
}
return result;
}
std::shared_ptr<Place> Goal::compile_shift(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env,
bool is_left,
bool is_arith) {
auto args = goos.get_uneval_args_no_rest(form, rest, 2);
if (args.unnamed_args.size() != 2 || !args.named_args.empty()) {
throw_compile_error(form, "invalid shlv form");
}
auto first_thing = compile_error_guard(args.unnamed_args.at(0), env);
auto second_thing = compile_error_guard(args.unnamed_args.at(1), env);
return compile_shift(first_thing, second_thing, env, is_left, is_arith);
}
std::shared_ptr<Place> Goal::compile_mod(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
auto args = goos.get_uneval_args_no_rest(form, rest, 2);
if (args.unnamed_args.size() != 2 || !args.named_args.empty()) {
throw_compile_error(form, "invalid mod form");
}
auto first_thing = compile_error_guard(args.unnamed_args.at(0), env);
first_thing = resolve_to_gpr(first_thing, env);
auto second_thing = compile_error_guard(args.unnamed_args.at(1), env);
auto math_type = get_math_mode(first_thing->type);
auto result = env->alloc_reg(first_thing->type);
env->emit(make_unique<IR_Set>(result, first_thing));
auto fenv = get_parent_env_of_type<FunctionEnv>(env);
RegConstraint result_rax_constraint;
result_rax_constraint.instr_id = fenv->code.size() - 1;
result_rax_constraint.var_id = fenv->vars.size() - 1;
result_rax_constraint.ass.kind = AssignmentKind::REGISTER;
result_rax_constraint.ass.reg_id = RAX;
env->constrain_reg(result_rax_constraint);
switch (math_type) {
case MATH_INT:
env->emit(make_unique<IR_IntegerMath>(IMOD_32, result,
resolve_to_gpr(to_integer(second_thing, env), env)));
break;
default:
throw_compile_error(form, "unhandled math type in mod");
}
return result;
}
std::shared_ptr<Place> Goal::compile_shlv(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
return compile_shift(form, rest, env, true, false);
}
std::shared_ptr<Place> Goal::compile_sarv(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
return compile_shift(form, rest, env, false, true);
}
std::shared_ptr<Place> Goal::compile_shrv(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
return compile_shift(form, rest, env, false, false);
}
std::shared_ptr<Place> Goal::compile_fixed_shift(std::shared_ptr<Place> in,
uint8_t sa,
std::shared_ptr<GoalEnv> env,
bool is_left,
bool is_arith) {
// force in to be in a register
in = resolve_to_gpr(in, env);
// get new register for result (keep same type as input)
auto result = env->alloc_reg(in->type);
// move into result register before shifting
env->emit(make_unique<IR_Set>(result, in));
auto math_type = get_math_mode(in->type);
switch (math_type) {
case MATH_INT:
env->emit(make_unique<IR_IntegerMath>(get_shift_kind(is_left, is_arith, false), result, sa));
break;
default:
ice("unsupported math type in compile_fixed_shift");
}
// do shift!
return result;
}
std::shared_ptr<Place> Goal::compile_fixed_shift(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env,
bool is_left,
bool is_arith) {
(void)form;
auto value = compile_error_guard(pair_car(rest), env);
rest = pair_cdr(rest);
auto shift_amount = compile_to_integer_constant(pair_car(rest), env);
expect_empty_list(pair_cdr(rest));
return compile_fixed_shift(value, shift_amount, env, is_left, is_arith);
}
std::shared_ptr<Place> Goal::compile_shl(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
return compile_fixed_shift(form, rest, env, true, false);
}
std::shared_ptr<Place> Goal::compile_sar(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
return compile_fixed_shift(form, rest, env, false, true);
}
std::shared_ptr<Place> Goal::compile_shr(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
return compile_fixed_shift(form, rest, env, false, false);
}
+25
View File
@@ -0,0 +1,25 @@
#include "GoalLambda.h"
std::string GoalLambdaParam::print() {
return "(" + name + " " + type.print() + (has_default ? (" " + default_value.print()) : "") +
")";
}
std::string GoalLambda::big_print() {
std::string result = "--lambda--\n";
result += "name: " + name;
result += "\n";
result += "Body:\n" + body.print() + "\n";
// result += "Env: " + env->print() + "\n";
// result += "IR:\n";
// for(auto& ir : env->code) {
// result += " " + ir->print() + "\n";
// }
result += "Params:\n";
for (auto& p : params) {
result += " " + p.print() + "\n";
}
return result;
}
+40
View File
@@ -0,0 +1,40 @@
#ifndef JAK_GOALLAMBDA_H
#define JAK_GOALLAMBDA_H
#include <string>
#include "goos/Object.h"
#include "GoalType.h"
enum LambdaKind { FUNCTION, METHOD, BEHAVIOR };
struct GoalLambdaParam {
std::string name;
Object default_value;
TypeSpec type;
bool has_default = false;
bool is_keyword = false;
GoalLambdaParam(const std::string& param_name, TypeSpec type_spec) {
name = param_name;
type = type_spec;
has_default = false;
is_keyword = (param_name.at(0) == ':');
}
GoalLambdaParam() = default;
std::string print();
};
class GoalLambda {
public:
Object body;
// std::shared_ptr<FunctionEnv> env;
std::vector<GoalLambdaParam> params;
std::string name;
std::string big_print();
};
#endif // JAK_GOALLAMBDA_H
+66
View File
@@ -0,0 +1,66 @@
#include "Goal.h"
std::shared_ptr<Place> Goal::compile_listen_to_target(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)env;
std::string ip = "127.0.0.1";
int port = 8112;
bool got_port = false, got_ip = false;
for_each_in_list(rest, [&](Object o) {
if (o.type == STRING) {
if (got_ip) {
throw_compile_error(form, "got multiple strings!");
}
got_ip = true;
ip = o.as_string()->data;
} else if (o.type == INTEGER) {
if (got_port) {
throw_compile_error(form, "got multiple ports!");
}
got_port = true;
port = o.integer_obj.value;
} else {
throw_compile_error(form, "invalid argument to listen-to-target");
}
});
listener.listen_to_target(ip, port);
return get_none();
}
std::shared_ptr<Place> Goal::compile_send_test_data(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)form;
(void)rest;
(void)env;
std::vector<uint32_t> data;
for (int i = 0; i < 512; i++) {
data.push_back(i);
}
listener.send_raw_data((const char*)data.data(), sizeof(uint32_t) * data.size());
return get_none();
}
std::shared_ptr<Place> Goal::compile_reset_target(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)form;
(void)rest;
(void)env;
listener.send_reset();
return get_none();
}
std::shared_ptr<Place> Goal::compile_poke(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)form;
(void)rest;
(void)env;
listener.send_poke();
return get_none();
}
+159
View File
@@ -0,0 +1,159 @@
#include "Goal.h"
std::shared_ptr<Place> Goal::compile_print_type(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
auto first = rest.as_pair()->car;
if (rest.as_pair()->cdr.type != EMPTY_LIST) {
throw_compile_error(form, "invalid print-type");
}
auto result = compile_error_guard(first, env);
printf("CODE: %s\nTYPE: %s\nPLACE: %s\n", first.print().c_str(), result->type.print().c_str(),
result->print().c_str());
return get_none();
}
std::shared_ptr<Place> Goal::compile_quote(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
auto first = rest.as_pair()->car;
if (rest.as_pair()->cdr.type != EMPTY_LIST) {
throw_compile_error(form, "invalid print-type");
}
switch (first.type) {
case SYMBOL:
return compile_get_sym_obj(first.as_symbol()->name, env);
break;
case EMPTY_LIST: {
auto empty_pair = compile_get_sym_obj("_empty_", env);
empty_pair->type = get_base_typespec("pair");
return empty_pair;
}
default:
throw_compile_error(rest, "cannot quote this yet");
}
return get_none();
}
std::shared_ptr<Place> Goal::compile_defconstant(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)env;
if (rest.type != PAIR) {
throw_compile_error(form, "invalid defglobalconstant");
}
auto sym = rest.as_pair()->car;
if (sym.type != SYMBOL) {
throw_compile_error(form, "invalid defglobalconstant");
}
auto ssym = sym.as_symbol();
rest = rest.as_pair()->cdr;
if (rest.type != PAIR) {
throw_compile_error(form, "invalid defglobalconstant");
}
auto value = rest.as_pair()->car;
rest = rest.as_pair()->cdr;
if (rest.type != EMPTY_LIST) {
throw_compile_error(form, "invalid defglobalconstant");
}
// GOAL constant
global_constants[ssym] = value;
return get_none();
}
std::shared_ptr<Place> Goal::compile_current_method_type(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
if (rest.type != EMPTY_LIST) {
throw_compile_error(form, "current-method-type accepts no arguments");
}
return compile_get_sym_obj(get_parent_env_of_type<FunctionEnv>(env)->method_of_type_name, env);
}
/*!
* Try to find a macro with the given name in the goal_goos_env of GOOS.
*/
bool Goal::try_getting_macro_from_goos(Object macro_name, Object* dest) {
Object macro_obj;
bool got_macro = false;
try {
macro_obj = goos.eval_symbol(macro_name, goos.goal_goos_env.as_env());
if (macro_obj.type == MACRO) {
got_macro = true;
}
} catch (std::runtime_error& e) {
got_macro = false;
}
if (got_macro) {
*dest = macro_obj;
}
return got_macro;
}
std::shared_ptr<Place> Goal::compile_goos_macro(Object o,
Object macro_obj,
Object rest,
std::shared_ptr<GoalEnv> env) {
// we got a macro, so first set up for macro expansion by creating a GOOS environment where the
// GOAL stuff is bound to the macro params:
GoosArgs args = goos.get_macro_args(o, rest);
// todo define keywords here
auto macro = macro_obj.as_macro();
auto mac_env_obj = EnvironmentObject::make_new();
auto mac_env = mac_env_obj.as_env();
mac_env->parent_env = goos.global_environment.as_env();
// check argument count
if (!macro->has_rest && args.unnamed_args.size() != macro->unnamed_args.size()) {
throw_compile_error(o, "Macro " + macro->name + " requires " +
std::to_string(macro->unnamed_args.size()) + " but got " +
std::to_string(args.unnamed_args.size()));
} else if (macro->has_rest && args.unnamed_args.size() < macro->unnamed_args.size()) {
throw_compile_error(o, "Macro " + macro->name + " requires at least " +
std::to_string(macro->unnamed_args.size()) + " but only got " +
std::to_string(args.unnamed_args.size()));
}
// populate macro env.
uint32_t i = 0;
for (; i < macro->unnamed_args.size(); i++) {
mac_env->vars[macro->unnamed_args.at(i).as_symbol()] = args.unnamed_args.at(i);
}
if (macro->has_rest) {
if (i < args.unnamed_args.size()) {
Object empty = EmptyListObject::make_new();
Object rest_head = PairObject::make_new(args.unnamed_args[i], empty);
Object last = rest_head;
i++;
for (; i < args.unnamed_args.size(); i++) {
last.as_pair()->cdr = PairObject::make_new(args.unnamed_args[i], empty);
last = last.as_pair()->cdr;
}
mac_env->vars[macro->rest_args.as_symbol()] = rest_head;
} else {
mac_env->vars[macro->rest_args.as_symbol()] = EmptyListObject::make_new();
}
}
goos.goal_to_goos.enclosing_method_type =
get_parent_env_of_type<FunctionEnv>(env)->method_of_type_name;
auto goos_result = goos.eval_list_return_last(macro->body, macro->body, mac_env);
goos.goal_to_goos.reset();
// then compile the result of compiling the GOOS eval'd macro body.
return compile_error_guard(goos_result, env);
}
+76
View File
@@ -0,0 +1,76 @@
#include <util.h>
#include "Goal.h"
static int32_t get_offset_of_method(uint8_t id) {
return 16 + id * 4;
}
std::shared_ptr<Place> Goal::compile_get_method_of_type(TypeSpec type,
const std::string& name,
std::shared_ptr<GoalEnv> env) {
auto info = types.get_method_info(type.type->get_name(), name);
auto get_sym_ir = make_unique<IR_GetSymbolValue>();
TypeSpec symbol_type = get_base_typespec("symbol");
get_sym_ir->symbol = std::make_shared<SymbolPlace>(type.type->get_name(), symbol_type);
get_sym_ir->dest = env->alloc_reg(get_base_typespec("type"));
auto dest = get_sym_ir->dest;
env->emit(std::move(get_sym_ir));
// next get the method pointer.
// todo - not this
int32_t offset = get_offset_of_method(info.id);
auto result = env->alloc_reg(info.type);
env->emit(make_unique<IR_LoadConstOffset>(result, dest, offset, 4, false));
return result;
}
std::shared_ptr<Place> Goal::compile_get_method_of_object(std::shared_ptr<Place> object,
const std::string& method_name,
std::shared_ptr<GoalEnv> env) {
auto& compile_time_type = object->type;
object = resolve_to_gpr(object, env);
// look up method info using compile time type
auto method_info = types.get_method_info(compile_time_type.type->get_name(), method_name);
// will contain the most accurate type at runtime
std::shared_ptr<Place> runtime_type;
if (is_basic(compile_time_type)) {
// can lookup at runtime!
runtime_type = env->alloc_reg(get_base_typespec("type"));
env->emit(make_unique<IR_LoadConstOffset>(runtime_type, object, -4, 4, false));
} else {
// can't look up at runtime
runtime_type = compile_get_sym_val(compile_time_type.type->get_name(), env);
}
// todo - not this!
int32_t offset = get_offset_of_method(method_info.id);
auto method = env->alloc_reg(method_info.type); // so the method has the correct type
env->emit(make_unique<IR_LoadConstOffset>(method, runtime_type, offset, 4, false));
return method;
}
std::shared_ptr<Place> Goal::compile_method(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)form;
// (method obj method-name)
// OR
// (method type method-name)
auto arg = pair_car(rest);
rest = pair_cdr(rest);
auto method_name = symbol_string(pair_car(rest));
expect_empty_list(pair_cdr(rest));
if (arg.type == SYMBOL) {
auto kv = types.types.find(symbol_string(arg));
if (kv != types.types.end()) {
return compile_get_method_of_type(compile_typespec(arg), method_name, env);
}
}
auto obj = compile_error_guard(arg, env);
return compile_get_method_of_object(obj, method_name, env);
}
+66
View File
@@ -0,0 +1,66 @@
#include "Goal.h"
#include "util.h"
// (new 'global ...
// (new 'static ...
// (new 'stack
std::shared_ptr<Place> Goal::compile_new(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
auto allocation = quoted_sym_as_string(pair_car(rest));
rest = pair_cdr(rest);
// auto type_of_obj = get_base_typespec(quoted_sym_as_string(pair_car(rest)));
auto type_as_string = quoted_sym_as_string(pair_car(rest));
rest = pair_cdr(rest);
if (allocation == "global" || allocation == "debug") {
if (type_as_string == "inline-array") {
auto elt_type = get_base_typespec(quoted_sym_as_string(pair_car(rest)));
rest = pair_cdr(rest);
auto elt_count_obj = pair_car(rest);
expect_empty_list(pair_cdr(rest));
// if(elt_count_obj.type != INTEGER) {
// throw_compile_error(form, "array size must be integer");
// }
// auto elt_count = elt_count_obj.integer_obj.value;
auto elt_count = compile_to_integer_constant(elt_count_obj, env);
auto mem_size = elt_count * elt_type.type->size; // it's an inline array
// printf("new inline array of size %ld (= %ld * %d)", mem_size, elt_count,
// elt_type.type->size);
auto malloc_func = compile_get_sym_val("malloc", env);
std::vector<std::shared_ptr<Place>> args;
args.push_back(compile_get_sym_obj(allocation, env));
args.push_back(compile_integer_to_gpr(mem_size, env));
auto result = compile_real_function_call(form, malloc_func, args, env);
result->type = TypeSpec(get_base_typespec("inline-array").type, {elt_type});
return result;
} else {
auto type_of_obj = get_base_typespec(type_as_string);
std::vector<std::shared_ptr<Place>> args;
// allocation
args.push_back(compile_get_sym_obj(allocation, env));
// type
args.push_back(compile_get_sym_val(type_of_obj.type->get_name(), env));
// the other arguments
for_each_in_list(rest, [&](Object o) { args.push_back(compile_error_guard(o, env)); });
auto new_method = compile_get_method_of_type(type_of_obj, "new", env);
auto new_obj = compile_real_function_call(form, new_method, args, env);
new_obj->type = type_of_obj;
return new_obj;
}
} else if (allocation == "static") {
auto type_of_obj = get_base_typespec(type_as_string);
return compile_make_static_object_of_type(form, type_of_obj, rest, env);
}
throw_compile_error(form, "unsupported new form");
return get_none();
}
+111
View File
@@ -0,0 +1,111 @@
#include "Goal.h"
TypeSpec Goal::compile_typespec(const Object& form) {
if (form.type == SYMBOL) {
return get_base_typespec(symbol_string(form));
}
if (form.type == PAIR) {
std::vector<TypeSpec> args;
auto head = compile_typespec(pair_car(form));
auto rest = pair_cdr(form);
for_each_in_list(rest, [&](Object o) { args.push_back(compile_typespec(o)); });
return TypeSpec(head.type, args);
}
throw_compile_error(form, "invalid typespec");
return {};
}
/*!
* Goal type cast.
* TODO - cast integer/binteger/float correctly
*/
std::shared_ptr<Place> Goal::compile_the(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
if (rest.type == EMPTY_LIST) {
throw_compile_error(form, "the must get two args");
}
auto type = rest.as_pair()->car;
rest = rest.as_pair()->cdr;
if (rest.type == EMPTY_LIST) {
throw_compile_error(form, "the must get two args");
}
auto obj = rest.as_pair()->car;
rest = rest.as_pair()->cdr;
if (rest.type != EMPTY_LIST) {
throw_compile_error(form, "the must get two args");
}
auto desired_ts = compile_typespec(type);
auto base = compile_error_guard(obj, env);
std::shared_ptr<Place> result = base;
if (is_binteger(desired_ts)) {
if (is_number(base->type)) {
result = to_binteger(base, env);
}
}
if (is_float(desired_ts)) {
if (is_number(base->type)) {
result = to_float(base, env);
}
}
// todo - do we really want all descendants of integer types to do this?
if (is_integer(desired_ts) && !is_binteger(desired_ts)) {
if (is_number(base->type)) {
result = to_integer(base, env);
}
}
/*
auto original = compile_error_guard(obj, env);
auto result = env->alloc_reg(get_base_typespec(type.as_symbol()->name));
env->emit(std::make_shared<IR_Set>(result, original));
*/
result = std::make_shared<AliasPlace>(desired_ts, result);
return result;
}
/*!
* Goal type cast.
* TODO - cast integer/binteger/float correctly
*/
std::shared_ptr<Place> Goal::compile_the_as(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
if (rest.type == EMPTY_LIST) {
throw_compile_error(form, "the must get two args");
}
auto type = rest.as_pair()->car;
rest = rest.as_pair()->cdr;
if (rest.type == EMPTY_LIST) {
throw_compile_error(form, "the must get two args");
}
auto obj = rest.as_pair()->car;
rest = rest.as_pair()->cdr;
if (rest.type != EMPTY_LIST) {
throw_compile_error(form, "the must get two args");
}
auto desired_ts = compile_typespec(type);
auto base = compile_error_guard(obj, env);
std::shared_ptr<Place> result = base;
result = std::make_shared<AliasPlace>(desired_ts, result);
return result;
}
+27
View File
@@ -0,0 +1,27 @@
#include "Goal.h"
std::shared_ptr<Place> Goal::compile_car(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
auto arg = pair_car(rest);
rest = pair_cdr(rest);
if (rest.type != EMPTY_LIST) {
throw_compile_error(form, "can't do this car");
}
return std::make_shared<PairPlace>(get_base_typespec("object"), true,
compile_error_guard(arg, env));
}
std::shared_ptr<Place> Goal::compile_cdr(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
auto arg = pair_car(rest);
rest = pair_cdr(rest);
if (rest.type != EMPTY_LIST) {
throw_compile_error(form, "can't do this cdr");
}
return std::make_shared<PairPlace>(get_base_typespec("object"), false,
compile_error_guard(arg, env));
}
+7
View File
@@ -0,0 +1,7 @@
#include "GoalPlace.h"
static std::shared_ptr<Place> none = std::make_shared<NonePlace>(TypeSpec(nullptr));
std::shared_ptr<Place> get_none() {
return none;
}
+217
View File
@@ -0,0 +1,217 @@
/*!
* @file: GoalPlace.h
* A "Place" is a typed reference to a register, memory, etc...
*/
#ifndef JAK_GOALVAR_H
#define JAK_GOALVAR_H
#include <string>
#include <memory>
#include "GoalType.h"
#include "codegen/ColoringAssignment.h"
#include "GoalLambda.h"
#include "StaticObject.h"
// Top Level Place
class Place {
public:
Place(TypeSpec& c) : type(c) {}
virtual std::string print() = 0;
virtual bool is_register() { return false; }
virtual ColoringInput get_assignment() {
throw std::runtime_error("invalid Place get_assignment: " + print());
}
TypeSpec type;
};
// Special Place indicating "don't care" values.
// Reading the value of a none place is undefined.
class NonePlace : public Place {
public:
explicit NonePlace(TypeSpec none_ctype) : Place(none_ctype) {}
std::string print() { return "none"; }
};
// Place for a General Purpose Register.
class GprPlace : public Place {
public:
GprPlace(int id, TypeSpec& ct) : Place(ct), identity(id) {}
bool is_register() override { return true; }
ColoringInput get_assignment() override {
ColoringInput ass;
ass.kind = RegisterKind::REG_GPR;
ass.id = identity;
return ass;
}
int identity;
std::string print() { return "var-" + std::to_string(identity); }
};
// "Alias" for a GPR which allows a GPR to referenced as a different type.
class GprAliasPlace : public Place {
public:
GprAliasPlace(std::shared_ptr<Place> _parent, TypeSpec& ct) : Place(ct), parent(_parent) {}
ColoringInput get_assignment() override { return parent->get_assignment(); }
bool is_register() override { return true; }
std::string print() { return std::string("alias-of ") + parent->print(); }
std::shared_ptr<Place> parent;
};
// Place for an XMM Floating Point register
class XmmPlace : public Place {
public:
XmmPlace(int id, TypeSpec& ct) : Place(ct), identity(id) {}
bool is_register() override { return true; }
ColoringInput get_assignment() override {
ColoringInput ass;
ass.kind = RegisterKind::REG_XMM_FLOAT;
ass.id = identity;
return ass;
}
int identity;
std::string print() { return "xvar-" + std::to_string(identity); }
};
// "Alias" for a XMM which allows a XMM to referenced as a different type.
class XmmAliasPlace : public Place {
public:
XmmAliasPlace(std::shared_ptr<Place> _parent, TypeSpec& ct) : Place(ct), parent(_parent) {}
bool is_register() override { return true; }
ColoringInput get_assignment() override { return parent->get_assignment(); }
std::string print() { return std::string("alias-of ") + parent->print(); }
std::shared_ptr<Place> parent;
};
// Place for a GOAL Symbol
class SymbolPlace : public Place {
public:
SymbolPlace(const std::string& s, TypeSpec& ct) : Place(ct), name(s) {}
std::string name;
std::string print() { return "<" + name + ">"; }
};
class FunctionEnv;
// Place for a GOAL Lambda.
// Contains both a GoalLambda, and the FunctionEnv, if the lambda generates a real function.
class LambdaPlace : public Place {
public:
LambdaPlace(TypeSpec ts) : Place(ts) {}
std::string print() override {
if (lambda.name.empty()) {
return "~noname~";
} else {
return "lambda-" + lambda.name;
}
}
GoalLambda lambda;
std::shared_ptr<FunctionEnv> func = nullptr;
};
// Place for a static variable.
class StaticPlace : public Place {
public:
StaticPlace(TypeSpec ts, std::shared_ptr<StaticObject> o) : Place(ts), object(o) {}
std::shared_ptr<StaticObject> object;
std::string print() override { return "static place " + object->print(); }
};
// Place indicating a location in memory. Just a wrapper around some address that give it the right
// type (address may internally be an integer, but MemoryBasePlace should be something else).
class MemoryBasePlace : public Place {
public:
MemoryBasePlace(TypeSpec ts, std::shared_ptr<Place> _base) : Place(ts), base(_base) {}
std::shared_ptr<Place> base;
std::string print() override { return base->print(); }
};
// Place indicating a constant offset from a memory address.
class MemoryOffsetConstPlace : public MemoryBasePlace {
public:
MemoryOffsetConstPlace(TypeSpec ts, int32_t _offset, std::shared_ptr<Place> _base)
: MemoryBasePlace(ts, _base), offset(_offset) {}
int32_t offset;
std::string print() override { return MemoryBasePlace::print() + "+" + std::to_string(offset); }
};
// Place indicating a variable offset from a memory address
class MemoryOffsetVarPlace : public MemoryBasePlace {
public:
MemoryOffsetVarPlace(TypeSpec ts, std::shared_ptr<Place> _offset, std::shared_ptr<Place> _base)
: MemoryBasePlace(ts, _base), offset(_offset) {}
std::shared_ptr<Place> offset;
std::string print() override { return MemoryBasePlace::print() + "+" + offset->print(); }
};
// Place indicating the value stored in memory at a certain location.
class MemoryDerefPlace : public MemoryBasePlace {
public:
MemoryDerefPlace(TypeSpec ts, int32_t _size, bool _is_signed, std::shared_ptr<Place> _base)
: MemoryBasePlace(ts, _base), size(_size), is_signed(_is_signed) {}
int32_t size;
bool is_signed;
std::string print() override {
return "[" + base->print() + "] s?" + (is_signed ? "y" : "f") + "sz: " + std::to_string(size);
}
};
class PairPlace : public MemoryBasePlace {
public:
PairPlace(TypeSpec ts, bool _is_car, std::shared_ptr<Place> _base)
: MemoryBasePlace(ts, _base), is_car(_is_car) {}
std::string print() override {
return std::string("(") + (is_car ? "car" : "cdr") + " " + base->print() + ")";
}
bool is_car;
};
class AliasPlace : public Place {
public:
AliasPlace(TypeSpec ts, std::shared_ptr<Place> _base) : Place(ts), base(_base) {}
std::shared_ptr<Place> base;
std::string print() override { return "alias-of " + base->print(); }
};
class IntegerConstantPlace : public Place {
public:
IntegerConstantPlace(TypeSpec ts, int64_t _value) : Place(ts), value(_value) {}
int64_t value;
std::string print() override { return "integer constant " + std::to_string(value); }
};
class BitfieldPlace : public Place {
public:
BitfieldPlace(GoalBitField _field, std::shared_ptr<Place> _base)
: Place(_field.type), base(_base), field(_field) {}
std::shared_ptr<Place> base;
GoalBitField field;
std::string print() override {
return "[bit-field " + field.print() + " of " + base->print() + "]";
}
};
std::shared_ptr<Place> get_none();
#endif // JAK_GOALVAR_H
+106
View File
@@ -0,0 +1,106 @@
#include <cstring>
#include "Goal.h"
std::shared_ptr<Place> Goal::compile_make_static_object_of_type(const Object& form,
TypeSpec& type,
Object field_defs,
std::shared_ptr<GoalEnv> env) {
std::shared_ptr<StaticStructure> obj;
if (is_basic(type)) {
auto basic_obj = std::make_shared<StaticBasic>();
basic_obj->type_name = type.type->get_name();
obj = std::move(basic_obj);
} else {
obj = std::make_shared<StaticStructure>();
}
obj->segment = MAIN_SEGMENT;
obj->data.resize(type.type->size);
auto result = std::make_shared<StaticPlace>(type, obj);
env->get_statics().push_back(result);
auto struct_type = std::dynamic_pointer_cast<StructureType>(type.type);
if (!struct_type) {
throw_compile_error(form, "cannot static new a non-structure type!"); // ... for now
}
while (field_defs.type != EMPTY_LIST) {
auto field_name_def = symbol_string(pair_car(field_defs));
field_defs = pair_cdr(field_defs);
auto field_value = pair_car(field_defs);
field_defs = pair_cdr(field_defs);
if (field_name_def.at(0) != ':') {
throw_compile_error(form,
"expected field def name to start with :, instead got " + field_name_def);
}
field_name_def = field_name_def.substr(1);
GoalField* d_field = nullptr;
for (auto& field : struct_type->fields) {
if (field.name == field_name_def) {
d_field = &field;
break;
}
}
if (!d_field) {
throw_compile_error(
form, "type " + struct_type->name + " does not have a field named " + field_name_def);
}
if (d_field->is_dynamic || d_field->is_inline || d_field->is_array) {
throw_compile_error(form, "in new, dynamic/inline/array fields are not yet supported");
}
// for now, no type-checking...
auto field_offset = d_field->offset;
auto field_size = d_field->type.type->load_size;
assert(field_offset + field_size <= type.type->size);
// TODO - warn on overflow?
if (is_integer(d_field->type)) {
auto value = compile_to_integer_constant(field_value, env);
switch (field_size) {
case 1:
case 2:
case 4:
case 8:
memcpy(obj->data.data() + field_offset, &value, field_size);
break;
default:
throw_compile_error(form, "can't store an integer in this field: " + d_field->print());
}
// if(field_value.type == INTEGER) {
// switch(field_size) {
// case 1:
// case 2:
// case 4:
// case 8:
// memcpy(obj->data.data() + field_offset, &field_value.integer_obj.value,
// field_size); break;
// default:
// throw_compile_error(form, "can't store an integer in this field: " +
// d_field->print());
// }
} else if (field_value.type == SYMBOL &&
(symbol_string(field_value) == "#t" || symbol_string(field_value) == "#f")) {
if (field_size != 4) {
throw_compile_error(form, "invalid set symbol on field " + d_field->print());
}
obj->symbol_ptr_recs[symbol_string(field_value)].push_back(field_offset);
uint32_t value = 0xffffffff;
memcpy(obj->data.data() + field_offset, &value, 4);
} else {
throw_compile_error(
form, "can't use this object as a static field definition: " + field_value.print());
}
}
return result;
}
+365
View File
@@ -0,0 +1,365 @@
#include "GoalType.h"
#include "TypeContainer.h"
static const std::string default_methods[] = {
"new", "delete", "print", "inspect", "length", "asize-of", "copy", "relocate", "mem-usage"};
std::string GoalField::print() {
auto result = type.type->get_name() + " " + name + " :offset " + std::to_string(offset);
if (is_inline) {
result += " :inline";
}
return result;
}
std::string GoalBitField::print() {
auto result =
type.print() + " " + name + " off " + std::to_string(offset) + " sz " + std::to_string(size);
return result;
}
void TypeContainer::fill_with_default_types() {
// NONE (not runtime)
types["none"] = std::make_shared<SimpleType>(0, nullptr, "none", "");
// OBJECT
auto object_type = std::make_shared<SimpleType>(4, nullptr, "object");
object_type->is_parent_type = true;
types["object"] = object_type;
// OBJECT 64 (not runtime)
auto object64_type = std::make_shared<SimpleType>(8, types["object"], "object64", "object");
object64_type->is_parent_type = true;
types["object64"] = object64_type;
// STRUCTURE
auto structure_type = std::make_shared<StructureType>(4, types["object"], "structure");
structure_type->is_parent_type = true;
types["structure"] = structure_type;
// BASIC
// can't set the type field's type yet
auto basic_type = std::make_shared<BasicType>(4, types["structure"], "basic", nullptr);
basic_type->is_parent_type = true;
types["basic"] = basic_type;
// TYPE (out of order...)
// can't set the type field's type yet
auto type_type = std::make_shared<BasicType>(0x38, types["basic"], "type", nullptr);
types["type"] = type_type;
// now we can.
basic_type->fields.front().type = TypeSpec(type_type);
type_type->fields.front().type = TypeSpec(type_type);
// SYMBOL (out of order...)
auto symbol_type = std::make_shared<SymbolType>(types["basic"], type_type);
symbol_type->fields.emplace_back(types["object"], "value", 4);
types["symbol"] = symbol_type;
// STRING
auto string_type = std::make_shared<BasicType>(8, types["basic"], "string", type_type);
string_type->dynamic = true;
types["string"] = string_type;
// BOOLEAN (not runtime)
auto boolean_type = std::make_shared<BooleanType>(types["symbol"], type_type);
types["boolean"] = boolean_type;
// FUNCTION
auto function_type = std::make_shared<FunctionType>(types["basic"], types["type"]);
function_type->dynamic = true;
types["function"] = function_type;
// TODO - VU FUNCTION
// TODO - LINK BLOCK
// KHEAP
auto kheap_type = std::make_shared<StructureType>(16, types["structure"], "kheap");
types["kheap"] = kheap_type;
// ARRAY
auto array_type = std::make_shared<BasicType>(16, types["basic"], "array", type_type);
array_type->dynamic = true;
types["array"] = array_type;
// PAIR
auto pair_type = std::make_shared<SimpleType>(8, types["object"], "pair");
pair_type->is_value_type = true;
types["pair"] = pair_type;
// PROCESS TREE
auto process_tree_type =
std::make_shared<BasicType>(32, types["basic"], "process-tree", type_type);
types["process-tree"] = process_tree_type;
// PROCESS
auto process_type = std::make_shared<BasicType>(112, types["process-tree"], "process", type_type);
types["process"] = process_type;
// THREAD (this one is redefined in gkernel-h.gc)
auto thread_type = std::make_shared<BasicType>(0x28, types["basic"], "thread", type_type);
types["thread"] = thread_type;
// CONNECTABLE
auto connectable_type = std::make_shared<StructureType>(16, types["structure"], "connectable");
types["connectable"] = connectable_type;
// STACK FRAME
auto stack_frame_type =
std::make_shared<BasicType>(0xc, types["basic"], "stack-frame", type_type);
types["stack-frame"] = stack_frame_type;
// TODO - FILE-STREAM
// POINTER
auto pointer_type = std::make_shared<SimpleType>(4, types["object"], "pointer");
pointer_type->is_value_type = true;
pointer_type->load_signed = false;
types["pointer"] = pointer_type;
// NUMBER
auto number_type = std::make_shared<SimpleType>(8, types["object"], "number");
number_type->is_parent_type = true;
number_type->is_value_type = true;
types["number"] = number_type;
// FLOAT
auto float_type = std::make_shared<SimpleType>(4, types["number"], "float");
float_type->is_value_type = true;
float_type->minimum_alignment = 4;
float_type->load_size = 4;
float_type->load_xmm_32_prefer = true;
float_type->load_signed = false;
types["float"] = float_type;
// INTEGER
auto integer_type = std::make_shared<SimpleType>(8, types["number"], "integer");
integer_type->is_parent_type = true;
integer_type->is_value_type = true;
types["integer"] = integer_type;
// BINTEGER
auto binteger_type = std::make_shared<SimpleType>(8, types["integer"], "binteger");
binteger_type->is_value_type = true;
types["binteger"] = binteger_type;
// SINTEGER
auto sinteger_type = std::make_shared<SimpleType>(8, types["integer"], "sinteger");
sinteger_type->is_parent_type = true;
sinteger_type->is_value_type = true;
types["sinteger"] = sinteger_type;
// INT8
auto int8_type = std::make_shared<SimpleType>(1, types["sinteger"], "int8");
int8_type->is_value_type = true;
int8_type->load_size = 1;
int8_type->load_signed = true;
types["int8"] = int8_type;
// INT16
auto int16_type = std::make_shared<SimpleType>(2, types["sinteger"], "int16");
int16_type->is_value_type = true;
int16_type->load_size = 2;
int16_type->load_signed = true;
int16_type->minimum_alignment = 2;
types["int16"] = int16_type;
// INT32
auto int32_type = std::make_shared<SimpleType>(4, types["sinteger"], "int32");
int32_type->is_value_type = true;
int32_type->load_size = 4;
int32_type->load_signed = true;
types["int32"] = int32_type;
// INT64
auto int64_type = std::make_shared<SimpleType>(8, types["sinteger"], "int64");
int64_type->is_value_type = true;
int64_type->load_size = 8;
int64_type->load_signed = true;
int64_type->minimum_alignment = 8;
types["int64"] = int64_type;
// UINTEGER
auto uinteger_type = std::make_shared<SimpleType>(8, types["integer"], "uinteger");
uinteger_type->is_parent_type = true;
uinteger_type->is_value_type = true;
types["uinteger"] = uinteger_type;
// UINT8
auto uint8_type = std::make_shared<SimpleType>(1, types["uinteger"], "uint8");
uint8_type->is_value_type = true;
uint8_type->load_size = 1;
uint8_type->load_signed = false;
types["uint8"] = uint8_type;
// UINT16
auto uint16_type = std::make_shared<SimpleType>(2, types["uinteger"], "uint16");
uint16_type->is_value_type = true;
uint16_type->load_size = 2;
uint16_type->load_signed = false;
types["uint16"] = uint16_type;
// UINT32
auto uint32_type = std::make_shared<SimpleType>(4, types["uinteger"], "uint32");
uint32_type->is_value_type = true;
uint32_type->load_size = 4;
uint32_type->load_signed = false;
types["uint32"] = uint32_type;
// UINT64
auto uint64_type = std::make_shared<SimpleType>(8, types["uinteger"], "uint64");
uint64_type->is_value_type = true;
uint64_type->load_size = 8;
uint64_type->load_signed = false;
uint64_type->minimum_alignment = 8;
types["uint64"] = uint64_type;
// INLINE-ARRAY (not runtime)
auto inline_array_type = std::make_shared<SimpleType>(4, types["pointer"], "inline-array");
types["inline-array"] = inline_array_type;
// TYPE
type_type->fields.emplace_back(types["symbol"], "symbol", 0 + 4);
type_type->fields.emplace_back(types["type"], "parent", 4 + 4);
type_type->fields.emplace_back(types["uint16"], "asize", 8 + 4);
type_type->fields.emplace_back(types["uint16"], "padded-size", 10 + 4);
type_type->fields.emplace_back(types["uint16"], "heap-base", 12 + 4);
type_type->fields.emplace_back(types["uint16"], "num-methods", 14 + 4);
type_type->fields.emplace_back(types["function"], "methods", 16 + 4, false, true);
// STRING
string_type->fields.emplace_back(types["int32"], "allocated-length", 4);
string_type->fields.emplace_back(types["uint8"], "data", 8, false, true);
// KHEAP
kheap_type->fields.emplace_back(types["pointer"], "base", 0);
kheap_type->fields.emplace_back(types["pointer"], "top", 4);
kheap_type->fields.emplace_back(types["pointer"], "cur", 8);
kheap_type->fields.emplace_back(types["pointer"], "top-base", 12);
// ARRAY
array_type->fields.emplace_back(types["int32"], "length", 4 + 0);
array_type->fields.emplace_back(types["int32"], "allocated-length", 4 + 4);
array_type->fields.emplace_back(types["type"], "elt-type", 4 + 8);
// PROCESS TREE
process_tree_type->fields.emplace_back(types["basic"], "name", 0 + 4);
process_tree_type->fields.emplace_back(types["int32"], "mask", 4 + 4);
process_tree_type->fields.emplace_back(TypeSpec(types["pointer"], {types["process-tree"]}),
"parent", 8 + 4);
process_tree_type->fields.emplace_back(TypeSpec(types["pointer"], {types["process-tree"]}),
"brother", 12 + 4);
process_tree_type->fields.emplace_back(TypeSpec(types["pointer"], {types["process-tree"]}),
"child", 16 + 4);
process_tree_type->fields.emplace_back(
TypeSpec(types["pointer"], {TypeSpec(types["process-tree"])}), "ppointer", 20 + 4);
process_tree_type->fields.emplace_back(types["process-tree"], "self", 24 + 4);
// PROCESS
process_type->fields.emplace_back(types["basic"], "name", 0 + 4);
process_type->fields.emplace_back(types["int32"], "mask", 4 + 4);
process_type->fields.emplace_back(TypeSpec(types["pointer"], {types["process-tree"]}), "parent",
8 + 4);
process_type->fields.emplace_back(TypeSpec(types["pointer"], {types["process-tree"]}), "brother",
12 + 4);
process_type->fields.emplace_back(TypeSpec(types["pointer"], {types["process-tree"]}), "child",
16 + 4);
process_type->fields.emplace_back(TypeSpec(types["pointer"], {TypeSpec(types["process-tree"])}),
"ppointer", 20 + 4);
process_type->fields.emplace_back(types["process-tree"], "self", 24 + 4);
process_type->fields.emplace_back(types["basic"], "pool", 0x1c + 4);
process_type->fields.emplace_back(types["basic"], "status", 0x20 + 4);
process_type->fields.emplace_back(types["int32"], "pid", 0x24 + 4);
process_type->fields.emplace_back(types["thread"], "main-thread", 0x28 + 4);
process_type->fields.emplace_back(types["thread"], "top-thread", 0x2c + 4);
process_type->fields.emplace_back(types["basic"], "entity", 0x30 + 4);
process_type->fields.emplace_back(types["basic"], "state", 0x34 + 4);
process_type->fields.emplace_back(types["function"], "trans-hook", 0x38 + 4);
process_type->fields.emplace_back(types["function"], "post-hook", 0x3c + 4);
process_type->fields.emplace_back(types["basic"], "event-hook", 0x40 + 4);
process_type->fields.emplace_back(types["int32"], "allocated-length", 0x44 + 4);
process_type->fields.emplace_back(types["basic"], "next-state", 0x48 + 4);
process_type->fields.emplace_back(types["pointer"], "heap-base", 0x4c + 4);
process_type->fields.emplace_back(types["pointer"], "heap-top", 0x50 + 4);
process_type->fields.emplace_back(types["pointer"], "heap-cur", 0x54 + 4);
process_type->fields.emplace_back(types["stack-frame"], "stack-frame-top", 0x58 + 4);
process_type->fields.emplace_back(types["connectable"], "connection-list", 0x5c + 4);
process_type->fields.back().is_inline = true;
// process_type->fields.emplace_back(types["basic"], "pool", 0x1c + 4); todo - connection list
process_type->fields.emplace_back(types["uint8"], "stack", 0x6c + 4);
process_type->fields.back().is_dynamic = true;
stack_frame_type->fields.emplace_back(types["basic"], "name", 4);
stack_frame_type->fields.emplace_back(types["stack-frame"], "next", 8);
add_method("process-tree", "new", {"type", "symbol", "basic"});
for (auto& kv : types) {
for (auto& name : default_methods) {
add_method(kv.first, name, TypeSpec(types["function"]));
}
}
add_method("type", "new", {"type", "symbol", "type", "integer"});
// for(auto& type : types) {
// printf("----%s----\n%s\n\n", type.first.c_str(), type.second->print().c_str());
// }
// string's size (u32), data (inline array) field.
// symbol's value, hash, string field.
// boolean's value, hash, string field.
}
bool TypeSpec::typecheck_base_only(TypeSpec& more_specific, TypeContainer& types) {
auto other = more_specific.type;
if (other->get_name() == type->get_name()) {
return true;
}
while (other->get_name() != "object") {
other = types.lookup(other->parent);
if (other->get_name() == type->get_name()) {
return true;
}
}
return false;
}
TypeSpec::TypeSpec(Object& o, TypeContainer& types) {
switch (o.type) {
case SYMBOL: {
auto t = types.types.find(o.as_symbol()->name);
if (t == types.types.end()) {
throw std::runtime_error("typespec cannot be created because the type is unknown: " +
o.print());
}
type = t->second;
} break;
default:
throw std::runtime_error("can't make typespec from " + o.print());
}
}
std::string TypeSpec::print() {
if (ts_args.empty()) {
return type->get_name();
} else {
std::string result = "(" + type->get_name();
for (auto& x : ts_args) {
result += " " + x.print();
}
return result + ")";
}
}
bool BitfieldType::find_field(const std::string& field_name, GoalBitField* field) {
for (auto& f : fields) {
if (f.name == field_name) {
*field = f;
return true;
}
}
return false;
}
+302
View File
@@ -0,0 +1,302 @@
#ifndef JAK_GOALTYPE_H
#define JAK_GOALTYPE_H
#include <string>
#include <vector>
#include <stdexcept>
#include <unordered_map>
#include <memory>
#include "goos/Goos.h"
#include "util.h"
constexpr int BASIC_OFFSET = 4;
constexpr int PSIZE_ALIGN = 16;
class TypeContainer;
class GoalType {
public:
GoalType(int sz, std::shared_ptr<GoalType> p) : size(sz) {
if (p) {
parent = p->get_name();
}
}
virtual std::string get_name() = 0;
// virtual std::string as_runtime_type() = 0;
virtual std::string print() = 0;
bool is_parent_type = false;
bool is_boxed = false;
bool is_value_type = false;
int minimum_alignment = 4;
int alignment_offset = 0;
int load_size = 4;
bool load_signed = true;
bool load_xmm_32_prefer = false;
bool pack_structure_type = false;
int size;
int get_size_in_inline_array() {
if (pack_structure_type) {
return size;
} else {
return align(size, 16, 0);
}
}
int get_size_in_non_inline_array() {
if (is_value_type) {
return size;
}
return 4;
}
std::string parent;
};
class TypeSpec {
public:
TypeSpec(std::shared_ptr<GoalType> gt) : type(gt) {}
TypeSpec(std::shared_ptr<GoalType> gt, std::vector<TypeSpec> args) : type(gt), ts_args(args) {}
TypeSpec() = default;
TypeSpec(Object& o, TypeContainer& types);
bool operator!=(const TypeSpec& other) const { return !(other == *this); }
bool operator==(const TypeSpec& other) const {
if (other.type != type)
return false;
if (other.ts_args.size() != ts_args.size())
return false;
for (uint32_t i = 0; i < ts_args.size(); i++) {
if (other.ts_args[i] != ts_args[i])
return false;
}
return true;
}
std::string print();
bool typecheck_base_only(TypeSpec& more_specific, TypeContainer& types);
std::shared_ptr<GoalType> type;
std::vector<TypeSpec> ts_args;
};
struct GoalBitField {
TypeSpec type;
std::string name;
int offset = -1;
int size = -1;
GoalBitField() = default;
GoalBitField(TypeSpec _type, std::string _name, int _offset, int _size)
: type(_type), name(std::move(_name)), offset(_offset), size(_size) {}
std::string print();
};
struct GoalField {
TypeSpec type;
std::string name;
int offset;
bool is_inline;
bool is_dynamic = false;
bool is_array = false;
int array_size = 0;
GoalField() = default;
GoalField(const TypeSpec& p,
const std::string& n,
int off,
bool iln = false,
bool dyn = false,
int arr_size = 0)
: type(p), name(n), offset(off), is_inline(iln), is_dynamic(dyn), array_size(arr_size) {
if (arr_size || is_dynamic) {
is_array = true;
}
}
std::string print();
};
// a type with just a name, for stuff like object, object64, none...
class SimpleType : public GoalType {
public:
SimpleType(int sz,
std::shared_ptr<GoalType> p,
const std::string& type_name,
const std::string& runtime_type_name)
: GoalType(sz, p), name(type_name), runtime_name(runtime_type_name) {}
SimpleType(int sz, std::shared_ptr<GoalType> p, const std::string& type_name)
: GoalType(sz, p), name(type_name), runtime_name(type_name) {}
std::string name;
std::string runtime_name;
std::string get_name() override { return name; }
// std::string as_runtime_type() override {
// return runtime_name;
// }
std::string print() override {
auto result = "Simple Type: " + name;
if (name != runtime_name) {
result += " (runtime: " + runtime_name + ")";
}
return result;
}
};
class BitfieldType : public GoalType {
public:
BitfieldType(const std::shared_ptr<GoalType>& _base, std::string _name)
: GoalType(_base->size, _base), name(std::move(_name)) {
assert(_base->is_value_type);
is_value_type = true;
minimum_alignment = _base->minimum_alignment;
load_signed = false; // todo...
load_size = _base->load_size;
}
std::string name;
std::vector<GoalBitField> fields;
std::string get_name() override { return name; }
std::string print() override {
auto result = "Bit-Field Type: " + name + "\n";
result += "align " + std::to_string(minimum_alignment) + " size " + std::to_string(load_size) +
" " + std::to_string(size);
for (auto& f : fields) {
result += "\n field: " + f.print() + "\n";
}
return result;
}
bool find_field(const std::string& name, GoalBitField* field);
};
class StructureType : public GoalType {
public:
StructureType(int sz, std::shared_ptr<GoalType> p, const std::string& type_name)
: GoalType(sz, p), name(type_name) {
minimum_alignment = 16;
}
std::string name;
std::vector<GoalField> fields;
bool dynamic = false;
std::string get_name() override { return name; }
// virtual std::string as_runtime_type() override {
// return "structure";
// }
virtual std::string print() override {
auto result = "Structure Type: " + name + "\n";
if (dynamic) {
result += " :dynamic\n";
}
for (auto& f : fields) {
result += " field: " + f.print() + "\n";
}
return result;
}
void inherit_fields(std::shared_ptr<StructureType> pp) {
fields.clear();
for (auto& f : pp->fields) {
fields.push_back(f);
}
minimum_alignment = pp->minimum_alignment;
dynamic = pp->dynamic;
}
};
class BasicType : public StructureType {
public:
BasicType(int sz,
std::shared_ptr<GoalType> p,
const std::string& type_name,
std::shared_ptr<GoalType> type_of_type)
: StructureType(sz, p, type_name) {
fields.emplace_back(type_of_type, "type", 0);
is_boxed = true;
}
std::string get_name() override { return name; }
// virtual std::string as_runtime_type() override {
// return name;
// }
virtual std::string print() override {
auto result = "Basic Type: " + name + "\n";
for (auto& f : fields) {
result += " field: " + f.print() + "\n";
}
return result;
}
};
// symbol will need its own stuff
class SymbolType : public BasicType {
public:
SymbolType(std::shared_ptr<GoalType> p, std::shared_ptr<GoalType> type_of_type)
: BasicType(8, p, "symbol", type_of_type) {}
std::string get_name() override { return name; }
// virtual std::string as_runtime_type() override {
// return name;
// }
virtual std::string print() override {
auto result = "Symbol Type: " + name + "\n";
for (auto& f : fields) {
result += " field: " + f.print() + "\n";
}
return result;
}
};
class BooleanType : public SymbolType {
public:
BooleanType(std::shared_ptr<GoalType> p, std::shared_ptr<GoalType> type_of_type)
: SymbolType(p, type_of_type) {
name = "boolean";
}
virtual std::string print() override {
auto result = "Boolean Type: " + name + "\n";
for (auto& f : fields) {
result += " field: " + f.print() + "\n";
}
return result;
}
};
class FunctionType : public BasicType {
public:
FunctionType(std::shared_ptr<GoalType> p, std::shared_ptr<GoalType> type_of_type)
: BasicType(4, p, "function", type_of_type) {}
virtual std::string print() override {
auto result = "Function Type: " + name + "\n";
for (auto& f : fields) {
result += " field: " + f.print() + "\n";
}
return result;
}
};
#endif // JAK_GOALTYPE_H
+145
View File
@@ -0,0 +1,145 @@
/*!
* @file GoalUtil.cpp
* Various GOAL utility functions related to types which do not cleanly fit anywhere else.
*/
#include "Goal.h"
/*!
* Can source be stored in destination?
* Checks only that the base type matches.
* Throws a compile error if not.
*/
void Goal::typecheck_base_only(const Object& form,
TypeSpec& destination_type,
TypeSpec& source_type,
const std::string& error) {
// source can be more specific
if (!destination_type.typecheck_base_only(source_type, types)) {
throw_compile_error(form, "Type check failure on " + error + ":\n" + destination_type.print() +
" and " + source_type.print());
}
}
void Goal::typecheck_for_set(const Object& form,
TypeSpec& destination_type,
TypeSpec& source_type,
const std::string& error) {
if (source_type.type->get_name() == "integer") {
if (is_integer(destination_type)) {
return;
}
}
if (source_type.type->get_name() == "boolean") {
return;
}
typecheck_base_only(form, destination_type, source_type, error);
}
/*!
* Get a base TypeSpec for a type with the given name.
* Error if the type doesn't exist.
*/
TypeSpec Goal::get_base_typespec(const std::string& name) {
auto t = types.types.find(name);
if (t == types.types.end()) {
throw std::runtime_error("could not find type " + name);
}
TypeSpec ts(t->second);
return ts;
}
TypeSpec Goal::get_base_of_inline_array(TypeSpec ts) {
if (ts.type->get_name() != "inline-array") {
throw std::runtime_error("tried to get_base_of_inline_array of " + ts.print());
}
if (ts.ts_args.size() != 1) {
throw std::runtime_error("invalid inline-array ts: " + ts.print());
}
return ts.ts_args.front();
}
TypeSpec Goal::get_base_of_pointer(TypeSpec ts) {
if (ts.type->get_name() != "pointer") {
throw std::runtime_error("tried to get_base_of_pointer of " + ts.print());
}
if (ts.ts_args.size() != 1) {
throw std::runtime_error("invalid pointer ts: " + ts.print());
}
return ts.ts_args.front();
}
/*!
* Get the list of all parent types.
*/
std::vector<std::shared_ptr<GoalType>> Goal::get_parents(std::shared_ptr<GoalType> t) {
auto parent = t->parent;
std::vector<std::shared_ptr<GoalType>> result;
result.push_back(t);
while (!parent.empty()) {
auto pt = get_base_typespec(parent).type;
result.push_back(pt);
parent = pt->parent;
}
return result;
}
TypeSpec Goal::lowest_common_ancestor(TypeSpec a, TypeSpec b) {
if (a.type == b.type) {
if (a == b) {
return a;
} else {
return get_base_typespec(a.type->get_name());
}
}
auto a_up = get_parents(a.type);
auto b_up = get_parents(b.type);
int ai = a_up.size() - 1;
int bi = b_up.size() - 1;
std::shared_ptr<GoalType> parent_type = nullptr;
while (ai >= 0 && bi >= 0) {
if (a_up.at(ai) == b_up.at(bi)) {
parent_type = a_up.at(ai);
} else {
break;
}
ai--;
bi--;
}
if (!parent_type) {
throw std::runtime_error("invalid types in lowest_common_ancestor: " + a.print() + " AND " +
b.print());
}
return get_base_typespec(parent_type->get_name());
}
TypeSpec Goal::lowest_common_ancestor(std::vector<TypeSpec> ts) {
if (ts.empty()) {
return get_base_typespec("none");
}
for (auto& x : ts) {
if (x.type->get_name() == "none") {
return get_base_typespec("none");
}
}
TypeSpec result = ts.front();
for (uint32_t i = 1; i < ts.size(); i++) {
result = lowest_common_ancestor(result, ts.at(i));
}
return result;
}
bool Goal::is_basic(TypeSpec& ts) {
return get_base_typespec("basic").typecheck_base_only(ts, types);
}
+183
View File
@@ -0,0 +1,183 @@
#include <logger/Logger.h>
#include "Goal.h"
/*!
* Helper to iterate through a Goos list.
*/
void Goal::for_each_in_list(Object list, const std::function<void(Object)>& f) {
while (list.type == PAIR) {
auto lap = list.as_pair();
f(lap->car);
list = lap->cdr;
}
if (list.type != EMPTY_LIST) {
throw_compile_error(list, "invalid list in for_each_in_list");
}
}
/*!
* Get the length of a list.
*/
int Goal::list_length(Object list) {
int l = 0;
for_each_in_list(list, [&](Object o) {
(void)o;
l++;
});
return l;
}
SymbolTable& Goal::get_symbol_table() {
return goos.reader.symbolTable;
}
std::shared_ptr<StringObject> Goal::as_string_obj(Object obj) {
if (obj.type != STRING) {
throw_compile_error(obj, "expected " + obj.print() + " to be a string.");
}
return obj.as_string();
}
std::string Goal::quoted_sym_as_string(Object obj) {
auto expected_quote = pair_car(obj);
if (symbol_string(expected_quote) != "quote") {
throw_compile_error(obj, "expected to be a quoted symbol");
}
obj = pair_cdr(obj);
auto item = pair_car(obj);
expect_empty_list(pair_cdr(obj));
return symbol_string(item);
}
std::string Goal::as_string(Object obj) {
return as_string_obj(obj)->data;
}
std::vector<std::string> Goal::as_string_list(Object obj) {
std::vector<std::string> result;
while (obj.type != ObjectType::EMPTY_LIST) {
result.push_back(as_string(pair_car(obj)));
obj = pair_cdr(obj);
}
return result;
}
std::shared_ptr<PairObject> Goal::as_pair_obj(Object obj) {
if (obj.type != PAIR) {
throw_compile_error(obj, "expected to be a pair: " + obj.print());
}
return obj.as_pair();
}
Object Goal::pair_car(Object obj) {
return as_pair_obj(obj)->car;
}
Object Goal::pair_cdr(Object obj) {
return as_pair_obj(obj)->cdr;
}
void Goal::expect_empty_list(Object obj) {
if (obj.type != EMPTY_LIST) {
throw_compile_error(obj, "expected to be an empty list");
}
}
std::shared_ptr<SymbolObject> Goal::as_symbol_obj(Object obj) {
if (obj.type != SYMBOL) {
throw_compile_error(obj, "expected to be be a symbol: " + obj.print());
}
return obj.as_symbol();
}
std::string Goal::symbol_string(Object obj) {
return as_symbol_obj(obj)->name;
}
/*!
* Get a constant named `name`, or throw a compile error on error_form.
*/
Object Goal::get_constant_or_error(Object error_form, const std::string& name) {
auto kv = global_constants.find(SymbolObject::make_new(get_symbol_table(), name).as_symbol());
if (kv == global_constants.end()) {
throw_compile_error(error_form, "failed to find constant named " + name);
}
return kv->second;
}
bool Goal::write_to_binary_file(const std::string& name, void* data, uint32_t size) {
FILE* fp = fopen(name.c_str(), "wb");
if (!fp) {
return false;
}
if (fwrite(data, size, 1, fp) != 1) {
return false;
}
fclose(fp);
return true;
}
Object Goal::read_from_file(const std::string& file_name) {
return goos.reader.read_from_file(file_name);
}
Object Goal::read_from_stdin_prompt(const std::string& prompt_name) {
return goos.reader.read_from_stdin(prompt_name);
}
Object Goal::read_from_string(const std::string& str) {
return goos.reader.read_from_string(str);
}
#include "DefaultConfig.h"
void Goal::setup_default_config() {
for (auto& e : default_config) {
auto read = read_from_string(e.second);
// read will be in the form (top-level <stuff>)
read = pair_cdr(read);
if (pair_cdr(read).type != EMPTY_LIST) {
ice("The default for configuration option \"" + e.first +
"\" is invalid. Check DefaultConfig.h");
}
set_config(e.first, pair_car(read));
}
}
Object Goal::get_config(const std::string& name) {
auto kv = config_data.find(name);
if (kv == config_data.end()) {
ice("No compiler configuration entry named " + name + " could be found. Check DefaultConfig.h");
}
return kv->second.value;
}
void Goal::ice(const std::string& error) {
gLogger.log(MSG_ICE, "[ICE] %s\n", error.c_str());
throw std::runtime_error("ICE");
}
void Goal::set_config(const std::string& name, const Object& value) {
config_data[name] = {name, value};
}
std::shared_ptr<Place> Goal::compile_set_config(const Object& form,
Object rest,
std::shared_ptr<GoalEnv> env) {
(void)form;
(void)env;
auto name = symbol_string(pair_car(rest));
rest = pair_cdr(rest);
auto value = pair_car(rest);
expect_empty_list(pair_cdr(rest));
set_config(name, value);
return get_none();
}
+478
View File
@@ -0,0 +1,478 @@
#include "IR.h"
#include "GoalEnv.h"
// std::string IR_Define::print() {
// return "DEFINE-SYMBOL " + sym->print() + " " + value->print() + "\n";
//}
std::string IR_LoadInteger::print() {
std::string result = "LOAD_INT ";
if (is_signed) {
result += std::to_string(s_value);
} else {
result += std::to_string(us_value);
}
result += " SIZE " + std::to_string((int)size);
result += " INTO " + value->print();
return result;
}
std::string IR_Return::print() {
return "RETURN " + value->print() + " in register " + dest->print();
}
std::string IR_Goto_Label::print() {
return "GOTO " + label->print();
}
// RegAllocInstr IR_Define::to_rai() {
// RegAllocInstr instr;
//}
RegAllocInstr IR_Set::to_rai() {
RegAllocInstr rai;
rai.write.push_back(dest->get_assignment());
rai.read.push_back(src->get_assignment());
// the "move" flag is used so the coloring system can attempt to eliminate moves.
// however, if the src and dst register kinds are different, it cannot be eliminated.
// so we don't consider this case to be a "move".
if (dest->get_assignment().kind == src->get_assignment().kind) {
rai.is_move = true;
}
return rai;
}
std::string IR_GetSymbolValue::print() {
return "GET-SYM-VAL " + symbol->print() + " IN " + dest->print();
}
RegAllocInstr IR_GetSymbolValue::to_rai() {
RegAllocInstr rai;
rai.write.push_back(dest->get_assignment());
return rai;
}
RegAllocInstr IR_Goto_Label::to_rai() {
RegAllocInstr rai;
if (!resolved) {
throw std::runtime_error("IR GOTO label has an unresolved label at coloring time!");
}
rai.jumps.push_back(label->idx);
rai.fallthrough = false;
return rai;
}
std::string IR_ConditionalBranch::print() {
return "BR " + cond.print() + " " + label->print();
}
RegAllocInstr IR_ConditionalBranch::to_rai() {
auto rai = cond.to_rai();
rai.jumps.push_back(label->idx);
if (!resolved) {
throw std::runtime_error(
"IR_ConditionalBranch label has an unresolved label at coloring time!");
}
return rai;
}
std::string GoalCondition::print() {
switch (kind) {
case NOT_EQUAL_64:
return a->print() + " != " + b->print();
case EQUAL_64:
return a->print() + " == " + b->print();
case LEQ_64:
return a->print() + " <= " + b->print();
case GEQ_64:
return a->print() + " >= " + b->print();
case LT_64:
return a->print() + " < " + b->print();
case GT_64:
return a->print() + " > " + b->print();
default:
throw std::runtime_error("unknown condition type in GoalCondition::print()");
}
}
RegAllocInstr GoalCondition::to_rai() {
RegAllocInstr rai;
switch (kind) {
case NOT_EQUAL_64:
case EQUAL_64:
case LEQ_64:
case GEQ_64:
case LT_64:
case GT_64:
rai.read.push_back(a->get_assignment());
rai.read.push_back(b->get_assignment());
break;
default:
throw std::runtime_error("unknown condition type in GoalCondition::to_rai()");
}
return rai;
}
RegAllocInstr IR_LoadInteger::to_rai() {
RegAllocInstr rai;
if (value->is_register())
rai.write.push_back(value->get_assignment());
return rai;
}
RegAllocInstr IR_Return::to_rai() {
RegAllocInstr rai;
if (dest->is_register())
rai.write.push_back(dest->get_assignment());
if (value->is_register())
rai.read.push_back(value->get_assignment());
if (value->is_register() && (dest->get_assignment().kind == value->get_assignment().kind)) {
rai.is_move = true;
}
return rai;
}
RegAllocInstr IR_SetSymbolValue::to_rai() {
RegAllocInstr rai;
if (value->is_register())
rai.read.push_back(value->get_assignment());
return rai;
}
std::string IR_FunctionCall::print() {
std::string result = "CALL " + func_in->print() + " ARGS ";
for (auto& arg : args) {
result += arg->print() + " ";
}
result += "INTO " + dest->print();
return result;
}
RegAllocInstr IR_FunctionCall::to_rai() {
RegAllocInstr rai;
rai.read.push_back(func_in->get_assignment());
rai.write.push_back(func_call->get_assignment());
for (auto& arg : args) {
rai.read.push_back(arg->get_assignment());
}
rai.write.push_back(dest->get_assignment());
// todo is this the right set of registers?
for (int i = 0; i < 8; i++) {
ColoringAssignment ass;
ass.kind = REGISTER;
ass.reg_id = ARG_REGS[i];
rai.clobber.push_back(ass);
}
for (int i = XMM0; i < XMM15; i++) {
ColoringAssignment ass;
ass.kind = REGISTER;
ass.reg_id = i;
rai.clobber.push_back(ass);
}
// duh...
ColoringAssignment ass;
ass.kind = REGISTER;
ass.reg_id = RAX;
rai.clobber.push_back(ass);
return rai;
}
void IR_FunctionCall::add_constraints_to_program(std::vector<RegConstraint>& constraints,
int my_id) {
for (uint32_t i = 0; i < args.size(); i++) {
RegConstraint c;
c.var_id = args[i]->get_assignment().id;
c.instr_id = my_id;
c.ass.kind = REGISTER;
c.ass.reg_id = ARG_REGS[i];
constraints.push_back(c);
}
// function call reg
RegConstraint fc;
fc.var_id = func_call->get_assignment().id;
fc.instr_id = my_id;
fc.ass.kind = REGISTER;
fc.ass.reg_id = T9_REG;
constraints.push_back(fc);
// funciton return
RegConstraint rc;
rc.var_id = dest->get_assignment().id;
rc.instr_id = my_id;
rc.ass.kind = REGISTER;
rc.ass.reg_id = RET_REG;
constraints.push_back(rc);
}
std::string IR_StaticVarAddr::print() {
return "GET-STATIC& " + dest->print() + " FROM " + src->print();
}
RegAllocInstr IR_StaticVarAddr::to_rai() {
RegAllocInstr rai;
rai.write.push_back(dest->get_assignment());
return rai;
}
std::string IR_StaticVar32::print() {
return "GET-STATIC-32 " + dest->print() + " FROM " + src->print();
}
RegAllocInstr IR_StaticVar32::to_rai() {
RegAllocInstr rai;
rai.write.push_back(dest->get_assignment());
return rai;
}
std::string IR_FunctionAddr::print() {
return "GET-FNC " + dest->print() + " FRM " + src->print();
}
RegAllocInstr IR_FunctionAddr::to_rai() {
RegAllocInstr rai;
rai.write.push_back(dest->get_assignment());
return rai;
}
std::string IR_FunctionBegin::print() {
return "FUNCTION BEGIN " + std::to_string(nargs);
}
RegAllocInstr IR_FunctionBegin::to_rai() {
RegAllocInstr rai;
for (auto& var : lambda->func->params) {
rai.write.push_back(var.second->get_assignment());
}
return rai;
}
std::string IR_IntegerMath::print() {
std::string result;
switch (math_kind) {
case ADD_64:
result = "ADD64 ";
break;
case SUB_64:
result = "SUB64 ";
break;
case IMUL_32:
result = "IMU32 ";
break;
case IDIV_32:
result = "IDV32 ";
break;
case SHLV_64:
result = "SHLV64 ";
break;
case SARV_64:
result = "SARV64 ";
break;
case SHRV_64:
result = "SHRV64 ";
break;
case SHL_64:
result = "SHL64 ";
break;
case SAR_64:
result = "SAR64 ";
break;
case SHR_64:
result = "SHR64 ";
break;
case IMOD_32:
result = "IMOD32 ";
break;
case OR_64:
result = "OR64 ";
break;
case AND_64:
result = "AND64 ";
break;
case XOR_64:
result = "XOR64 ";
break;
case NOT_64:
result = "NOT64 ";
break;
default:
throw std::runtime_error("unknown kind of IntegerMath");
}
result += d->print() + " " + a0->print();
return result;
}
RegAllocInstr IR_IntegerMath::to_rai() {
RegAllocInstr rai;
if (math_kind == SHL_64 || math_kind == SHR_64 || math_kind == SAR_64) {
rai.write.push_back(d->get_assignment());
rai.read.push_back(d->get_assignment());
return rai;
}
if (math_kind == IDIV_32) {
ColoringAssignment ca;
ca.kind = AssignmentKind::REGISTER;
ca.reg_id = RDX;
rai.exclusive.push_back(ca);
}
if (math_kind == IMOD_32) {
ColoringAssignment ca;
ca.kind = AssignmentKind::REGISTER;
ca.reg_id = RDX;
rai.exclusive.push_back(ca);
}
rai.write.push_back(d->get_assignment());
rai.read.push_back(d->get_assignment());
if (math_kind != NOT_64) {
rai.read.push_back(a0->get_assignment());
}
return rai;
}
std::string IR_FloatMath::print() {
std::string result;
switch (math_kind) {
case MUL_SS:
result = "MULSS ";
break;
case DIV_SS:
result = "DIVSS ";
break;
case SUB_SS:
result = "SUBSS ";
break;
case ADD_SS:
result = "ADDSS ";
break;
default:
throw std::runtime_error("unknown kind of FloatMath");
}
result += d->print() + " " + a0->print();
return result;
}
RegAllocInstr IR_FloatMath::to_rai() {
RegAllocInstr rai;
rai.write.push_back(d->get_assignment());
rai.read.push_back(a0->get_assignment());
rai.read.push_back(d->get_assignment());
return rai;
}
std::string IR_GetSymbolObj::print() {
return "GET-SYM-OBJ " + sym->print() + " IN " + dest->print();
}
RegAllocInstr IR_GetSymbolObj::to_rai() {
RegAllocInstr rai;
rai.write.push_back(dest->get_assignment());
return rai;
}
// std::string IR_Xmm2Gpr::print() {
// return "MOVD " + dst->print() + " " + src->print();
//}
//
// RegAllocInstr IR_Xmm2Gpr::to_rai() {
// RegAllocInstr rai;
// rai.write.push_back(dst->get_assignment());
// rai.read.push_back(src->get_assignment());
// return rai;
//}
std::string IR_LoadConstOffset::print() {
return "LOAD" + std::to_string(size) + " " + dst->print() + " (" + src->print() + " " +
std::to_string(offset) + ")";
}
RegAllocInstr IR_LoadConstOffset::to_rai() {
RegAllocInstr rai;
rai.write.push_back(dst->get_assignment());
rai.read.push_back(src->get_assignment());
return rai;
}
std::string IR_StoreConstOffset::print() {
return "STORE" + std::to_string(size) + " " + val->print() + " -> (" + mem->print() + " " +
std::to_string(offset) + ")";
}
RegAllocInstr IR_StoreConstOffset::to_rai() {
RegAllocInstr rai;
rai.read.push_back(val->get_assignment());
rai.read.push_back(mem->get_assignment());
return rai;
}
RegAllocInstr IR_IntToFloat::to_rai() {
RegAllocInstr rai;
rai.read.push_back(src->get_assignment());
rai.write.push_back(dest->get_assignment());
return rai;
}
RegAllocInstr IR_FloatToInt::to_rai() {
RegAllocInstr rai;
rai.read.push_back(src->get_assignment());
rai.write.push_back(dest->get_assignment());
return rai;
}
std::string IR_GetReturnAddressPointer::print() {
return "GET-RA-PTR " + dest->print();
}
RegAllocInstr IR_GetReturnAddressPointer::to_rai() {
RegAllocInstr rai;
rai.write.push_back(dest->get_assignment());
return rai;
}
std::string IR_Asm::print() {
return "ASM";
}
RegAllocInstr IR_Asm::to_rai() {
RegAllocInstr rai;
switch (asm_kind) {
case IR_Asm::PUSH:
case IR_Asm::POP:
rai.read.push_back(args.at(0)->get_assignment());
break;
case IR_Asm::JMP:
rai.read.push_back(args.at(0)->get_assignment());
break;
case IR_Asm::RET:
break;
case IR_Asm::RET_REGISTER:
rai.read.push_back(args.at(0)->get_assignment());
break;
case IR_Asm::SUB:
rai.read.push_back(args.at(0)->get_assignment());
rai.read.push_back(args.at(1)->get_assignment());
rai.write.push_back(args.at(0)->get_assignment());
break;
default:
throw std::runtime_error("unknown asm mode in ir_asm to_rai");
}
return rai;
}
+422
View File
@@ -0,0 +1,422 @@
#ifndef JAK_IR_H
#define JAK_IR_H
#include <string>
#include <cassert>
#include "regalloc/RegAllocInstr.h"
#include "GoalPlace.h"
#include "Label.h"
enum IR_Kind {
SET_SYMBOL_VALUE,
GET_SYMBOL_VALUE,
LOAD_INTEGER,
STATIC_VAR_ADDR,
STATIC_VAR_32,
RETURN,
GOTO_LABEL,
SET,
FUNCTION_CALL,
FUNC_ADDR,
FUNCTION_BEGIN,
INTEGER_MATH,
FLOAT_MATH,
GET_SYMBOL_OBJ,
CONDITIONAL_BRANCH,
XMM_TO_GPR,
LOAD_CONST_OFFSET,
STORE_CONST_OFFSET,
GET_RETURN_ADDRESS_POINTER,
FLOAT_TO_INT,
INT_TO_FLOAT,
ASM,
IR_NULL
};
class IR {
public:
virtual std::string print() = 0;
virtual RegAllocInstr to_rai() = 0;
virtual void add_constraints_to_program(std::vector<RegConstraint>& constraints, int my_id) {
(void)constraints;
(void)my_id;
}
IR_Kind kind;
};
class IR_SetSymbolValue : public IR {
public:
IR_SetSymbolValue() { kind = SET_SYMBOL_VALUE; }
std::string print() { return "SET-SYM-VAL " + dest->print() + " TO " + value->print(); }
RegAllocInstr to_rai();
std::shared_ptr<Place> value;
std::shared_ptr<SymbolPlace> dest;
};
class IR_GetSymbolValue : public IR {
public:
IR_GetSymbolValue() { kind = GET_SYMBOL_VALUE; }
std::string print();
RegAllocInstr to_rai();
bool sext = false;
std::shared_ptr<Place> dest;
std::shared_ptr<SymbolPlace> symbol;
};
class IR_GetSymbolObj : public IR {
public:
IR_GetSymbolObj() { kind = GET_SYMBOL_OBJ; }
IR_GetSymbolObj(std::shared_ptr<Place> _dest, std::shared_ptr<SymbolPlace> _sym)
: dest(_dest), sym(_sym) {
kind = GET_SYMBOL_OBJ;
}
std::string print();
RegAllocInstr to_rai();
std::shared_ptr<Place> dest;
std::shared_ptr<SymbolPlace> sym;
};
// always a variable
class IR_Set : public IR {
public:
IR_Set() { kind = SET; }
IR_Set(std::shared_ptr<Place> _dst, std::shared_ptr<Place> _src) : dest(_dst), src(_src) {
kind = SET;
}
std::string print() { return "SET " + dest->print() + " TO " + src->print(); }
RegAllocInstr to_rai();
std::shared_ptr<Place> dest;
std::shared_ptr<Place> src;
};
class IR_FloatToInt : public IR {
public:
IR_FloatToInt() { kind = FLOAT_TO_INT; }
IR_FloatToInt(std::shared_ptr<Place> _dst, std::shared_ptr<Place> _src) : dest(_dst), src(_src) {
kind = FLOAT_TO_INT;
}
std::string print() override { return "F2I " + dest->print() + " " + src->print(); }
RegAllocInstr to_rai() override;
std::shared_ptr<Place> dest;
std::shared_ptr<Place> src;
};
class IR_IntToFloat : public IR {
public:
IR_IntToFloat() { kind = INT_TO_FLOAT; }
IR_IntToFloat(std::shared_ptr<Place> _dst, std::shared_ptr<Place> _src) : dest(_dst), src(_src) {
kind = INT_TO_FLOAT;
}
std::string print() override { return "I2F " + dest->print() + " " + src->print(); }
RegAllocInstr to_rai() override;
std::shared_ptr<Place> dest;
std::shared_ptr<Place> src;
};
class IR_Goto_Label : public IR {
public:
IR_Goto_Label() { kind = GOTO_LABEL; }
std::string print();
RegAllocInstr to_rai();
std::shared_ptr<Label> label;
bool resolved = false;
};
class IR_LoadInteger : public IR {
public:
IR_LoadInteger() { kind = LOAD_INTEGER; }
std::string print();
RegAllocInstr to_rai();
union {
uint64_t us_value;
int64_t s_value;
};
bool is_signed;
uint8_t size;
std::shared_ptr<Place> value;
};
class IR_StaticVarAddr : public IR {
public:
IR_StaticVarAddr() { kind = STATIC_VAR_ADDR; }
std::string print();
RegAllocInstr to_rai();
std::shared_ptr<Place> dest;
std::shared_ptr<Place> src;
};
class IR_StaticVar32 : public IR {
public:
IR_StaticVar32() { kind = STATIC_VAR_32; }
std::string print();
RegAllocInstr to_rai();
std::shared_ptr<Place> dest;
std::shared_ptr<Place> src;
};
class IR_FunctionAddr : public IR {
public:
IR_FunctionAddr() { kind = FUNC_ADDR; }
std::string print();
RegAllocInstr to_rai();
std::shared_ptr<Place> dest;
std::shared_ptr<Place> src;
};
class IR_Return : public IR {
public:
IR_Return(std::shared_ptr<Place> v, std::shared_ptr<Place> dst) : value(v), dest(dst) {
kind = RETURN;
}
std::string print();
RegAllocInstr to_rai();
std::shared_ptr<Place> value, dest;
void add_constraints_to_program(std::vector<RegConstraint>& constraints, int my_id) override {
RegConstraint c;
if (std::dynamic_pointer_cast<NonePlace>(dest)) {
return;
}
assert(dest->is_register());
c.var_id = dest->get_assignment().id;
c.instr_id = my_id;
c.ass.kind = REGISTER;
c.ass.reg_id = RAX;
constraints.push_back(c);
}
};
class IR_FunctionBegin : public IR {
public:
IR_FunctionBegin(std::shared_ptr<LambdaPlace> l) : lambda(l) { kind = FUNCTION_BEGIN; }
std::string print();
RegAllocInstr to_rai();
int nargs = -1;
std::shared_ptr<LambdaPlace> lambda;
};
class IR_FunctionCall : public IR {
public:
IR_FunctionCall(std::shared_ptr<Place> func_call_reg,
std::shared_ptr<Place> func_in_reg,
std::shared_ptr<Place> dest_reg,
const std::vector<std::shared_ptr<Place>>& arg_regs) {
kind = FUNCTION_CALL;
func_call = func_call_reg;
func_in = func_in_reg;
dest = dest_reg;
args = arg_regs;
}
std::string print();
RegAllocInstr to_rai();
void add_constraints_to_program(std::vector<RegConstraint>& constraints, int my_id) override;
std::shared_ptr<Place> func_in, func_call;
std::shared_ptr<Place> dest;
std::vector<std::shared_ptr<Place>> args;
};
enum IntegerMathKind {
ADD_64,
SUB_64,
IMUL_32,
IDIV_32,
SHLV_64,
SARV_64,
SHRV_64,
SHL_64,
SAR_64,
SHR_64,
IMOD_32,
OR_64,
AND_64,
XOR_64,
NOT_64
};
class IR_IntegerMath : public IR {
public:
IR_IntegerMath(IntegerMathKind _math_kind, std::shared_ptr<Place> _dest, uint8_t _sa)
: math_kind(_math_kind), d(std::move(_dest)), sa(_sa) {
kind = INTEGER_MATH;
}
IR_IntegerMath(IntegerMathKind k, std::shared_ptr<Place> dest, std::shared_ptr<Place> arg0) {
d = dest;
a0 = arg0;
kind = INTEGER_MATH;
math_kind = k;
}
std::string print();
RegAllocInstr to_rai();
IntegerMathKind math_kind;
std::shared_ptr<Place> d, a0;
uint8_t sa = -1;
};
enum FloatMathKind { MUL_SS, DIV_SS, SUB_SS, ADD_SS };
class IR_FloatMath : public IR {
public:
IR_FloatMath(FloatMathKind k, std::shared_ptr<Place> dest, std::shared_ptr<Place> arg0) {
d = dest;
a0 = arg0;
kind = FLOAT_MATH;
math_kind = k;
}
std::string print();
RegAllocInstr to_rai();
FloatMathKind math_kind;
std::shared_ptr<Place> d, a0;
};
enum ConditionKind { NOT_EQUAL_64, EQUAL_64, LEQ_64, LT_64, GT_64, GEQ_64, INVALID_CONDITION };
struct GoalCondition {
ConditionKind kind = INVALID_CONDITION;
std::shared_ptr<Place> a, b;
bool is_signed = false;
bool is_float = false;
RegAllocInstr to_rai();
std::string print();
};
class IR_ConditionalBranch : public IR {
public:
IR_ConditionalBranch() { kind = CONDITIONAL_BRANCH; }
std::string print();
RegAllocInstr to_rai();
GoalCondition cond;
std::shared_ptr<Label> label = nullptr;
bool resolved = false;
};
// class IR_Xmm2Gpr : public IR {
// public:
// IR_Xmm2Gpr() {
// kind = XMM_TO_GPR;
// }
//
// std::string print();
// RegAllocInstr to_rai();
//
// std::shared_ptr<Place> dst;
// std::shared_ptr<Place> src;
//};
class IR_LoadConstOffset : public IR {
public:
IR_LoadConstOffset() { kind = LOAD_CONST_OFFSET; }
IR_LoadConstOffset(std::shared_ptr<Place> _dst,
std::shared_ptr<Place> _src,
int32_t _offset,
int32_t _size,
bool _signed)
: dst(_dst), src(_src), offset(_offset), size(_size), is_signed(_signed) {
kind = LOAD_CONST_OFFSET;
}
std::string print();
RegAllocInstr to_rai();
std::shared_ptr<Place> dst;
std::shared_ptr<Place> src;
int32_t offset;
int32_t size;
bool is_signed;
};
class IR_StoreConstOffset : public IR {
public:
IR_StoreConstOffset() { kind = STORE_CONST_OFFSET; }
IR_StoreConstOffset(std::shared_ptr<Place> _mem,
std::shared_ptr<Place> _val,
int32_t _offset,
int32_t _size,
bool _signed)
: mem(_mem), val(_val), offset(_offset), size(_size), is_signed(_signed) {
kind = STORE_CONST_OFFSET;
}
std::string print();
RegAllocInstr to_rai();
std::shared_ptr<Place> mem;
std::shared_ptr<Place> val;
int32_t offset;
int32_t size;
bool is_signed;
};
class IR_GetReturnAddressPointer : public IR {
public:
IR_GetReturnAddressPointer(std::shared_ptr<Place> _dest) : dest(_dest) {
kind = GET_RETURN_ADDRESS_POINTER;
}
std::string print();
RegAllocInstr to_rai();
std::shared_ptr<Place> dest;
};
class IR_Asm : public IR {
public:
enum AsmKind { RET, RET_REGISTER, MOVE_GPR_U64, PUSH, POP, JMP, SUB, CALL_GOAL_STACK_SUB_8 };
IR_Asm(AsmKind _kind, std::vector<std::shared_ptr<Place>>& _args) : asm_kind(_kind), args(_args) {
kind = ASM;
}
std::string print();
RegAllocInstr to_rai();
AsmKind asm_kind;
std::vector<std::shared_ptr<Place>> args;
};
class IR_Null : public IR {
public:
IR_Null() { kind = IR_NULL; }
std::string print() { return "NULL"; }
RegAllocInstr to_rai() { return {}; }
};
struct UnresolvedGoto {
IR_Goto_Label* ir;
std::string label_name;
};
struct UnresolvedConditionalGoto {
IR_ConditionalBranch* ir;
std::string label_name;
};
#endif // JAK_IR_H
+14
View File
@@ -0,0 +1,14 @@
#ifndef JAK_LABEL_H
#define JAK_LABEL_H
class FunctionEnv;
struct Label {
Label() = default;
Label(FunctionEnv* _func, int _idx = -1) : func(_func), idx(_idx) {}
FunctionEnv* func;
int idx;
std::string print() { return "LABEL-" + std::to_string(idx); }
};
#endif // JAK_LABEL_H
+111
View File
@@ -0,0 +1,111 @@
#include <cassert>
#include "StaticObject.h"
template <typename T>
uint32_t push_data_to_byte_vector(T data, std::vector<uint8_t>& v) {
auto* ptr = (uint8_t*)(&data);
for (std::size_t i = 0; i < sizeof(T); i++) {
v.push_back(ptr[i]);
}
return sizeof(T);
}
std::string StaticString::print() {
return "static string: " + data;
}
int StaticString::emit_into(
std::vector<uint8_t>& out_data,
std::unordered_map<std::string, std::vector<StaticLinkRecord>>& type_links) {
while (out_data.size() & 7) {
out_data.push_back(0);
}
type_links["string"].emplace_back(StaticLinkRecord::TYPE_PTR, out_data.size());
for (int i = 0; i < 4; i++) {
out_data.push_back(0xbe); // slot for type tag
}
offset = out_data.size();
push_data_to_byte_vector<uint32_t>(data.size(), out_data);
for (auto c : data) {
out_data.push_back(c);
}
out_data.push_back(0); // null terminate!
return offset;
}
std::string StaticFloat::print() {
return "static float: " + std::to_string(as_float);
}
int StaticFloat::emit_into(
std::vector<uint8_t>& data,
std::unordered_map<std::string, std::vector<StaticLinkRecord>>& type_links) {
(void)type_links;
while (data.size() & 3) {
data.push_back(0);
}
// no type link needed
offset = data.size();
push_data_to_byte_vector<uint32_t>(as_u32, data);
return offset;
}
std::string StaticStructure::print() {
return "static structure of size " + std::to_string(data.size());
}
int StaticStructure::emit_into(
std::vector<uint8_t>& out_data,
std::unordered_map<std::string, std::vector<StaticLinkRecord>>& type_links) {
(void)type_links;
while (out_data.size() & 7) {
out_data.push_back(0);
}
offset = out_data.size();
out_data.insert(out_data.end(), data.begin(), data.end());
for (auto& sym : symbol_ptr_recs) {
auto& dst_vector = type_links[sym.first];
for (auto& rec : sym.second) {
dst_vector.emplace_back(StaticLinkRecord::SYMBOL_PTR, rec);
}
}
return offset;
}
std::string StaticBasic::print() {
return "static basic of size " + std::to_string(data.size());
}
int StaticBasic::emit_into(
std::vector<uint8_t>& out_data,
std::unordered_map<std::string, std::vector<StaticLinkRecord>>& type_links) {
while (out_data.size() & 7) {
out_data.push_back(0);
}
offset = out_data.size();
type_links[type_name].emplace_back(StaticLinkRecord::TYPE_PTR, offset);
assert(data.size() >= 4);
offset += 4;
out_data.insert(out_data.end(), data.begin(), data.end());
for (auto& sym : symbol_ptr_recs) {
auto& dst_vector = type_links[sym.first];
for (auto& rec : sym.second) {
dst_vector.emplace_back(StaticLinkRecord::SYMBOL_PTR, rec + offset - 4);
}
}
return offset;
}
+72
View File
@@ -0,0 +1,72 @@
#ifndef JAK_STATICOBJECT_H
#define JAK_STATICOBJECT_H
#include <string>
#include <vector>
#include <unordered_map>
#include <cstdint>
#include "codegen/StaticLinkRecord.h"
class StaticObject {
public:
virtual std::string print() = 0;
virtual int emit_into(
std::vector<uint8_t>& data,
std::unordered_map<std::string, std::vector<StaticLinkRecord>>& type_links) = 0;
virtual int load_size() = 0;
int segment = -1;
int offset = -1;
};
class StaticFloat : public StaticObject {
public:
union {
float as_float;
uint32_t as_u32;
};
int load_size() override { return 4; }
std::string print() override;
int emit_into(
std::vector<uint8_t>& data,
std::unordered_map<std::string, std::vector<StaticLinkRecord>>& type_links) override;
};
class StaticString : public StaticObject {
public:
std::string data;
std::string print() override;
int load_size() override { return -1; }
int emit_into(
std::vector<uint8_t>& data,
std::unordered_map<std::string, std::vector<StaticLinkRecord>>& type_links) override;
};
class StaticStructure : public StaticObject {
public:
std::vector<uint8_t> data;
std::string print() override;
std::unordered_map<std::string, std::vector<int>> symbol_ptr_recs;
int emit_into(
std::vector<uint8_t>& data,
std::unordered_map<std::string, std::vector<StaticLinkRecord>>& type_links) override;
int load_size() override { return -1; }
};
class StaticBasic : public StaticStructure {
public:
std::string type_name;
std::string print() override;
int emit_into(
std::vector<uint8_t>& data,
std::unordered_map<std::string, std::vector<StaticLinkRecord>>& type_links) override;
int load_size() override { return -1; }
};
#endif // JAK_STATICOBJECT_H
+52
View File
@@ -0,0 +1,52 @@
/*! @file Timer.h
* @brief Timer for measuring how long things take
*/
#ifndef PROJECT_TIMER_H
#define PROJECT_TIMER_H
#include <assert.h>
#include <stdint.h>
#include <time.h>
/*!
* Timer for measuring time elapsed with clock_monotonic
*/
class Timer {
public:
/*!
* Construct and start timer
*/
explicit Timer() { start(); }
/*!
* Start the timer
*/
void start() { clock_gettime(CLOCK_MONOTONIC, &_startTime); }
/*!
* Get milliseconds elapsed
*/
double getMs() { return (double)getNs() / 1.e6; }
double getUs() { return (double)getNs() / 1.e3; }
/*!
* Get nanoseconds elapsed
*/
int64_t getNs() {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
return (int64_t)(now.tv_nsec - _startTime.tv_nsec) +
1000000000 * (now.tv_sec - _startTime.tv_sec);
}
/*!
* Get seconds elapsed
*/
double getSeconds() { return (double)getNs() / 1.e9; }
struct timespec _startTime;
};
#endif // PROJECT_TIMER_H
+117
View File
@@ -0,0 +1,117 @@
#ifndef JAK_TYPECONTAINER_H
#define JAK_TYPECONTAINER_H
#include "GoalType.h"
// todo get method count, more legit checking of enough room for methods...
struct MethodType {
uint8_t id;
std::string method_name;
TypeSpec type;
bool operator==(const MethodType& other) {
return (id == other.id) && (method_name == other.method_name) && (type == other.type);
}
};
class TypeContainer {
public:
TypeContainer() = default;
void fill_with_default_types();
std::shared_ptr<GoalType> lookup(const std::string& name) {
auto kv = types.find(name);
if (kv == types.end()) {
throw std::runtime_error("unknown type " + name);
}
return kv->second;
}
bool try_get_method_info(const std::string& type_name,
const std::string& method_name,
MethodType* result) {
auto kv = type_method_types.find(type_name);
if (kv == type_method_types.end()) {
throw std::runtime_error("unknown type for method lookup " + type_name);
}
for (auto& x : kv->second) {
if (x.method_name == method_name) {
*result = x;
return true;
}
}
return false;
}
MethodType get_method_info(const std::string& type_name, const std::string& method_name) {
MethodType t;
if (try_get_method_info(type_name, method_name, &t)) {
return t;
}
throw std::runtime_error("unknonw method " + method_name + " for type " + type_name);
}
uint8_t add_method(const std::string& type_name, const std::string& method_name, TypeSpec type) {
type_method_types[type_name];
auto kv = type_method_types.find(type_name);
if (kv == type_method_types.end()) {
throw std::runtime_error("unknown type for method lookup " + type_name);
}
for (auto& x : kv->second) {
if (x.method_name == method_name) {
x.type = type;
return x.id;
}
}
auto id = kv->second.size();
if (id >= 255) {
throw std::runtime_error("too many methods for " + type_name);
}
MethodType mt;
mt.id = id;
mt.method_name = method_name;
mt.type = type;
kv->second.push_back(mt);
return id;
}
uint8_t add_method(const std::string& type_name,
const std::string& method_name,
std::vector<std::string> ts_args) {
std::vector<TypeSpec> args;
for (auto& a : ts_args) {
args.emplace_back(lookup(a));
}
TypeSpec ts(lookup("function"), args);
return add_method(type_name, method_name, ts);
}
void inherit_methods(const std::string& parent_name, const std::string& child_name) {
auto kv = type_method_types.find(parent_name);
if (kv == type_method_types.end()) {
throw std::runtime_error("unknown type " + parent_name + " in inherit methods");
}
// auto& child = type_method_types[child_name];
for (auto& m : kv->second) {
add_method(child_name, m.method_name, m.type);
// if(child.size() > m.id) {
// if(child.at(m.id) == m) continue;
// }
// child.push_back(m);
}
}
std::unordered_map<std::string, std::shared_ptr<GoalType>> types;
std::unordered_map<std::string, std::vector<MethodType>> type_method_types;
};
#endif // JAK_TYPECONTAINER_H
+711
View File
@@ -0,0 +1,711 @@
DONE
(gs) enter GOOS listener
(:exit) exit GOAL
TODO
--------
Method inheritance
defmethod
method calls
access types
FLOAT to INTEGER conversions
buggy test print result timing
TEST for logior,and,xor,not,nor
real test for format
BINTEGER for all the existing math
Rigorous signed/unsigned integer stuff.
MORE REASONABLE ERROR MESSAGE WHEN (inline func-name) is used not in the head of function call
UNCOLORABLE FUNCTIONS (insert a print in the recursive factorial, todo is this still bad?)
UNCOLORABLE NESTED FUNCTIONS
MAKE ST A GOAL POINTER
CONST PROP / more rigourous use of none and resolve to gpr
COLORING HINTS
THE
ASH in the compiler
MIN/MAX in the compiler
INTEGER SIZE / SIGNED ISSUES
STACK SPILLS/STACK FRAMES
only push regs which need to be pushed
stack alignment hack removal
methods
named arguments and default arguments
coloring hints
put link data inside the code where possible
shorter immediates where possible
REGISTER SAVING / correct prologues
log/don't spam print statements
defun docstrings
reader #| bug
Test for string->symbol function
coloring spills / bonus moves
register backups
constant propagation
lambda keywords and defaults
type system
type system in compiler
type system constant prop.
method system
lambda / inline lambda
linking of symbols (pointer to symbol)
linking of types
linking of addresses
segments
symbol file
write file to disk
runtime load file
CGO/DGO
object file assembly
static stuff
xmm
decompiler
emulator
Skipped
---------
label& (needs linking stuff)
catch (needs kernel)
throw (needs kernel)
unwind-protect (is a macro, needs kernel/throw/catch)
*user*
(user?)
&file, &line, &column
(param x) ;; this seems like a bad idea.
protect - requires gensym and set!
define-perm - requires branching and comparison
quasiquote (will require pair/list support in compiler)
unquote (requires quasiquote)
defenum (probably easier when more typesystem exists)
alias-enum (requires enum)
enum-size (requires enum)
aop-align (not needed on x86-64 IMO)
case (needs or)
unless (needs not)
Done
------
begin
block
return-from
label
goto
#cond
#when
#unless
defglobalconstant
seval
lambda (no keyword arguments or method/behavior or friends work yet)
inline (error handling is bad!)
with-inline
rlet (only does the default bind, no reg-type other than gprs)
let
let*
mlet
define
define-extern
set! (doesn't do ->'s or car's/cdr's or methods
defun
defun-extern
true! (needs a test)
false! (needs a test)
print-type
quote (symbols only, no pairs yet)
defmacro
defconstant
defsmacro
desfun
in-package
declare (only works for inline)
cond
if
when
while
v1.0
-------------------------------------------
Runtime/compiler connection for sending code and getting prints/status/reset messages.
Most of GOOS implemented.
GOAL "env" framework partially implemented - can set and return GPR places
GOAL codegen framework partially implemented - can generate some code
GOAL coloring framework partially implemented - only "greedy" coloring algorithm, no concept of blocks/dependencies yet
Can work with extremely simple programs (at this point, can compile/send/run/see results from a function which returns a constant number)
Support for GOOS macros in GOAL.
GOOS macros for connecting/resetting the target.
Bug: if target dies, compiler hangs.
Bug: empty programs are sent when no program should be sent, which return garbage.
- (lt) listen to target
- (:r) reset target and reconnect
- (:status) - poke the target and get any pending prints
- (:exit) exit GOAL/GOOS
- (gs) run GOOS interpreter
- (begin)
- (seval) run GOOS code
v1.1
------------------------------------------
- introduction of "Label"
- introduction of "BlockEnv" (label spaces)
- support for (block)
- support for (return-from) (generate IR only)
- (asm-file) now supports choosing if you want to color or load
- tests framework set up and test macros
- Types exist, but are not part of the compilation process yet
- (test) form to run all tests
- (goto) form (IR only)
- #cond, #while, #unless conditional compilation forms
- defglobalconstant partial implementation
v1.2
----------------------------
- Register coloring using linear scan (live range) allocator
- relatively untested, but no known bugs with simple programs
- no support for spilling to stack or other regs
- no support for function calls
- no support for saving/restoring registers
- Added basic tests related to register coloring
- IOP Framework partially implemented
- Support for goto and return-from in generated code
- added macros for asm-file - (m) to compile and color, (ml) to compile color and load
v1.3
-----------------
- define allows you to define a symbol
- symbols can now be read from the symbol table
- defglobalconstant works fully
- emitter supports multiple segments
- runtime supports object files with multiple segments
- runtime linker support interning symbols and linking their writes
- fixed null termination bug in printing messages from the target
v1.4
----------------
- define-extern works
- basic type checking for
- define
- define-extern
- function calls (just make sure it's a function you're calling, doesn't check args)
- function calls are supported
- more of the type system exists in the compiler
- lambdas can be created and can be inline applied (but no functions generated yet)
- let and let* work
- static objects can be inserted into the object file (so far just string)
- static strings work like you would expect in most places.
- static strings always go into the debug segment currently
v1.5
------------
- you can now define and call functions!
- but a lot of functions will fail to color correctly
- functions other than top level functions will default to going into the debug segment
- and functions themselves are not typed properly at runtime or aligned properly
- lambdas can be marked :inline-only to prevent code form being generated (useful for let and let*)
- still no inline lambdas
v1.6
----------
- the coloring is significantly better
- it stands a chance to get some harder colorings by being allowed to insert moves
- for a return
- for function args
- for function pointer
- but it is still a greedy algorithm and can miss things sometimes
- it also is kinda dumb and will insert some extra moves
- I think this is very solvable by inserting a "hinting" system
- this would make it default to eliminating moves greedily
- + now works for integers
v1.7
-----------
- function types are now working so the compiler knows the return type of a function
- function argument types are also working
- typechecking of function is still not fully implemented
- if you have a symbol that's defined to be a function returning a certain type, you can redefine the function
- to a function returning a different type and the checker won't catch it at this point.
- internal cleanup of TypeSpec required to get correct typing
v1.8
------------------------------
- coloring can spill/unspill, some vague amount of stack things are working.
- subtraction works for integers
- defconstant has been added.
- multiplication works for integers, but uses signed multiplication always.
- (declare) exists for immediate and function lambdas, but only inline, allow-inline are accepted.
- functions can now be inlined using either (declare (inline)) or with (declare (allow-inline)) and (inline func-sym)
v1.9
-------------
- coloring spilling now works correctly when a single operation either reads/write multiple spilled vars
- (with-inline) is added
- rlet is added, but only works for GPR and with a single bind type (works with coloring system, doesn't reserve)
- mlet is added
v1.10
----------------
- set! exists and works on global symbols, function parameters, and lexical vars
- defun-extern works
- some C kernel functions are defun-extern'd
- the debugging form "print-type" has been added
- quote works, but only on symbols for now
- defsmacro from goal works
- desfun from goal works
v2.0
--------------
- the "cond" control flow works, for tests =, !=, and else
- if and when work
- integer division works (requiring some coloring weirdness because x86 is annoying)
- bug fix in inline lambdas
- bug fix in register coloring when calling a function and not using return value
- goos now has (type?) form for checking the GOOS type of something
- off by one bug fix in clobbering checks
- defun can now have doc strings
- protect form works
v2.1
------------
- support for variable shift of integers
- support for ">" number test (only works on signed integers currently)
- cleanup of emitter for cmp/jump instruction pairs
- added ash to gcommon
- cond has correct typing of its result
- integer divide colors better and has one fewer instruction
- coloring adds "exclusive" field for holding a register differently from a function call.
- DECI2 over TCP uses TCP_NODELAY to massively speed up compiler tests.
v2.2
--------------
- new logging framework to reduce print spam
- sign extension when loading memory implemented for symbols and test
- bug fix for branches when there are multiple functions and test
- mod/rem/abs work
- integers are now integer by default, not int32
- added logior, logxor, logand, lognor, lognot
v2.3
-------------
- build system for runtime now allows for nasm assembled files, so we can get functions written in assembly
- x86 emitter finally gets stack alignment correct!
- loading gcommon is now a test
- added fact (recursive) to gcommon
- added format (and a good assembly hack to make it work!)
- added defun-recursive
- made call_goal (c function in runtime) an assembly function so it gets stack alignment right
v2.4
--------------------
- preliminary support for floating point variables!
- you can have floating point immediates
- you can print floats with format ~f, ~F
- you can write functions with float arguments and returns
- * and / are the only supported functions
- can't mix/convert floating point and integers yet
- Listener now has larger buffer to avoid message over-size error messages.
- Symbols can now start with a number (so you can have 1/ as a function)
- coloring system works with XMM registers
- ability to set XMM (float) and GPR registers to each others
- support for 3 byte x86-64 opcodes, XMM regs in emitter
- split link table entry LINK_DISTANCE_TO_OTHER_SEG into LINK_DISTANCE_TO_OTHER_SEG_32, LINK_DISTANCE_TO_OTHER_SEG_64
- change OFF_REG (RBP) to be an actual memory address, not GOAL pointer offset
- support for loading 32-bit static variables relative to RBP into GPR/XMM
- resolve to xmm and resolve to gpr work and work with each other
- static addr system allows loading and pointers
(mg) make group
(mgf) make group force
(lg) reload/recompile GOAL
(cga) force recompile of GOAL
(:mch) output C header file for runtime
(m "file") make file, don't load
(ml "file") make file and load
(asm-file "file") compile
(sml "file") set list of files to be a project
(set-user-machine "target-machine") set target machine
(get-user-machine) get target machine
(lt) connect to target
(:r) connect to target and reset and run
(:rh) connect to target and hard reset
(:kfs) kill fileserver
:pop go up one error level
:p ACL->listener
(:mac1 expr) macro expand
(loaded-object-files) list loaded object files
(listen-to-target) connect to target
(get-user-server)
(set-user-server)
(load "file")
(:status)
(:pa addr)
(:ia addr)
(:iall)
(:reset)
// DEBUGGER
(:break)
(:c)
(:cont)
(:s)
(:n)
(:ss)
(:sn)
(:getbps)
(:abp)
(:rbp)
(:tbp)
(:gbp address-spec)
(:cbp)
(:hbp r|w|i|rw|v address-spec [mask])
(:chbp [r|w|i|rw|v])
(:dm address-spec word-length)
(:bm address-spec byte-length)
(:hm address-spec byte-length)
(:wm address-spec byte-length)
(:lm address-spec byte-length)
(:qm address-spec byte-length)
(:sm address-spec byte-length)
(:la address-spec [word count])
(:lam address-spec [word count])
(:las address-spec [word count])
(:lams address-spec [word count])
(:dis #t|#f|t|nil)
(:cs)
(:getreg rreg|freg|sreg)
(:printregs [vu])
(:getconf)
(:w [expression])
(:rw [index])
(:tw [index])
(:minst mips-instruction)
(:ginst goal-instruction)
(:syms file [p|d|dd|ad|a|ed|u|au])
(:eval lisp-expression)
(:seval goos-expression)
(:mac1 goal-expression)
(:mac goal-expression)
(:ver)
(:gc)
(:help)
(loaded-object-files)
(load-object-file “filename”)
(unload-object-file “filename”)
(asm-file “filename” [:write t] [:color t] [:load nil])
(m “filename”)
(load “filename”)
// make
(make node-name [:message compile-and-load] [:force nil])
(mg [f | force | compile | load])
(collect-goal-nodes node-name)
// reader
#t - boolean true
#f - boolan false
n any integer
n.m any float (fractions must lead with 0, for example 0.5)
#xn any hex integer
#bn any binary integer
#(...) an array
#\n the character n (see lisp or R5 manual)
“string” any string
symbol
symbol
#| - start a comment (may be nested)
|# - end a comment (may be nested)
; - comment until end of line
(...) a list of atoms or other lists
(...) a quoted list
`(... ,n ...) a quasi-quoted list
`(... ,@n ...) a quasi-quoted list with splicing
`(...,+(a b) ...) a quasi-quoted list in which the special “,+” operator evaluates all the
// allocations
static
global
process
stack
scratch
// types
object
none
symbol
boolean
number
float
integer
sinteger
binteger
bint64
buint64
int128
int64
int32
int16
int8
uint128
uint64
uint32
uint16
uint8
char
object64
pair
pointer
structure
basic
array
function
link-block
process-tree
process
stack-frame
catch-frame
protect-frame
state
string
type
thread
vu-function
// type stuff
(current-method-friends)
(declare (friends))
(add-method-friends)
(declare (inline))
// BLOCK stuff
(begin)
(block)
(return-from)
(label)
(label&)
(goto)
(catch)
(throw)
(unwind-protect)
// Conditional Compilation
(#cond)
(#when)
(#unless)
*user*
(user?)
(defglobalconstant)
(seval)
&file
&line
&column
// Function
(lambda)
(inline)
(with-inline)
(param)
(rlet)
(let)
(let*)
(protect)
(mlet)
// Define
(define)
(define-extern)
(define-perm)
(set!)
(defun)
(defun-extern)
(true!)
(false!)
// Macro
(print-type)
(quote)
(quasiquote)
(unquote)
(defenum)
(alias-enum)
(enum-size)
(defmacro)
(defconstant)
(defsconstant)
(defsmacro)
(desfun)
(in-package)
(declare)
(aop-align)
// Flow
(cond)
(case)
(if)
(when)
(unless)
(do)
(do*)
(while)
(until)
(loop)
(for)
(dotimes)
(doarray)
(aif)
(awhen)
(aunless)
(acond)
(awhile)
(aand)
// object
(deftype)
(deftype-extern)
(the)
(the-as)
(&)
(->)
(&->)
(type-of)
(type=)
(type?)
(type?)
(type->symbol)
(type->string)
(psize-of)
(element-psize-of)
(element-pshift-of)
(offset-of)
(bit-ffset-of)
(mask-of-bit-field-type-spec)
(method)
(defmethod)
(current-functrion-param-length)
(current-function-param)
(current-function-name)
(current-method-type)
(current-method-friends)
(with-method-friends)
(new)
(object-new)
(reset-scratch)
(call-parent-method)
// methods
(new)
(delete)
(print)
(inspect)
(length)
(asize-of)
(copy)
(copy!)
// maths
(+)
(1+)
(+!)
(&+)
(&+!)
(-)
(1-)
(-!)
(*)
(/)
(rem)
(mod)
(ash)
(abs)
(min)
(max)
(lognot)
(logand)
(logior)
(logxor)
(lognor)
(logtest?)
(logbit?)
(bit-field)
// logical
(and)
(or)
(not)
(identity)
(nothing)
(zero?)
(eq?)
(eqv?)
(equal?)
(=)
(!=)
(<)
(>)
(<=)
(>=)
// pair
(pair?)
(cons)
(list)
(null?)
(car)
(cdr)
(second) (third) (fourth)
(push)
(pop)
(length)
(last)
(member)
(assoc)
(delete!)
(delete-car!)
(append!)
(insert-cons!)
// array
(length)
// string/symbol
(string->symbol)
(symbol->string)
(legnth)
(copy-string->string)
(clear)
// I/O
(print)
(inspect)
(format)
*print-column*
*tab-size*
(load)
(loado)
// File
(file-stream-error?)
(new)
(file-stream-open)
(file-stream-close)
(file-stream-length)
(length)
(file-stream-seek)
(file-stream-read)
(file-stream-write)
(file-stream-string-read)
(with-open-file)
// LANGUAGE
(in-package goal)
+2
View File
@@ -0,0 +1,2 @@
add_library(goos_old SHARED Object.cpp Goos.cpp GoosBuiltins.cpp GoosTest.cpp)
#target_link_libraries(goos reader)
+891
View File
@@ -0,0 +1,891 @@
#include "Goos.h"
#include "Object.h"
Goos::Goos() {
goal_to_goos.reset();
global_environment = EnvironmentObject::make_new();
global_environment.as_env()->name = "global";
global_environment.as_env()->parent_env = nullptr;
// global_environment.as_env()->vars[SymbolObject::make_new(reader.symbolTable,
// "*global-env*").as_symbol()] = global_environment;
goal_goos_env = EnvironmentObject::make_new();
goal_goos_env.as_env()->name = "goal";
goal_goos_env.as_env()->parent_env = nullptr;
// make both environments available in both.
define_var_in_env(global_environment, global_environment, "*global-env*");
define_var_in_env(goal_goos_env, goal_goos_env, "*goal-env*");
define_var_in_env(goal_goos_env, global_environment, "*global-env*");
define_var_in_env(global_environment, goal_goos_env, "*goal-env*");
load_goos_library();
// eval_with_rewind(reader.read_from_string("(load-file \"compiler-lib/goos-lib.gs\")"),
// global_environment.as_env());
}
void Goos::load_goos_library() {
auto next_dir = reader.get_next_dir();
auto cmd = "(load-file \"old_compiler/gs/goos-lib.gs\")";
eval_with_rewind(reader.read_from_string(cmd), global_environment.as_env());
}
void Goos::define_var_in_env(Object& env, Object& var, const std::string& name) {
env.as_env()->vars[SymbolObject::make_new(reader.symbolTable, name).as_symbol()] = var;
}
void print_and_inspect(Object o) {
printf("%s\n%s\n", o.print().c_str(), o.inspect().c_str());
}
void Goos::execute_repl() {
// read, evaluate, print loop!
while (!want_exit) {
try {
Object obj = reader.read_from_stdin("goos");
// print_and_inspect(obj);
Object evald = eval_with_rewind(obj, global_environment.as_env());
printf("%s\n", evald.print().c_str());
// print_and_inspect(evald);
} catch (std::exception& e) {
printf("REPL Error: %s\n", e.what());
}
}
}
Object Goos::eval_with_rewind(Object obj, std::shared_ptr<EnvironmentObject> env) {
Object result = EmptyListObject::make_new();
try {
result = eval(obj, env);
} catch (std::runtime_error& e) {
printf("-----------------------------------------\n");
printf("Eval error:\n%s\nobject %s\nat %s\n", e.what(), obj.inspect().c_str(),
reader.db.get_info_for(obj).c_str());
throw e;
}
return result;
}
Object Goos::eval(Object obj, std::shared_ptr<EnvironmentObject> env) {
switch (obj.type) {
case PAIR:
return eval_pair(obj, env);
break;
case INTEGER:
case FLOAT:
case STRING:
return obj;
case SYMBOL:
return eval_symbol(obj, env);
default:
throw_eval_error(obj, "cannot evaluate this object");
return Object();
}
}
Object Goos::eval_symbol(Object sym, std::shared_ptr<EnvironmentObject> env) {
if (sym.as_symbol()->name == "#t")
return sym;
if (sym.as_symbol()->name == "#f")
return sym;
std::shared_ptr<EnvironmentObject> search_env = env;
for (;;) {
auto kv = search_env->vars.find(sym.as_symbol());
if (kv != search_env->vars.end()) {
return kv->second;
}
auto pe = search_env->parent_env;
if (pe) {
search_env = pe;
} else {
throw_eval_error(sym, "symbol is not defined");
}
}
}
void Goos::throw_eval_error(Object o, const std::string& err) {
(void)o;
throw std::runtime_error(err);
}
static const std::unordered_map<
std::string,
Object (Goos::*)(const Object& form, Object rest, std::shared_ptr<EnvironmentObject>& env)>
special_forms = {
{"define", &Goos::eval_define},
{"quote", &Goos::eval_quote},
{"set!", &Goos::eval_set},
{"lambda", &Goos::eval_lambda},
{"cond", &Goos::eval_cond},
{"or", &Goos::eval_or},
{"and", &Goos::eval_and},
{"macro", &Goos::eval_macro},
{"quasiquote", &Goos::eval_quasiquote},
{"while", &Goos::eval_while},
};
static const std::unordered_map<
std::string,
Object (Goos::*)(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env)>
builtins = {
{"top-level", &Goos::eval_begin},
{"begin", &Goos::eval_begin},
{"exit", &Goos::eval_exit},
{"read", &Goos::eval_read},
{"read-file", &Goos::eval_read_file},
{"print", &Goos::eval_print},
{"inspect", &Goos::eval_inspect},
{"load-file", &Goos::eval_load_file},
{"eq?", &Goos::eval_equals},
{"gensym", &Goos::eval_gensym},
{"eval", &Goos::eval_eval},
{"cons", &Goos::eval_cons},
{"car", &Goos::eval_car},
{"cdr", &Goos::eval_cdr},
// set-car!
// set-cdr!
{"+", &Goos::eval_plus},
{"-", &Goos::eval_minus},
{"*", &Goos::eval_times},
// divide
{"=", &Goos::eval_numequals},
{"<", &Goos::eval_lt},
{">", &Goos::eval_gt},
{"<=", &Goos::eval_leq},
{">=", &Goos::eval_geq},
// eval
// not
// xor
// nor
// nand
// position
// length
{"null?", &Goos::eval_null},
{"type?", &Goos::eval_type},
{"current-method-type", &Goos::eval_current_method_type},
// the float
// the int
// the char
// is pair
// is symbol
// is integer
// is char
// is float
// is null
// is proc
// is macro
// is array
// is string
// string manip
// array manip
// lots more...
};
GoosArgs Goos::eval_args(const Object& form, Object rest, std::shared_ptr<EnvironmentObject>& env) {
// todo, keyword args
Object o = rest;
GoosArgs args;
for (;;) {
if (o.type == PAIR) {
auto op = o.as_pair();
args.unnamed_args.push_back(eval_with_rewind(op->car, env));
o = op->cdr;
} else if (o.type == EMPTY_LIST) {
return args;
} else {
throw_eval_error(form, "malformed argument list");
}
}
}
GoosArgs Goos::get_macro_args(const Object& form, Object rest) {
// todo, keyword args
Object o = rest;
GoosArgs args;
for (;;) {
if (o.type == PAIR) {
auto op = o.as_pair();
args.unnamed_args.push_back(op->car);
o = op->cdr;
} else if (o.type == EMPTY_LIST) {
return args;
} else {
throw_eval_error(form, "malformed argument list");
}
}
}
GoosArgs Goos::get_uneval_args(const Object& form, Object rest, int count) {
GoosArgs args;
args.rest = EmptyListObject::make_new();
Object o = rest;
auto next = [&]() {
if (rest.type != PAIR) {
throw_eval_error(form, "invalid arguments");
}
o = rest.as_pair()->car;
rest = rest.as_pair()->cdr;
};
next();
int got = 0;
for (;;) {
bool add_normal = true;
if (o.type == SYMBOL) {
auto osym = o.as_symbol();
if (osym->name[0] == ':') {
add_normal = false;
std::string name = osym->name.substr(1);
next();
if (args.named_args.find(name) != args.named_args.end()) {
throw_eval_error(rest, "multiply defined keyword argument");
}
args.named_args[name] = o;
}
}
if (add_normal) {
args.unnamed_args.push_back(o);
got++;
if (got == count) {
args.rest = rest;
args.has_rest = true;
break;
}
}
if (rest.type == PAIR) {
next();
} else {
break;
}
}
return args;
}
GoosArgs Goos::get_uneval_args_no_rest(const Object& form, Object rest, int count) {
GoosArgs args;
args.has_rest = false;
args.rest = EmptyListObject::make_new();
Object o = rest;
auto next = [&]() {
if (rest.type != PAIR) {
throw_eval_error(form, "invalid arguments");
}
o = rest.as_pair()->car;
rest = rest.as_pair()->cdr;
};
next();
int got = 0;
for (;;) {
bool add_normal = true;
if (o.type == SYMBOL) {
auto osym = o.as_symbol();
if (osym->name[0] == ':') {
add_normal = false;
std::string name = osym->name.substr(1);
next();
if (args.named_args.find(name) != args.named_args.end()) {
throw_eval_error(rest, "multiply defined keyword argument");
}
args.named_args[name] = o;
}
}
if (add_normal) {
if (got == count) {
throw_eval_error(rest, "too many arguments");
}
args.unnamed_args.push_back(o);
got++;
}
if (rest.type == PAIR) {
next();
} else {
break;
}
}
return args;
}
std::string GoosArgs::print() {
std::string result;
for (auto& kv : named_args) {
result += "[" + kv.first + "] " + kv.second.print() + "\n";
}
for (auto& a : unnamed_args) {
result += a.print() + "\n";
}
result += "rest: " + rest.print();
return result;
}
bool GoosArgs::check_count(int count) {
return (int)unnamed_args.size() == count;
}
bool GoosArgs::check_keywords(std::unordered_set<std::string>& keywords) {
for (auto& kv : named_args) {
if (keywords.find(kv.first) == keywords.end()) {
return false;
}
}
return true;
}
Object Goos::eval_list_return_last(const Object& form,
Object rest,
std::shared_ptr<EnvironmentObject>& env) {
Object o = rest;
Object rv = EmptyListObject::make_new();
for (;;) {
if (o.type == PAIR) {
auto op = o.as_pair();
rv = eval_with_rewind(op->car, env);
o = op->cdr;
} else if (o.type == EMPTY_LIST) {
return rv;
} else {
throw_eval_error(form, "malformed body to evaluate");
}
}
}
Object Goos::eval_pair(Object obj, std::shared_ptr<EnvironmentObject> env) {
auto pair = obj.as_pair();
Object head = pair->car;
Object rest = pair->cdr;
// first see if we got a symbol:
if (head.type == SYMBOL) {
auto head_sym = head.as_symbol();
// try a special form first
auto kv_sf = special_forms.find(head_sym->name);
if (kv_sf != special_forms.end()) {
return ((*this).*(kv_sf->second))(obj, rest, env);
}
// try builtins next
auto kv_b = builtins.find(head_sym->name);
if (kv_b != builtins.end()) {
// eval:
GoosArgs args = eval_args(obj, rest, env);
return ((*this).*(kv_b->second))(obj, args, env);
}
// try macros next
Object macro_obj;
bool got_macro = false;
try {
macro_obj = eval_symbol(head, env);
if (macro_obj.type == MACRO) {
got_macro = true;
}
} catch (std::runtime_error& e) {
got_macro = false;
}
if (got_macro) {
GoosArgs args = get_macro_args(obj, rest);
// todo define keywords here
auto macro = macro_obj.as_macro();
auto mac_env_obj = EnvironmentObject::make_new();
auto mac_env = mac_env_obj.as_env();
mac_env->parent_env = env; // todo, is this how macros work?
if (!macro->has_rest && args.unnamed_args.size() != macro->unnamed_args.size()) {
throw_eval_error(obj, "macro didn't get expected argument count");
} else if (macro->has_rest && args.unnamed_args.size() < macro->unnamed_args.size()) {
throw_eval_error(obj, "macro (with rest args) didn't get enough arguments");
}
uint32_t i = 0;
for (; i < macro->unnamed_args.size(); i++) {
mac_env->vars[macro->unnamed_args.at(i).as_symbol()] = args.unnamed_args.at(i);
}
if (macro->has_rest) {
if (i < args.unnamed_args.size()) {
Object empty = EmptyListObject::make_new();
Object rest_head = PairObject::make_new(args.unnamed_args[i], empty);
Object last = rest_head;
i++;
for (; i < args.unnamed_args.size(); i++) {
last.as_pair()->cdr = PairObject::make_new(args.unnamed_args[i], empty);
last = last.as_pair()->cdr;
}
mac_env->vars[macro->rest_args.as_symbol()] = rest_head;
} else {
mac_env->vars[macro->rest_args.as_symbol()] = EmptyListObject::make_new();
}
}
return eval_with_rewind(eval_list_return_last(macro->body, macro->body, mac_env), env);
}
}
// eval the head and try it as a lambda
Object eval_head = eval_with_rewind(head, env);
if (eval_head.type != LAMBDA) {
throw_eval_error(obj, "head of form didn't evaluate to lambda");
}
GoosArgs args = eval_args(obj, rest, env);
// todo define keywords here
auto lam = eval_head.as_lambda();
auto lam_env_obj = EnvironmentObject::make_new();
auto lam_env = lam_env_obj.as_env();
lam_env->parent_env = lam->parent_env;
if (!lam->has_rest && args.unnamed_args.size() != lam->unnamed_args.size()) {
throw_eval_error(obj, "lambda didn't get expected argument count");
} else if (lam->has_rest && args.unnamed_args.size() < lam->unnamed_args.size()) {
throw_eval_error(obj, "lambda (with rest args) didn't get enough arguments");
}
uint32_t i = 0;
for (; i < lam->unnamed_args.size(); i++) {
lam_env->vars[lam->unnamed_args.at(i).as_symbol()] = args.unnamed_args.at(i);
}
if (lam->has_rest) {
if (i < args.unnamed_args.size()) {
Object empty = EmptyListObject::make_new();
Object rest_head = PairObject::make_new(args.unnamed_args[i], empty);
Object last = rest_head;
i++;
for (; i < args.unnamed_args.size(); i++) {
last.as_pair()->cdr = PairObject::make_new(args.unnamed_args[i], empty);
last = last.as_pair()->cdr;
}
lam_env->vars[lam->rest_args.as_symbol()] = rest_head;
} else {
lam_env->vars[lam->rest_args.as_symbol()] = EmptyListObject::make_new();
}
}
return eval_list_return_last(lam->body, lam->body, lam_env);
throw_eval_error(obj, "don't know how to evaluate this form");
return EmptyListObject::make_new();
}
Object Goos::eval_define(const Object& form, Object rest, std::shared_ptr<EnvironmentObject>& env) {
// if(rest.type == PAIR) {
// Object to_define = rest.as_pair()->car;
// if(to_define.type != SYMBOL) {
// throw_eval_error(form, "define's first argument must be a symbol");
// }
// Object rrest = rest.as_pair()->cdr;
// if(rrest.type != PAIR) {
// throw_eval_error(form, "define must be given two arguments, you have not provided
// enough");
// }
// Object to_eval = rrest.as_pair()->car;
// if(rrest.as_pair()->cdr.type != EMPTY_LIST) {
// throw_eval_error(form, "define must be given two arguments, you have provided too many");
// }
// Object to_set = eval_with_rewind(to_eval, env);
// env->vars[to_define.as_symbol()] = to_set;
// return to_set;
// } else {
// throw_eval_error(form, "define must be given two arguments, you have not provided enough");
// }
//
// return EmptyListObject::make_new();
auto next = [&]() {
if (rest.type != PAIR) {
throw_eval_error(form, "invalid arguments");
}
auto rv = rest.as_pair()->car;
rest = rest.as_pair()->cdr;
return rv;
};
auto done = [&]() {
if (rest.type != EMPTY_LIST) {
throw_eval_error(form, "too many arguments");
}
};
Object first_arg = next();
if (first_arg.type != SYMBOL) {
throw_eval_error(form, "define's first argument must be a symbol");
}
if (first_arg.as_symbol()->name == ":env") {
// special
Object define_env = eval_with_rewind(next(), env);
if (define_env.type != ENVIRONMENT) {
throw_eval_error(form, "define :env must give an environment");
}
Object sym_name = next();
if (sym_name.type != SYMBOL) {
throw_eval_error(form, "define's symbol argument must be a symbol");
}
Object to_set = eval_with_rewind(next(), env);
define_env.as_env()->vars[sym_name.as_symbol()] = to_set;
done();
return to_set;
} else {
Object to_set = eval_with_rewind(next(), env);
env->vars[first_arg.as_symbol()] = to_set;
done();
return to_set;
}
}
Object Goos::eval_set(const Object& form, Object rest, std::shared_ptr<EnvironmentObject>& env) {
if (rest.type == PAIR) {
Object to_define = rest.as_pair()->car;
if (to_define.type != SYMBOL) {
throw_eval_error(form, "set!'s first argument must be a symbol");
}
Object rrest = rest.as_pair()->cdr;
if (rrest.type != PAIR) {
throw_eval_error(form, "set! must be given two arguments, you have not provided enough");
}
Object to_eval = rrest.as_pair()->car;
if (rrest.as_pair()->cdr.type != EMPTY_LIST) {
throw_eval_error(form, "set! must be given two arguments, you have provided too many");
}
Object to_set = eval_with_rewind(to_eval, env);
std::shared_ptr<EnvironmentObject> search_env = env;
for (;;) {
auto kv = search_env->vars.find(to_define.as_symbol());
if (kv != search_env->vars.end()) {
kv->second = to_set;
return kv->second;
}
auto pe = search_env->parent_env;
if (pe) {
search_env = pe;
} else {
throw_eval_error(to_define, "symbol is not defined");
}
}
return to_set;
} else {
throw_eval_error(form, "set! must be given two arguments, you have not provided enough");
}
return EmptyListObject::make_new();
}
// lambda special form.
Object Goos::eval_lambda(const Object& form, Object rest, std::shared_ptr<EnvironmentObject>& env) {
if (rest.type != PAIR) {
throw_eval_error(form, "lambda must receive two arguments");
}
Object arg_list = rest.as_pair()->car;
if (arg_list.type != PAIR && arg_list.type != EMPTY_LIST) {
throw_eval_error(form, "lambda argument list must be a list");
}
Object new_lambda = LambdaObject::make_new();
auto l = new_lambda.as_lambda();
Object arg_iter = arg_list;
for (;;) {
if (arg_iter.type == EMPTY_LIST) {
break;
} else if (arg_iter.type == PAIR) {
// todo handle keyword arguments here
Object arg_name = arg_iter.as_pair()->car;
arg_iter = arg_iter.as_pair()->cdr;
if (arg_name.type != SYMBOL) {
throw_eval_error(form, "lambda args must be symbols");
}
if (arg_name.as_symbol()->name == "&rest") {
// should have a name
if (arg_iter.type != PAIR) {
throw_eval_error(form, "rest argument must have a name");
}
if (arg_iter.as_pair()->car.type != SYMBOL) {
throw_eval_error(form, "rest argument must be a symbol");
}
if (arg_iter.as_pair()->cdr.type != EMPTY_LIST) {
throw_eval_error(form, "no arguments can follow a rest argument");
}
l->rest_args = arg_iter.as_pair()->car;
l->has_rest = true;
break;
}
l->unnamed_args.push_back(arg_name);
} else {
throw_eval_error(form, "lambda has invalid arg list");
}
}
Object rrest = rest.as_pair()->cdr;
if (rrest.type != PAIR) {
throw_eval_error(form, "lamba body must be a list");
}
l->body = rrest;
l->parent_env = env;
return new_lambda;
}
Object Goos::eval_macro(const Object& form, Object rest, std::shared_ptr<EnvironmentObject>& env) {
if (rest.type != PAIR) {
throw_eval_error(form, "macro must receive two arguments");
}
Object arg_list = rest.as_pair()->car;
if (arg_list.type != PAIR && arg_list.type != EMPTY_LIST) {
throw_eval_error(form, "macro argument list must be a list");
}
Object new_macro = MacroObject::make_new();
auto m = new_macro.as_macro();
Object arg_iter = arg_list;
for (;;) {
if (arg_iter.type == EMPTY_LIST) {
break;
} else if (arg_iter.type == PAIR) {
// todo handle keyword arguments here
Object arg_name = arg_iter.as_pair()->car;
arg_iter = arg_iter.as_pair()->cdr;
if (arg_name.type != SYMBOL) {
throw_eval_error(form, "macro args must be symbols");
}
if (arg_name.as_symbol()->name == "&rest") {
// should have a name
if (arg_iter.type != PAIR) {
throw_eval_error(form, "rest argument must have a name");
}
if (arg_iter.as_pair()->car.type != SYMBOL) {
throw_eval_error(form, "rest argument must be a symbol");
}
if (arg_iter.as_pair()->cdr.type != EMPTY_LIST) {
throw_eval_error(form, "no arguments can follow a rest argument");
}
m->rest_args = arg_iter.as_pair()->car;
m->has_rest = true;
break;
}
m->unnamed_args.push_back(arg_name);
} else {
throw_eval_error(form, "macro has invalid arg list");
}
}
Object rrest = rest.as_pair()->cdr;
if (rrest.type != PAIR) {
throw_eval_error(form, "macro body must be a list");
}
m->body = rrest;
m->parent_env = env;
return new_macro;
}
Object Goos::eval_quote(const Object& form, Object rest, std::shared_ptr<EnvironmentObject>& env) {
(void)env;
if (rest.type != PAIR) {
throw_eval_error(form, "quote must be given exactly one argument");
}
Object result = rest.as_pair()->car;
Object rrest = rest.as_pair()->cdr;
if (rrest.type != EMPTY_LIST) {
throw_eval_error(form, "quote must be given exactly one argument, you have given more");
}
return result;
}
bool Goos::get_object_by_name(const std::string& name, Object& dest) {
auto kv = global_environment.as_env()->vars.find(
SymbolObject::make_new(reader.symbolTable, name).as_symbol());
if (kv != global_environment.as_env()->vars.end()) {
dest = kv->second;
return true;
}
return false;
}
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;
}
Object Goos::eval_cond(const Object& form, Object rest, std::shared_ptr<EnvironmentObject>& env) {
if (rest.type != PAIR)
throw_eval_error(form, "cond must have at least one clause, which must be a form");
Object result;
Object lst = rest;
for (;;) {
if (lst.type == PAIR) {
Object current_case = lst.as_pair()->car;
if (current_case.type != PAIR)
throw_eval_error(lst, "bogus cond case");
// check condition:
Object condition_result = eval_with_rewind(current_case.as_pair()->car, env);
if (truthy(condition_result)) {
if (current_case.as_pair()->cdr.type == EMPTY_LIST) {
return condition_result;
}
// got a match!
return eval_list_return_last(current_case, current_case.as_pair()->cdr, env);
} else {
// no match, continue.
lst = lst.as_pair()->cdr;
}
} else if (lst.type == EMPTY_LIST) {
return SymbolObject::make_new(reader.symbolTable, "#f");
} else {
throw_eval_error(form, "malformed cond");
}
}
}
Object Goos::eval_or(const Object& form, Object rest, std::shared_ptr<EnvironmentObject>& env) {
if (rest.type != PAIR)
throw_eval_error(form, "or must have at least one argument!");
Object lst = rest;
for (;;) {
if (lst.type == PAIR) {
Object current = eval_with_rewind(lst.as_pair()->car, env);
if (truthy(current)) {
return current;
}
lst = lst.as_pair()->cdr;
} else if (lst.type == EMPTY_LIST) {
return SymbolObject::make_new(reader.symbolTable, "#f");
} else {
throw_eval_error(form, "invalid or form");
}
}
}
Object Goos::eval_and(const Object& form, Object rest, std::shared_ptr<EnvironmentObject>& env) {
if (rest.type != PAIR)
throw_eval_error(form, "and must have at least one argument!");
Object lst = rest;
Object current;
for (;;) {
if (lst.type == PAIR) {
current = eval_with_rewind(lst.as_pair()->car, env);
if (!truthy(current)) {
return SymbolObject::make_new(reader.symbolTable, "#f");
}
lst = lst.as_pair()->cdr;
} else if (lst.type == EMPTY_LIST) {
return current;
} else {
throw_eval_error(form, "invalid and form");
}
}
}
Object Goos::quasiquote_helper(Object form, std::shared_ptr<EnvironmentObject>& env) {
Object lst = form;
std::vector<Object> result;
for (;;) {
if (lst.type == PAIR) {
Object item = lst.as_pair()->car;
if (item.type == PAIR) {
if (item.as_pair()->car.type == SYMBOL &&
item.as_pair()->car.as_symbol()->name == "unquote") {
Object unquote_arg = item.as_pair()->cdr;
if (unquote_arg.type != PAIR || unquote_arg.as_pair()->cdr.type != EMPTY_LIST) {
throw_eval_error(form, "unquote must have exactly 1 arg");
}
item = eval_with_rewind(unquote_arg.as_pair()->car, env);
} else if (item.as_pair()->car.type == SYMBOL &&
item.as_pair()->car.as_symbol()->name == "unquote-splicing") {
Object unquote_arg = item.as_pair()->cdr;
if (unquote_arg.type != PAIR || unquote_arg.as_pair()->cdr.type != EMPTY_LIST) {
throw_eval_error(form, "unquote must have exactly 1 arg");
}
item = eval_with_rewind(unquote_arg.as_pair()->car, env);
// bypass normal addition:
lst = lst.as_pair()->cdr;
Object to_add = item;
for (;;) {
if (to_add.type == PAIR) {
result.push_back(to_add.as_pair()->car);
to_add = to_add.as_pair()->cdr;
} else if (to_add.type == EMPTY_LIST) {
break;
} else {
throw_eval_error(form, "malformed unquote-splicing result");
}
}
continue;
}
else {
item = quasiquote_helper(item, env);
}
}
lst = lst.as_pair()->cdr;
result.push_back(item);
} else if (lst.type == EMPTY_LIST) {
return build_list(result);
} else {
throw_eval_error(form, "malformed quasiquote");
}
}
}
Object Goos::eval_quasiquote(const Object& form,
Object rest,
std::shared_ptr<EnvironmentObject>& env) {
if (rest.type != PAIR || rest.as_pair()->cdr.type != EMPTY_LIST)
throw_eval_error(form, "quasiquote must have one argument!");
return quasiquote_helper(rest.as_pair()->car, env);
}
Object Goos::eval_while(const Object& form, Object rest, std::shared_ptr<EnvironmentObject>& env) {
if (rest.type != PAIR)
throw_eval_error(form, "while must have condition and body");
Object condition = rest.as_pair()->car;
Object body = rest.as_pair()->cdr;
if (body.type != PAIR)
throw_eval_error(form, "while must have condition and body");
Object rv = SymbolObject::make_new(reader.symbolTable, "#f");
while (truthy(eval_with_rewind(condition, env))) {
rv = eval_list_return_last(form, body, env);
}
return rv;
}
+132
View File
@@ -0,0 +1,132 @@
#ifndef COMPILER_GOOS_H
#define COMPILER_GOOS_H
#include <unordered_set>
#include "reader/Reader.h"
#include "goos/Object.h"
struct GoosArgs {
std::vector<Object> unnamed_args;
std::unordered_map<std::string, Object> named_args;
Object rest;
bool has_rest = false;
std::string print();
bool check_count(int count);
bool check_keywords(std::unordered_set<std::string>& keywords);
};
struct GoalToGoosData {
std::string enclosing_method_type;
void reset() { enclosing_method_type = "#f"; }
};
class Goos {
public:
Goos();
void execute_repl();
void throw_eval_error(Object o, const std::string& err);
Object eval(Object obj, std::shared_ptr<EnvironmentObject> env);
Object eval_with_rewind(Object obj, std::shared_ptr<EnvironmentObject> env);
bool get_object_by_name(const std::string& name, Object& dest);
Object get_object_by_name(const std::string& name) {
Object o = EmptyListObject::make_new();
get_object_by_name(name, o);
return o;
}
Reader reader;
Object global_environment;
Object goal_goos_env;
GoalToGoosData goal_to_goos;
Object eval_eval(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
Object eval_equals(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
Object eval_exit(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
Object eval_begin(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
Object eval_read(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
Object eval_read_file(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env);
Object eval_load_file(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env);
Object eval_print(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
Object eval_inspect(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
Object eval_plus(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
Object eval_minus(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
Object eval_times(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
Object eval_numequals(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env);
Object eval_lt(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
Object eval_gt(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
Object eval_leq(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
Object eval_geq(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
Object eval_car(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
Object eval_cdr(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
Object eval_gensym(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
Object eval_cons(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
Object eval_null(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
Object eval_type(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
Object eval_current_method_type(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env);
// specials
Object eval_define(const Object& form, Object rest, std::shared_ptr<EnvironmentObject>& env);
Object eval_quote(const Object& form, Object rest, std::shared_ptr<EnvironmentObject>& env);
Object eval_set(const Object& form, Object rest, std::shared_ptr<EnvironmentObject>& env);
Object eval_lambda(const Object& form, Object rest, std::shared_ptr<EnvironmentObject>& env);
Object eval_cond(const Object& form, Object rest, std::shared_ptr<EnvironmentObject>& env);
Object eval_or(const Object& form, Object rest, std::shared_ptr<EnvironmentObject>& env);
Object eval_and(const Object& form, Object rest, std::shared_ptr<EnvironmentObject>& env);
Object eval_quasiquote(const Object& form, Object rest, std::shared_ptr<EnvironmentObject>& env);
Object eval_macro(const Object& form, Object rest, std::shared_ptr<EnvironmentObject>& env);
Object eval_while(const Object& form, Object rest, std::shared_ptr<EnvironmentObject>& env);
private:
friend class Goal;
Object eval_pair(Object o, std::shared_ptr<EnvironmentObject> env);
Object eval_symbol(Object sym, std::shared_ptr<EnvironmentObject> env);
GoosArgs eval_args(const Object& form, Object rest, std::shared_ptr<EnvironmentObject>& env);
GoosArgs get_macro_args(const Object& form, Object rest);
GoosArgs get_uneval_args(const Object& form, Object rest, int count);
GoosArgs get_uneval_args_no_rest(const Object& form, Object rest, int count);
Object eval_list_return_last(const Object& form,
Object rest,
std::shared_ptr<EnvironmentObject>& env);
Object quasiquote_helper(Object form, std::shared_ptr<EnvironmentObject>& env);
int64_t number_to_integer(const Object& obj);
char number_to_char(const Object& obj);
double number_to_float(const Object& obj);
void define_var_in_env(Object& env, Object& var, const std::string& name);
void load_goos_library();
template <typename T>
T number(const Object& obj);
template <typename T>
Object num_lt(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
template <typename T>
Object num_gt(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
template <typename T>
Object num_leq(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
template <typename T>
Object num_geq(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
template <typename T>
Object num_plus(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
template <typename T>
Object num_minus(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
template <typename T>
Object num_times(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env);
bool want_exit = false;
};
#endif // COMPILER_GOOS_H
+549
View File
@@ -0,0 +1,549 @@
#include "goos/Goos.h"
static int64_t gensym_id = 0;
Object Goos::eval_exit(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env) {
(void)form;
(void)args;
(void)env;
want_exit = true;
return EmptyListObject::make_new();
}
Object Goos::eval_begin(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env) {
(void)env;
if (!args.named_args.empty()) {
throw_eval_error(form, "begin form cannot have keyword arguments");
}
if (args.unnamed_args.empty()) {
return EmptyListObject::make_new();
} else {
return args.unnamed_args.back();
}
}
Object Goos::eval_read(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env) {
(void)env;
if (args.unnamed_args.size() != 1 || !args.named_args.empty() ||
args.unnamed_args.at(0).type != STRING) {
throw_eval_error(form, "read must be given a single string argument");
}
try {
return reader.read_from_string(args.unnamed_args.at(0).as_string()->data);
} catch (std::runtime_error& e) {
throw_eval_error(form, std::string("reader error inside of read:\n") + e.what());
}
return EmptyListObject::make_new();
}
Object Goos::eval_read_file(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env) {
(void)env;
if (args.unnamed_args.size() != 1 || !args.named_args.empty() ||
args.unnamed_args.at(0).type != STRING) {
throw_eval_error(form, "read-file must be given a single string argument");
}
try {
return reader.read_from_file(args.unnamed_args.at(0).as_string()->data);
} catch (std::runtime_error& e) {
throw_eval_error(form, std::string("reader error inside of read-file:\n") + e.what());
}
return EmptyListObject::make_new();
}
Object Goos::eval_load_file(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env) {
(void)env;
if (args.unnamed_args.size() != 1 || !args.named_args.empty() ||
args.unnamed_args.at(0).type != STRING) {
throw_eval_error(form, "read-file must be given a single string argument");
}
Object o;
try {
o = reader.read_from_file(args.unnamed_args.at(0).as_string()->data);
} catch (std::runtime_error& e) {
throw_eval_error(form, std::string("reader error inside of load-file:\n") + e.what());
}
try {
return eval_with_rewind(o, global_environment.as_env());
} catch (std::runtime_error& e) {
throw_eval_error(form, std::string("eval error inside of load-file:\n") + e.what());
}
return EmptyListObject::make_new();
}
Object Goos::eval_print(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env) {
(void)env;
if (args.unnamed_args.size() != 1 || !args.named_args.empty()) {
throw_eval_error(form, "print must be given a single argument");
}
printf("%s\n", args.unnamed_args.at(0).print().c_str());
return EmptyListObject::make_new();
}
Object Goos::eval_inspect(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env) {
(void)env;
if (args.unnamed_args.size() != 1 || !args.named_args.empty()) {
throw_eval_error(form, "inspect must be given a single argument");
}
printf("%s\n", args.unnamed_args.at(0).inspect().c_str());
return EmptyListObject::make_new();
}
Object Goos::eval_equals(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env) {
(void)env;
if (args.unnamed_args.size() != 2 || !args.named_args.empty()) {
throw_eval_error(form, "eq? must be given two unnamed arguments");
}
return SymbolObject::make_new(reader.symbolTable,
args.unnamed_args[0] == args.unnamed_args[1] ? "#t" : "#f");
}
int64_t Goos::number_to_integer(const Object& obj) {
switch (obj.type) {
case INTEGER:
return obj.integer_obj.value;
case FLOAT:
return (int64_t)obj.float_obj.value;
case CHAR:
return obj.char_obj.value;
default:
throw_eval_error(obj, "object cannot be interpreted as a number!");
}
return 0;
}
double Goos::number_to_float(const Object& obj) {
switch (obj.type) {
case INTEGER:
return obj.integer_obj.value;
case FLOAT:
return obj.float_obj.value;
case CHAR:
return obj.char_obj.value;
default:
throw_eval_error(obj, "object cannot be interpreted as a number!");
}
return 0;
}
char Goos::number_to_char(const Object& obj) {
switch (obj.type) {
case INTEGER:
return obj.integer_obj.value;
case FLOAT:
return obj.float_obj.value;
case CHAR:
return obj.char_obj.value;
default:
throw_eval_error(obj, "object cannot be interpreted as a number!");
}
return 0;
}
template <>
double Goos::number(const Object& obj) {
return number_to_float(obj);
}
template <>
int64_t Goos::number(const Object& obj) {
return number_to_integer(obj);
}
template <>
char Goos::number(const Object& obj) {
return number_to_char(obj);
}
template <typename T>
Object Goos::num_plus(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env) {
(void)env;
(void)form;
T result = 0;
for (const auto& arg : args.unnamed_args) {
result += number<T>(arg);
}
return Object::make_number<T>(result);
}
template <typename T>
Object Goos::num_times(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env) {
(void)env;
(void)form;
T result = 1;
for (const auto& arg : args.unnamed_args) {
result *= number<T>(arg);
}
return Object::make_number<T>(result);
}
template <typename T>
Object Goos::num_minus(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env) {
(void)env;
(void)form;
T result = 0;
if (args.unnamed_args.size() > 1) {
result = number<T>(args.unnamed_args[0]);
for (uint32_t i = 1; i < args.unnamed_args.size(); i++) {
result -= number<T>(args.unnamed_args[i]);
}
} else {
result = -number<T>(args.unnamed_args[0]);
}
return Object::make_number<T>(result);
}
Object Goos::eval_plus(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env) {
if (!args.named_args.empty() || args.unnamed_args.empty())
throw_eval_error(form, "+ must receive at least one unnamed argument!");
switch (args.unnamed_args.front().type) {
case INTEGER:
return num_plus<int64_t>(form, args, env);
case FLOAT:
return num_plus<double>(form, args, env);
case CHAR:
return num_plus<char>(form, args, env);
default:
throw_eval_error(form, "+ must have a numeric argument");
return EmptyListObject::make_new();
}
}
Object Goos::eval_times(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env) {
if (!args.named_args.empty() || args.unnamed_args.empty())
throw_eval_error(form, "* must receive at least one unnamed argument!");
switch (args.unnamed_args.front().type) {
case INTEGER:
return num_times<int64_t>(form, args, env);
case FLOAT:
return num_times<double>(form, args, env);
case CHAR:
return num_times<char>(form, args, env);
default:
throw_eval_error(form, "* must have a numeric argument");
return EmptyListObject::make_new();
}
}
Object Goos::eval_minus(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env) {
if (!args.named_args.empty() || args.unnamed_args.empty())
throw_eval_error(form, "- must receive at least one unnamed argument!");
switch (args.unnamed_args.front().type) {
case INTEGER:
return num_minus<int64_t>(form, args, env);
case FLOAT:
return num_minus<double>(form, args, env);
case CHAR:
return num_minus<char>(form, args, env);
default:
throw_eval_error(form, "- must have a numeric argument");
return EmptyListObject::make_new();
}
}
Object Goos::eval_numequals(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env) {
(void)env;
if (!args.named_args.empty() || args.unnamed_args.size() < 2)
throw_eval_error(form, "= must receive at least two unnamed arguments!");
bool result = true;
switch (args.unnamed_args.front().type) {
case INTEGER: {
int64_t ref = number_to_integer(args.unnamed_args.front());
for (uint32_t i = 1; i < args.unnamed_args.size(); i++) {
if (ref != number_to_integer(args.unnamed_args[i])) {
result = false;
break;
}
}
} break;
case FLOAT: {
double ref = number_to_float(args.unnamed_args.front());
for (uint32_t i = 1; i < args.unnamed_args.size(); i++) {
if (ref != number_to_float(args.unnamed_args[i])) {
result = false;
break;
}
}
} break;
case CHAR: {
char ref = number_to_char(args.unnamed_args.front());
for (uint32_t i = 1; i < args.unnamed_args.size(); i++) {
if (ref != number_to_char(args.unnamed_args[i])) {
result = false;
break;
}
}
} break;
default:
throw_eval_error(form, "+ must have a numeric argument");
return EmptyListObject::make_new();
}
return SymbolObject::make_new(reader.symbolTable, result ? "#t" : "#f");
}
template <typename T>
Object Goos::num_lt(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env) {
(void)form;
(void)env;
T a = number<T>(args.unnamed_args[0]);
T b = number<T>(args.unnamed_args[1]);
return SymbolObject::make_new(reader.symbolTable, (a < b) ? "#t" : "#f");
}
Object Goos::eval_lt(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env) {
if (!args.named_args.empty() || args.unnamed_args.size() != 2)
throw_eval_error(form, "< must receive two unnamed arguments!");
switch (args.unnamed_args.front().type) {
case INTEGER:
return num_lt<int64_t>(form, args, env);
case FLOAT:
return num_lt<double>(form, args, env);
case CHAR:
return num_lt<char>(form, args, env);
default:
throw_eval_error(form, "< must have a numeric argument");
return EmptyListObject::make_new();
}
}
template <typename T>
Object Goos::num_gt(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env) {
(void)form;
(void)env;
T a = number<T>(args.unnamed_args[0]);
T b = number<T>(args.unnamed_args[1]);
return SymbolObject::make_new(reader.symbolTable, (a > b) ? "#t" : "#f");
}
Object Goos::eval_gt(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env) {
if (!args.named_args.empty() || args.unnamed_args.size() != 2)
throw_eval_error(form, "> must receive two unnamed arguments!");
switch (args.unnamed_args.front().type) {
case INTEGER:
return num_gt<int64_t>(form, args, env);
case FLOAT:
return num_gt<double>(form, args, env);
case CHAR:
return num_gt<char>(form, args, env);
default:
throw_eval_error(form, "> must have a numeric argument");
return EmptyListObject::make_new();
}
}
template <typename T>
Object Goos::num_leq(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env) {
(void)form;
(void)env;
T a = number<T>(args.unnamed_args[0]);
T b = number<T>(args.unnamed_args[1]);
return SymbolObject::make_new(reader.symbolTable, (a <= b) ? "#t" : "#f");
}
Object Goos::eval_leq(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env) {
if (!args.named_args.empty() || args.unnamed_args.size() != 2)
throw_eval_error(form, "<= must receive two unnamed arguments!");
switch (args.unnamed_args.front().type) {
case INTEGER:
return num_leq<int64_t>(form, args, env);
case FLOAT:
return num_leq<double>(form, args, env);
case CHAR:
return num_leq<char>(form, args, env);
default:
throw_eval_error(form, "<= must have a numeric argument");
return EmptyListObject::make_new();
}
}
template <typename T>
Object Goos::num_geq(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env) {
(void)form;
(void)env;
T a = number<T>(args.unnamed_args[0]);
T b = number<T>(args.unnamed_args[1]);
return SymbolObject::make_new(reader.symbolTable, (a >= b) ? "#t" : "#f");
}
Object Goos::eval_geq(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env) {
if (!args.named_args.empty() || args.unnamed_args.size() != 2)
throw_eval_error(form, ">= must receive two unnamed arguments!");
switch (args.unnamed_args.front().type) {
case INTEGER:
return num_geq<int64_t>(form, args, env);
case FLOAT:
return num_geq<double>(form, args, env);
case CHAR:
return num_geq<char>(form, args, env);
default:
throw_eval_error(form, ">= must have a numeric argument");
return EmptyListObject::make_new();
}
}
Object Goos::eval_eval(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env) {
if (!args.named_args.empty() || args.unnamed_args.size() != 1) {
throw_eval_error(form, "eval must receive exactly one argument!");
}
return eval(args.unnamed_args[0], env);
}
Object Goos::eval_car(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env) {
(void)env;
if (!args.named_args.empty() || args.unnamed_args.size() != 1 ||
args.unnamed_args[0].type != PAIR)
throw_eval_error(form, "car must receive a single pair argument");
return args.unnamed_args[0].as_pair()->car;
}
Object Goos::eval_cdr(const Object& form, GoosArgs& args, std::shared_ptr<EnvironmentObject>& env) {
(void)env;
if (!args.named_args.empty() || args.unnamed_args.size() != 1 ||
args.unnamed_args[0].type != PAIR)
throw_eval_error(form, "cdr must receive a single pair argument");
return args.unnamed_args[0].as_pair()->cdr;
}
Object Goos::eval_gensym(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env) {
(void)env;
if (!args.named_args.empty())
throw_eval_error(form, "gensym must take 1 or 0 arguments");
if (args.unnamed_args.size() == 0) {
return SymbolObject::make_new(reader.symbolTable, "gensym" + std::to_string(gensym_id++));
} else if (args.unnamed_args.size() == 1 && args.unnamed_args[0].type == SYMBOL) {
return SymbolObject::make_new(
reader.symbolTable,
"gs-" + args.unnamed_args[0].as_symbol()->name + std::to_string(gensym_id++));
} else {
throw_eval_error(form, "gensym error");
}
return EmptyListObject::make_new();
}
Object Goos::eval_cons(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env) {
(void)env;
if (!args.named_args.empty() || args.unnamed_args.size() != 2)
throw_eval_error(form, "cons must receive two unnamed arguments!");
return PairObject::make_new(args.unnamed_args[0], args.unnamed_args[1]);
}
Object Goos::eval_null(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env) {
(void)env;
if (!args.named_args.empty() || args.unnamed_args.size() != 1)
throw_eval_error(form, "null? must receive one unnamed argument!");
return SymbolObject::make_new(reader.symbolTable,
args.unnamed_args[0].type == EMPTY_LIST ? "#t" : "#f");
}
Object Goos::eval_type(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env) {
(void)env;
if (!args.named_args.empty() || args.unnamed_args.size() != 2)
throw_eval_error(form, "type? must receive two unnamed arguments!");
auto type = args.unnamed_args.at(0);
auto val = args.unnamed_args.at(1);
if (type.type != SYMBOL) {
throw_eval_error(form, "invalid type name");
}
auto tas = type.as_symbol();
bool matches = true;
if (tas->name == "string") {
matches = (val.type == STRING);
} else if (tas->name == "symbol") {
matches = (val.type == SYMBOL);
} else {
throw_eval_error(form, "invalid type to type?");
}
if (matches) {
return SymbolObject::make_new(reader.symbolTable, "#t");
} else {
return SymbolObject::make_new(reader.symbolTable, "#f");
}
}
Object Goos::eval_current_method_type(const Object& form,
GoosArgs& args,
std::shared_ptr<EnvironmentObject>& env) {
(void)env;
if (!args.unnamed_args.empty() || !args.named_args.empty()) {
throw_eval_error(form, "current-method-type accepts no arguments");
}
return SymbolObject::make_new(reader.symbolTable, goal_to_goos.enclosing_method_type);
}
+81
View File
@@ -0,0 +1,81 @@
#include <memory.h>
#include "GoosTest.h"
GoosTest::GoosTest(const std::string& filename) : file(filename) {}
bool GoosTest::run() {
printf("-------------------------------------\n");
try {
auto test_program = goos.reader.read_from_file(file);
goos.eval_with_rewind(test_program, goos.global_environment.as_env());
} catch (std::runtime_error& e) {
printf("Test in file %s threw exception:\n%s\n", file.c_str(), e.what());
return false;
}
printf("Test file: %s\n", basename(file.c_str()));
printf("Test name: %s\n", goos.get_object_by_name("*test-name*").print().c_str());
Object lhs = goos.get_object_by_name("*test-expected*");
Object rhs = goos.get_object_by_name("*test-actual*");
if (lhs == rhs) {
printf("got %s\n", lhs.print().c_str());
return true;
} else {
printf("%s\nis not equal to\n%s\n", lhs.print().c_str(), rhs.print().c_str());
return false;
}
}
static const std::string test_prefix = "goal/gs/tests/";
bool run_all_tests() {
std::vector<std::string> test_files;
try {
Goos goos;
Object test_def_prog = goos.reader.read_from_file(test_prefix + "test-definition.gs");
Object test_list = goos.eval_with_rewind(test_def_prog, goos.global_environment.as_env());
Object o = test_list;
for (;;) {
if (o.type == PAIR) {
auto op = o.as_pair();
auto test_obj = op->car;
if (test_obj.type != STRING) {
throw std::runtime_error("invalid test name " + test_obj.print());
}
test_files.push_back(test_obj.as_string()->data);
o = op->cdr;
} else if (o.type == EMPTY_LIST) {
break;
} else {
throw std::runtime_error("malformed test list");
}
}
} catch (std::exception& e) {
printf("failed to load test list: %s\n", e.what());
}
bool all_okay = true;
std::vector<std::string> failed = {};
for (const auto& test : test_files) {
auto test_name = test_prefix + test;
GoosTest goos_test(test_name);
if (!goos_test.run()) {
failed.push_back(test);
all_okay = false;
}
}
printf("-------------------------------------\n");
if (all_okay) {
printf("all tests passed!\n");
} else {
printf("failed tests:\n");
for (const auto& fail : failed) {
printf(" %s\n", fail.c_str());
}
}
return all_okay;
}
+24
View File
@@ -0,0 +1,24 @@
#ifndef COMPILER_GOOSTEST_H
#define COMPILER_GOOSTEST_H
#include <string>
#include "goos/Goos.h"
class GoosTest {
public:
GoosTest(const std::string& filename);
Goos goos;
bool run();
std::string name;
std::string description;
Object expected, actual;
private:
std::string file;
};
bool run_all_tests();
#endif // COMPILER_GOOSTEST_H
+19
View File
@@ -0,0 +1,19 @@
#include "Object.h"
// EmptyListObject* gEmptyList = nullptr;
std::shared_ptr<EmptyListObject> gEmptyList = nullptr;
template <>
Object Object::make_number(double value) {
return Object::make_float(value);
}
template <>
Object Object::make_number(int64_t value) {
return Object::make_integer(value);
}
template <>
Object Object::make_number(char value) {
return Object::make_char(value);
}
+524
View File
@@ -0,0 +1,524 @@
#ifndef COMPILER_Object_H
#define COMPILER_Object_H
#include <cstdint>
#include <string>
#include <memory>
#include <unordered_map>
#include <vector>
#include <cassert>
class Object;
enum ObjectType : uint8_t {
INTEGER,
FLOAT,
CHAR,
EMPTY_LIST,
SYMBOL,
STRING,
PAIR,
// todo ARRAY,
LAMBDA,
MACRO,
ENVIRONMENT,
INVALID
};
template <typename T>
std::string object_type_to_string(T x) {
return std::to_string(x);
}
template <>
inline std::string object_type_to_string(double x) {
char buff[256];
sprintf(buff, "%g", x);
return std::string(buff);
}
template <>
inline std::string object_type_to_string(char x) {
char buff[256];
// printable
if (x >= 33 && x <= 126) {
sprintf(buff, "#\\%c", x);
return std::string(buff);
}
switch (x) {
case '\n':
sprintf(buff, "#\\newline");
break;
case ' ':
sprintf(buff, "#\\space");
break;
default:
sprintf(buff, "#\\{%d}", (uint8_t)x);
}
// not printable
return std::string(buff);
}
// non-heap-allocated object
template <typename T>
class FixedTypeObject {
public:
T value;
explicit FixedTypeObject(T v) : value(v) {}
FixedTypeObject() = default;
std::string print() { return object_type_to_string(value); }
std::string inspect() {
return type_as_string() + "\n value: " + object_type_to_string(value) + "\n";
}
~FixedTypeObject() = default;
private:
std::string type_as_string() {
if (std::is_same<T, double>())
return "[Float]";
if (std::is_same<T, int64_t>())
return "[Integer]";
if (std::is_same<T, char>())
return "[Char]";
return "[Unknown Fixed Type]";
}
};
using IntegerObject = FixedTypeObject<int64_t>;
using FloatObject = FixedTypeObject<double>;
using CharObject = FixedTypeObject<char>;
// for objects which are heap allocated (reference semantics
// these use virtual methods to implement print/insepct...
class AllocObject {
public:
virtual std::string print() = 0;
virtual std::string inspect() = 0;
virtual ~AllocObject() = default;
};
class PairObject;
class EnvironmentObject;
class SymbolObject;
class StringObject;
class LambdaObject;
class MacroObject;
// wrapper for both heap allocated and value objects.
class Object {
public:
std::shared_ptr<AllocObject> alloc = nullptr;
union {
IntegerObject integer_obj;
FloatObject float_obj;
CharObject char_obj;
};
ObjectType type = INVALID;
std::string print() {
switch (type) {
case INTEGER:
return integer_obj.print();
case FLOAT:
return float_obj.print();
case CHAR:
return char_obj.print();
default:
return alloc->print();
}
}
std::string inspect() {
switch (type) {
case INTEGER:
return integer_obj.inspect();
case FLOAT:
return float_obj.inspect();
case CHAR:
return char_obj.inspect();
default:
return alloc->inspect();
}
}
template <typename T>
static Object make_number(T value);
static Object make_integer(int64_t value) {
Object o;
o.type = INTEGER;
o.integer_obj.value = value;
return o;
}
static Object make_float(double value) {
Object o;
o.type = FLOAT;
o.float_obj.value = value;
return o;
}
static Object make_char(char value) {
Object o;
o.type = CHAR;
o.char_obj.value = value;
return o;
}
std::shared_ptr<PairObject> as_pair() {
assert(type == PAIR);
return std::dynamic_pointer_cast<PairObject>(alloc);
}
std::shared_ptr<EnvironmentObject> as_env() {
assert(type == ENVIRONMENT);
return std::dynamic_pointer_cast<EnvironmentObject>(alloc);
}
std::shared_ptr<SymbolObject> as_symbol() const {
assert(type == SYMBOL);
return std::dynamic_pointer_cast<SymbolObject>(alloc);
}
std::shared_ptr<StringObject> as_string() const {
assert(type == STRING);
return std::dynamic_pointer_cast<StringObject>(alloc);
}
std::shared_ptr<LambdaObject> as_lambda() {
assert(type == LAMBDA);
return std::dynamic_pointer_cast<LambdaObject>(alloc);
}
std::shared_ptr<MacroObject> as_macro() {
assert(type == MACRO);
return std::dynamic_pointer_cast<MacroObject>(alloc);
}
};
// There is a single heap allocated EmptyListObject.
class EmptyListObject;
extern std::shared_ptr<EmptyListObject> gEmptyList;
class EmptyListObject : public AllocObject {
public:
EmptyListObject() = default;
static Object make_new() {
Object obj;
obj.type = EMPTY_LIST;
if (!gEmptyList) {
gEmptyList = std::make_shared<EmptyListObject>();
}
obj.alloc = gEmptyList;
return obj;
}
std::string print() override { return "()"; }
std::string inspect() override {
char buff[256];
sprintf(buff, "[Empty List]\n");
return std::string(buff);
}
~EmptyListObject() = default;
};
class StringObject : public AllocObject {
public:
std::string data;
explicit StringObject(const std::string& text) : data(text) {}
static Object make_new(const std::string& text) {
Object obj;
obj.type = STRING;
obj.alloc = std::make_shared<StringObject>(text);
return obj;
}
std::string print() override { return "\"" + data + "\""; }
std::string inspect() override {
return "[String]\n data: " + data + "\n length: " + std::to_string(data.size()) + "\n";
}
~StringObject() = default;
};
class PairObject : public AllocObject {
public:
Object car, cdr;
PairObject(Object car_, Object cdr_) : car(car_), cdr(cdr_) {}
static Object make_new(Object a, Object b) {
Object obj;
obj.type = PAIR;
obj.alloc = std::make_shared<PairObject>(a, b);
return obj;
}
std::string print() override {
std::pair<Object, Object> to_print_pair = std::make_pair(car, cdr);
std::string result = "(";
// print first thing:
result += car.print();
// print second thing
Object to_print = cdr;
if (to_print.type == EMPTY_LIST) {
result += ")";
return result;
} else {
result += " ";
}
for (;;) {
if (to_print.type == PAIR) {
Object to_print_car = std::dynamic_pointer_cast<PairObject>(to_print.alloc)->car;
result += to_print_car.print();
to_print = std::dynamic_pointer_cast<PairObject>(to_print.alloc)->cdr;
if (to_print.type == EMPTY_LIST) {
result += ")";
return result;
} else {
result += " ";
}
} else {
result += ". ";
result += to_print.print();
result += ")";
return result;
}
}
}
std::string inspect() override { return "[Pair]\n value: " + print() + "\n"; }
~PairObject() = default;
};
class SymbolTable;
class SymbolObject : public AllocObject {
public:
std::string name;
explicit SymbolObject(const std::string& c) : name(c) {}
static Object make_new(SymbolTable& st, const std::string& name);
std::string print() override { return name; }
std::string inspect() override {
char buff[1024];
sprintf(buff, "[Symbol]\n name: %s\n value: 0x%lx\n", name.c_str(), (uint64_t)(this));
return std::string(buff);
}
~SymbolObject() = default;
};
class SymbolTable {
public:
std::shared_ptr<SymbolObject> intern(const std::string& name) {
auto kv = table.find(name);
if (kv == table.end()) {
auto iter = table.insert({name, std::make_shared<SymbolObject>(name)});
return (*iter.first).second;
} else {
return kv->second;
}
}
~SymbolTable() = default;
private:
std::unordered_map<std::string, std::shared_ptr<SymbolObject>> table;
};
inline Object SymbolObject::make_new(SymbolTable& st, const std::string& name) {
Object obj;
obj.type = SYMBOL;
obj.alloc = st.intern(name);
return obj;
}
class EnvironmentObject : public AllocObject {
public:
std::string name;
std::shared_ptr<EnvironmentObject> parent_env;
std::unordered_map<std::shared_ptr<SymbolObject>, Object> vars;
EnvironmentObject() = default;
static Object make_new() {
Object obj;
obj.type = ENVIRONMENT;
obj.alloc = std::make_shared<EnvironmentObject>();
return obj;
}
std::string print() override {
if (name.empty()) {
return "<unnamed environment>";
} else {
return "<environment \"" + name + "\">";
}
}
std::string inspect() override {
std::string result = "[Environment]\n name: " + name +
"\n parent: " + (parent_env ? parent_env->print() : "NONE") +
"\n vars:\n";
for (auto kv : vars) {
result += " " + kv.first->print() + ": " + kv.second.print() + "\n";
}
return result;
}
};
class LambdaObject : public AllocObject {
public:
std::string name;
std::shared_ptr<EnvironmentObject> parent_env;
Object body;
std::vector<Object> unnamed_args;
std::unordered_map<std::string, Object> named_args;
Object rest_args;
bool has_rest = false;
LambdaObject() = default;
static Object make_new() {
Object obj;
obj.type = LAMBDA;
obj.alloc = std::make_shared<LambdaObject>();
return obj;
}
std::string print() override {
if (name.empty()) {
return "<unnamed procedure>";
} else {
return "<procedure \"" + name + "\">";
}
}
std::string inspect() override {
std::string result = "[Procedure]\n name: " + name + "\n unnamed args:\n";
for (auto& arg : unnamed_args) {
result += " " + arg.print() + "\n";
}
result += " named args:\n";
for (auto& arg : named_args) {
result += " " + arg.first + " : " + arg.second.print() + "\n";
}
if (has_rest) {
result += " rest: " + rest_args.print() + "\n";
}
return result;
}
};
class MacroObject : public AllocObject {
public:
std::string name;
std::shared_ptr<EnvironmentObject> parent_env;
Object body;
std::vector<Object> unnamed_args;
std::unordered_map<std::string, Object> named_args;
Object rest_args;
bool has_rest = false;
MacroObject() = default;
static Object make_new() {
Object obj;
obj.type = MACRO;
obj.alloc = std::make_shared<MacroObject>();
return obj;
}
std::string print() override {
if (name.empty()) {
return "<unnamed macro>";
} else {
return "<macro \"" + name + "\">";
}
}
std::string inspect() override {
std::string result = "[Macro]\n name: " + name + "\n unnamed args:\n";
for (auto& arg : unnamed_args) {
result += " " + arg.print() + "\n";
}
result += " named args:\n";
for (auto& arg : named_args) {
result += " " + arg.first + " : " + arg.second.print() + "\n";
}
if (has_rest) {
result += " rest: " + rest_args.print() + "\n";
}
return result;
}
};
inline Object build_list(const std::vector<Object>& objects) {
if (objects.empty()) {
return EmptyListObject::make_new();
}
Object empty = EmptyListObject::make_new();
Object head = PairObject::make_new(objects[0], empty);
Object last = head;
for (std::size_t i = 1; i < objects.size(); i++) {
last.as_pair()->cdr = PairObject::make_new(objects[i], empty);
last = last.as_pair()->cdr;
}
return head;
}
inline bool operator==(Object& lhs, Object& rhs) {
if (lhs.type != rhs.type)
return false;
switch (lhs.type) {
case STRING:
return lhs.as_string()->data == rhs.as_string()->data;
case INTEGER:
return lhs.integer_obj.value == rhs.integer_obj.value;
case FLOAT:
return lhs.float_obj.value == rhs.float_obj.value;
case SYMBOL:
case ENVIRONMENT:
case LAMBDA:
case MACRO:
return lhs.alloc == rhs.alloc;
// todo, the rest.
default:
throw std::runtime_error("equality not implemented for " + lhs.print());
}
}
#endif // COMPILER_Object_H
+1
View File
@@ -0,0 +1 @@
add_library(listener_old SHARED Listener.cpp)
+369
View File
@@ -0,0 +1,369 @@
#include <cstdio>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <cassert>
#include <netinet/tcp.h>
#include "Listener.h"
constexpr bool debug_listener = false;
struct DeciHeader {
uint16_t len;
uint16_t rsvd;
uint16_t proto;
uint8_t src;
uint8_t dest;
};
struct MessageHeader {
DeciHeader deci2_hdr;
uint16_t msg_kind;
uint16_t u6;
uint32_t msg_size;
uint64_t u8;
};
#include "logger/Logger.h"
Listener::Listener() {
buffer = new char[BUFFER_SIZE];
}
Listener::~Listener() {
delete[] buffer;
}
void Listener::listen_to_target(std::string ip, int port) {
if (connected) {
printf("already connected, doing nothing!\n");
return;
}
printf("Connecting to target...\n");
int tries = 0;
int rv = -1;
socket_fd = -1;
while (tries < 50) {
if (tries) {
usleep(10000);
}
if (socket_fd >= 0)
close(socket_fd);
tries++;
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd < 0) {
printf("[Error] Listener failed to create socket.\n");
continue;
}
timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 100000;
if (setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < 0) {
printf("[Error] setsockopt failed\n");
continue;
}
int one = 1;
if (setsockopt(socket_fd, SOL_TCP, TCP_NODELAY, &one, sizeof(one))) {
printf("[Error] failed to TCP_NODELAY\n");
continue;
}
sockaddr_in server_address;
server_address.sin_family = AF_INET;
server_address.sin_port = htons(port);
if (inet_pton(AF_INET, ip.c_str(), &server_address.sin_addr) <= 0) {
printf("[Error] Listener was given invalid IP address.\n");
continue;
}
rv = connect(socket_fd, (sockaddr*)&server_address, sizeof(server_address));
if (rv < 0) {
continue;
}
int32_t version_buffer[2] = {-1, -1};
int read_tries = 0;
int prog = 0;
bool ok = true;
while (prog < 8) {
auto r = read(socket_fd, version_buffer + prog, 8 - prog);
if (r < 0) {
ok = false;
break;
}
prog += r;
read_tries++;
if (read_tries > 50) {
ok = false;
break;
}
}
if (!ok)
continue;
printf("Got version %d.%d", version_buffer[0], version_buffer[1]);
if (version_buffer[0] == GOAL_VERSION_MAJOR && version_buffer[1] == GOAL_VERSION_MINOR) {
printf(" OK!\n");
set_connected(true);
return;
} else {
printf(", expected %d.%d. Cannot connect.\n", GOAL_VERSION_MAJOR, GOAL_VERSION_MINOR);
return;
}
}
printf("Failed to connect.\n");
}
void Listener::send_raw_data(const char* data, int size) {
int total_size = size + sizeof(MessageHeader);
if (total_size > BUFFER_SIZE) {
printf("[ERROR] DECI2 send_raw_data got too big of a message!\n");
return;
}
auto* header = (MessageHeader*)buffer;
auto* buffer_data = (char*)(header + 1);
header->deci2_hdr.rsvd = 0;
header->deci2_hdr.len = total_size;
header->deci2_hdr.proto = 0xe042; // todo don't hardcode
header->deci2_hdr.src = 'H';
header->deci2_hdr.dest = 'E';
header->msg_size = size;
header->msg_kind = 0;
header->u6 = 0;
header->u8 = 0;
memcpy(buffer_data, data, size);
send_buffer(total_size);
}
void Listener::send_code(std::vector<uint8_t>& code) {
receiver_got_ack = false;
int total_size = code.size() + sizeof(MessageHeader);
if (total_size > BUFFER_SIZE) {
printf("[ERROR] Listener send_code got too big of a message\n");
return;
}
auto* header = (MessageHeader*)buffer;
auto* buffer_data = (char*)(header + 1);
header->deci2_hdr.rsvd = 0;
header->deci2_hdr.len = total_size;
header->deci2_hdr.proto = 0xe042; // todo don't hardcode
header->deci2_hdr.src = 'H';
header->deci2_hdr.dest = 'E';
header->msg_size = code.size();
header->msg_kind = LTT_MSG_CODE;
header->u6 = 0;
header->u8 = 0;
memcpy(buffer_data, code.data(), code.size());
send_buffer(total_size);
}
void Listener::send_reset() {
if (!connected) {
printf("Not connected, so cannot reset target.\n");
return;
}
auto* header = (MessageHeader*)buffer;
header->deci2_hdr.rsvd = 0;
header->deci2_hdr.len = sizeof(MessageHeader);
header->deci2_hdr.proto = 0xe042; // todo don't hardcode
header->deci2_hdr.src = 'H';
header->deci2_hdr.dest = 'E';
header->msg_size = 0;
header->msg_kind = LTT_MSG_RESET;
header->u6 = 0;
header->u8 = 0;
send_buffer(sizeof(MessageHeader));
set_connected(false);
close(socket_fd);
printf("closed connection to target\n");
}
void Listener::send_buffer(int sz) {
int wrote = 0;
if (debug_listener) {
printf("[L -> T] sending %d bytes...\n", sz);
}
receiver_got_ack = false;
while (wrote < sz) {
auto to_send = std::min(512, sz - wrote);
auto x = write(socket_fd, buffer + wrote, to_send);
wrote += x;
}
if (debug_listener) {
printf(" waiting for ack...\n");
}
if (wait_for_ack()) {
if (debug_listener) {
printf("ack buff:\n");
printf("%s\n", ack_recv_buff);
printf(" OK\n");
}
} else {
printf(" NG - target has timed out. If it has died, disconnect with (disconnect-target)\n");
}
}
void Listener::send_poke() {
if (!connected) {
printf("Not connected, so cannot set target status!\n");
return;
}
auto* header = (MessageHeader*)buffer;
header->deci2_hdr.rsvd = 0;
header->deci2_hdr.len = sizeof(MessageHeader);
header->deci2_hdr.proto = 0xe042; // todo don't hardcode
header->deci2_hdr.src = 'H';
header->deci2_hdr.dest = 'E';
header->msg_size = 0;
header->msg_kind = LTT_MSG_POKE;
header->u6 = 0;
header->u8 = 0;
send_buffer(sizeof(MessageHeader));
}
void Listener::receive_data() {
while (connected) {
int rcvd = 0;
int rcvd_desired = sizeof(MessageHeader);
char buff[sizeof(MessageHeader)];
while (rcvd < rcvd_desired) {
auto got = read(socket_fd, buff + rcvd, rcvd_desired - rcvd);
rcvd += got > 0 ? got : 0;
if (got == 0 || (got == -1 && errno != EAGAIN)) {
connected = false;
}
if (!connected)
return;
}
MessageHeader* hdr = (MessageHeader*)buff;
if (debug_listener) {
printf("[T -> L] received %d bytes, kind %d\n", hdr->deci2_hdr.len, hdr->msg_kind);
}
switch (hdr->msg_kind) {
case MSG_ACK:
if (hdr->deci2_hdr.len < 512) {
int ack_recv_prog = 0;
while (rcvd < hdr->deci2_hdr.len) {
if (!connected)
return;
int got = read(socket_fd, ack_recv_buff + ack_recv_prog, hdr->deci2_hdr.len - rcvd);
got = got > 0 ? got : 0;
rcvd += got;
ack_recv_prog += got;
}
ack_recv_buff[ack_recv_prog] = '\0';
assert(ack_recv_prog < 512);
receiver_got_ack = true;
} else {
printf("got invalid ack!\n");
}
break;
case MSG_OUTPUT:
case MSG_PRINT: {
auto* str_buff = new char[hdr->msg_size + 1];
int msg_prog = 0;
while (rcvd < hdr->deci2_hdr.len) {
if (!connected)
return;
int got = read(socket_fd, str_buff + msg_prog, hdr->deci2_hdr.len - rcvd);
got = got > 0 ? got : 0;
rcvd += got;
msg_prog += got;
}
str_buff[hdr->msg_size] = '\0';
if (hdr->msg_kind == MSG_PRINT) {
rcv_mtx.lock();
pending_messages.emplace_back(str_buff);
gLogger.log(MSG_TGT, "%s\n", pending_messages.back().c_str());
rcv_mtx.unlock();
} else {
gLogger.log(MSG_TGT_INFO, "NOTE: %s\n", str_buff);
}
} break;
default:
printf("unhandled message type %d from target\n", hdr->msg_kind);
break;
}
}
}
void Listener::clear_pending_incoming() {
rcv_mtx.lock();
pending_messages.clear();
rcv_mtx.unlock();
}
std::string Listener::pop_pending() {
std::string result;
rcv_mtx.lock();
if (!pending_messages.empty()) {
result = pending_messages.back();
pending_messages.pop_back();
}
rcv_mtx.unlock();
return result;
}
bool Listener::has_pending() {
rcv_mtx.lock();
bool r = !pending_messages.empty();
rcv_mtx.unlock();
return r;
}
void Listener::set_connected(bool con) {
if (con) {
connected = true;
receiver_got_ack = false;
if (thread_running) {
rcv_thread.join();
thread_running = false;
}
rcv_thread = std::thread(&Listener::receive_data, this);
thread_running = true;
} else {
connected = false;
if (thread_running)
rcv_thread.join();
thread_running = false;
}
}
bool Listener::wait_for_ack() {
if (!connected) {
printf("Can't wait for ack if we aren't connected!\n");
}
for (int i = 0; i < 2000; i++) {
if (receiver_got_ack)
return true;
usleep(1000);
}
return false;
}
+45
View File
@@ -0,0 +1,45 @@
#ifndef JAK_LISTENER_H
#define JAK_LISTENER_H
#include <string>
#include <thread>
#include <mutex>
#include <vector>
#include "shared_config.h"
class Listener {
public:
static constexpr int BUFFER_SIZE = 4096 * 4096;
Listener();
~Listener();
void listen_to_target(std::string ip = "127.0.0.1", int port = 8112);
bool is_connected() { return connected; }
void send_raw_data(const char* data, int size);
void send_reset();
void send_poke();
void send_code(std::vector<uint8_t>& code);
void receive_data();
bool wait_for_ack();
void clear_pending_incoming();
std::string pop_pending();
bool has_pending();
void set_connected(bool con);
bool receiver_got_ack = false;
bool thread_running = false;
private:
void send_buffer(int sz);
char* buffer;
int socket_fd;
bool connected = false;
std::thread rcv_thread;
std::mutex rcv_mtx;
std::vector<std::string> pending_messages;
char ack_recv_buff[512];
};
#endif // JAK_LISTENER_H
+1
View File
@@ -0,0 +1 @@
add_library(logger SHARED Logger.cpp)
+67
View File
@@ -0,0 +1,67 @@
#include "Logger.h"
void Logger::close() {
if (fp) {
fclose(fp);
}
}
void Logger::set_file(std::string filename) {
if (fp) {
fclose(fp);
}
fp = fopen(filename.c_str(), "w");
if (!fp) {
throw std::runtime_error("invalid file name " + filename + " in logger");
}
}
void Logger::log(LoggerMessageKind kind, const char* format, ...) {
FILE* dest = nullptr;
auto& settings = config[kind];
switch (settings.kind) {
case LOG_STDERR:
dest = stderr;
break;
case LOG_STDOUT:
dest = stdout;
break;
case LOG_IGNORE:
dest = nullptr;
break;
case LOG_FILE:
dest = fp;
break;
default:
throw std::runtime_error("unknown log destination in log");
}
if (!dest)
return;
if (!settings.prefix.empty()) {
fprintf(dest, "%s", settings.prefix.c_str());
}
if (settings.color != COLOR_NORMAL) {
const char* color_codes[] = {"", "[0;31m", "[0;32m", "[0;36m"};
printf("\033%s", color_codes[settings.color]);
}
va_list arglist;
va_start(arglist, format);
vfprintf(dest, format, arglist);
va_end(arglist);
if (settings.color != COLOR_NORMAL) {
printf("\033[0m");
}
// todo, does this make things slow?
if (settings.kind == LOG_FILE) {
fflush(fp);
}
}
Logger gLogger;
+43
View File
@@ -0,0 +1,43 @@
#ifndef JAK_LOGGER_H
#define JAK_LOGGER_H
#include <string>
#include <cstdarg>
#include <unordered_map>
enum LoggerColor { COLOR_NORMAL, COLOR_RED, COLOR_GREEN, COLOR_BLUE };
enum LoggerDestKind { LOG_STDOUT, LOG_STDERR, LOG_FILE, LOG_IGNORE };
struct LoggerDest {
LoggerDestKind kind = LOG_STDOUT;
LoggerColor color = COLOR_NORMAL;
std::string prefix;
};
enum LoggerMessageKind {
MSG_GOAL,
MSG_ICE,
MSG_ERR,
MSG_COLOR,
MSG_EMIT,
MSG_DEBUG,
MSG_WARN,
MSG_TGT,
MSG_TGT_INFO,
};
class Logger {
public:
void set_file(std::string filename);
void log(LoggerMessageKind kind, const char* format, ...);
std::unordered_map<LoggerMessageKind, LoggerDest> config;
void close();
private:
FILE* fp = nullptr;
};
extern Logger gLogger;
#endif // JAK_LOGGER_H
+18
View File
@@ -0,0 +1,18 @@
#include <cstdio>
#include "goos/Goos.h"
#include "goos/GoosTest.h"
#include "goal/Goal.h"
#include "shared_config.h"
int main(int argc, char** argv) {
printf("Goal III v. %d.%d alpha (Game Oriented Assembly Lisp)\n", GOAL_VERSION_MAJOR,
GOAL_VERSION_MINOR);
if (argc > 1 && std::string("--test-goos") == argv[1])
run_all_tests();
Goal goal;
goal.execute_repl();
return 0;
}
+1
View File
@@ -0,0 +1 @@
add_library(reader_old SHARED Reader.cpp TextDb.cpp)
+431
View File
@@ -0,0 +1,431 @@
#include <iostream>
#include "Reader.h"
#include "TextDb.h"
#include "third-party/linenoise.h"
Reader::Reader() {
linenoise::SetHistoryMaxLen(400);
for (auto& x : valid_symbols_chars) {
x = false;
}
for (char x = 'a'; x <= 'z'; x++) {
valid_symbols_chars[(int)x] = true;
}
for (char x = 'A'; x <= 'Z'; x++) {
valid_symbols_chars[(int)x] = true;
}
for (char x = '0'; x <= '9'; x++) {
valid_symbols_chars[(int)x] = true;
}
const char bonus[] = "!$%&*+-/\\.,@^_-;:<>?~=#";
for (const char* c = bonus; *c; c++) {
valid_symbols_chars[(int)*c] = true;
}
}
Object Reader::read_from_stdin(const std::string& prompt_name) {
// // display prompt
// printf("%s> ", prompt_name.c_str());
//
// // read text
// std::string line;
// std::getline(std::cin, line);
std::string line;
std::string prompt_full = "\033[0m" + prompt_name + "> ";
linenoise::Readline(prompt_full.c_str(), line);
linenoise::AddHistory(line.c_str());
// todo, decide if we should keep reading or not.
// create text fragment and add to the DB
auto textFrag = std::make_shared<ReplText>(line);
db.insert(textFrag);
// perform read
auto result = internal_read(textFrag);
db.link(result, textFrag, 0);
return result;
}
Object Reader::read_from_string(const std::string& str) {
// create text fragment and add to the DB
auto textFrag = std::make_shared<ProgramString>(str);
db.insert(textFrag);
// perform read
auto result = internal_read(textFrag);
db.link(result, textFrag, 0);
return result;
}
Object Reader::read_from_file(const std::string& filename) {
auto textFrag = std::make_shared<FileText>(get_next_dir() + "/" + filename);
db.insert(textFrag);
auto result = internal_read(textFrag);
db.link(result, textFrag, 0);
return result;
}
Object Reader::internal_read(std::shared_ptr<ITextFragment> text) {
// first create stream
TextStream ts(text);
// clean up first whitespace
seek_past_whitespace_and_comments(ts);
// read list!
auto objs = read_list(ts, false);
return PairObject::make_new(SymbolObject::make_new(symbolTable, "top-level"), objs);
}
void Reader::seek_past_whitespace_and_comments(TextStream& stream) {
while (stream.text_remains()) {
char c = stream.peek();
switch (c) {
case ' ':
case '\t':
case '\n':
// just a whitespace, eat it!
stream.read();
break;
case ';':
// line comment.
while (stream.text_remains() && stream.read() != '\n') {
}
break;
case '#':
if (stream.text_remains(1) && stream.peek(1) == '|') {
assert(stream.read() == '#'); // #
assert(stream.read() == '|'); // |
bool found_end = false;
// find |#
while (stream.text_remains() && !found_end) {
// find |
while (stream.text_remains() && stream.read() != '|') {
}
if (stream.text_remains() && stream.read() == '#') {
found_end = true;
}
}
return;
} else {
// not a line comment
return;
}
break;
default:
return;
}
}
}
// given a stream starting at first character of the token,
// return the token. does not consume any whitespace at the end.
Token Reader::get_next_token(TextStream& stream) {
assert(stream.text_remains());
Token t;
t.source_line = stream.line_count;
t.source_offset = stream.seek;
t.source_text = stream.text;
char first = stream.read();
t.text.push_back(first);
// paren/double quote is its own token.
if (first == '(' || first == ')' || first == '"' || first == '\'' || first == '`')
return t;
if (first == ',' && stream.text_remains() && stream.peek() == '@') {
t.text.push_back(stream.read());
return t;
} else if (first == ',') {
return t;
}
while (stream.text_remains()) {
char next = stream.peek();
if (next == ' ' || next == '\n' || next == '\t' || next == ')' ||
next == ';' /*|| next == '#'*/ || next == '(') {
return t;
} else {
t.text.push_back(stream.read());
}
}
return t;
}
const static std::unordered_map<std::string, std::string> reader_macros = {
{"'", "quote"},
{"`", "quasiquote"},
{",", "unquote"},
{",@", "unquote-splicing"}};
// call on char after open paren.
Object Reader::read_list(TextStream& ts, bool expect_close_paren) {
seek_past_whitespace_and_comments(ts);
std::vector<Object> objects;
bool got_close_paren = false;
int start_offset = ts.seek;
while (ts.text_remains()) {
auto tok = get_next_token(ts);
// reader macro thing:
bool got_reader_macro = false;
std::string reader_macro_string;
auto kv = reader_macros.find(tok.text);
if (kv != reader_macros.end()) {
got_reader_macro = true;
reader_macro_string = kv->second;
tok = get_next_token(ts);
}
auto insert_object = [&](Object o) {
if (got_reader_macro) {
objects.push_back(
build_list({SymbolObject::make_new(symbolTable, reader_macro_string), o}));
} else {
objects.push_back(o);
}
};
if (tok.text.empty()) {
// empty list
break;
} else if (tok.text[0] == '(') {
assert(tok.text.length() == 1);
insert_object(read_list(ts, true));
seek_past_whitespace_and_comments(ts);
continue;
} else if (tok.text[0] == ')') {
got_close_paren = true;
assert(tok.text.length() == 1);
break;
} else {
Object obj;
// try as an array
// try as char
// try as integer
if (try_token_as_integer(tok, obj)) {
seek_past_whitespace_and_comments(ts);
insert_object(obj);
continue;
}
// try as hex
if (try_token_as_hex(tok, obj)) {
seek_past_whitespace_and_comments(ts);
insert_object(obj);
continue;
}
// try as binary
if (try_token_as_binary(tok, obj)) {
seek_past_whitespace_and_comments(ts);
insert_object(obj);
continue;
}
// try as float
if (try_token_as_float(tok, obj)) {
seek_past_whitespace_and_comments(ts);
insert_object(obj);
continue;
}
// try as bool
if (try_token_as_boolean(tok, obj)) {
seek_past_whitespace_and_comments(ts);
insert_object(obj);
continue;
}
// try as string
if (tok.text[0] == '"') {
// it's a string.
assert(tok.text.length() == 1);
if (read_string(ts, obj)) {
seek_past_whitespace_and_comments(ts);
insert_object(obj);
} else {
display_error_info(ts, "failed to read string, close quote not found");
}
continue;
}
// try as symbol
if (try_token_as_symbol(tok, obj)) {
insert_object(obj);
seek_past_whitespace_and_comments(ts);
continue;
}
display_error_info(ts, "invalid token encountered in reader: " + tok.text);
}
}
if (expect_close_paren && !got_close_paren) {
display_error_info(ts, "failed to find close paren");
}
if (got_close_paren && !expect_close_paren) {
display_error_info(ts, "found an unexpected close paren");
}
auto rv = build_list(objects);
db.link(rv, ts.text, start_offset);
return rv;
}
bool Reader::try_token_as_symbol(const Token& tok, Object& obj) {
// check start character is valid:
assert(!tok.text.empty());
char start = tok.text[0];
// if(start == '!' || start == '*' || start == '/' || start == '+' || start == '-' || start == '='
// || start == '<' || start == '>' || start == '?' || (start >= 'A' && start < 'Z') || (start >=
// 'a' && start <= 'z')) {
if (valid_symbols_chars[(int)start]) {
obj = SymbolObject::make_new(symbolTable, tok.text);
return true;
} else {
return false;
}
}
// todo, handle \n in a string.
bool Reader::read_string(TextStream& stream, Object& obj) {
bool got_close_quote = false;
std::string str;
while (stream.text_remains()) {
char c = stream.read();
if (c == '"') {
obj = StringObject::make_new(str);
got_close_quote = true;
break;
}
str.push_back(c);
}
return got_close_quote;
}
static bool number_start(char c) {
return (c >= '0' && c <= '9') || c == '-';
}
static bool str_contains(const std::string& str, char c) {
for (auto& x : str)
if (x == c)
return true;
return false;
}
bool Reader::try_token_as_float(const Token& tok, Object& obj) {
if (number_start(tok.text[0]) && str_contains(tok.text, '.')) {
try {
std::size_t end = 0;
double v = std::stod(tok.text, &end);
if (end != tok.text.size())
return false;
obj = Object::make_float(v);
return true;
} catch (std::exception& e) {
return false;
}
}
return false;
}
bool Reader::try_token_as_binary(const Token& tok, Object& obj) {
if (tok.text.size() >= 3 && tok.text[0] == '#' && tok.text[1] == 'b') {
uint64_t value = 0;
if (tok.text.size() > 64 + 2)
return false;
for (uint32_t i = 2; i < tok.text.size(); i++) {
value <<= 1u;
if (tok.text[i] == '1')
value++;
else if (tok.text[i] != '0')
return false;
}
obj = Object::make_integer((int64_t)value);
return true;
}
return false;
}
bool Reader::try_token_as_hex(const Token& tok, Object& obj) {
if (tok.text.size() >= 3 && tok.text[0] == '#' && tok.text[1] == 'x') {
uint64_t v = 0;
try {
std::size_t end = 0;
v = std::stoll(tok.text.substr(2), &end, 16);
if (end + 2 != tok.text.size())
return false;
obj = Object::make_integer(v);
return true;
} catch (std::exception& e) {
return false;
}
}
return false;
}
bool Reader::try_token_as_integer(const Token& tok, Object& obj) {
if (number_start(tok.text[0]) && !str_contains(tok.text, '.')) {
uint64_t v = 0;
try {
std::size_t end = 0;
v = std::stoll(tok.text, &end);
if (end != tok.text.size())
return false;
obj = Object::make_integer(v);
return true;
} catch (std::exception& e) {
return false;
}
}
return false;
}
bool Reader::try_token_as_boolean(const Token& tok, Object& obj) {
if (tok.text.size() != 2)
return false;
if (tok.text[0] == '#') {
if (tok.text[1] == 'f') {
obj = SymbolObject::make_new(symbolTable, "#f");
return true;
} else if (tok.text[1] == 't') {
obj = SymbolObject::make_new(symbolTable, "#t");
return true;
}
}
return false;
}
void Reader::display_error_info(TextStream& here, const std::string& err) {
printf("Reader error:\n%s\nat %s", err.c_str(), db.get_info_for(here.text, here.seek).c_str());
throw std::runtime_error(err);
}
std::string Reader::get_next_dir() {
auto result = std::getenv("NEXT_DIR");
if (!result)
throw std::runtime_error(
"Environment variable NEXT_DIR is not set. Please set this to point to next/");
return {result};
}
+77
View File
@@ -0,0 +1,77 @@
#ifndef COMPILER_READER_H
#define COMPILER_READER_H
#include <memory>
#include <cassert>
#include "goos/Object.h"
#include "reader/TextDb.h"
struct TextStream {
explicit TextStream(std::shared_ptr<ITextFragment> ptr) { text = ptr; }
std::shared_ptr<ITextFragment> text;
int seek = 0;
int line_count = 0;
char peek() {
assert(seek < text->get_size());
return text->get_text()[seek];
}
char peek(int i) {
assert(seek + i < text->get_size());
return text->get_text()[seek + i];
}
char read() {
assert(seek < text->get_size());
char c = text->get_text()[seek++];
if (c == '\n')
line_count++;
return c;
}
bool text_remains() { return seek < text->get_size(); }
bool text_remains(int i) { return seek + i < text->get_size(); }
};
struct Token {
std::shared_ptr<ITextFragment> source_text;
int source_offset;
int source_line;
std::string text;
};
class Reader {
public:
Reader();
Object read_from_string(const std::string& str);
Object read_from_stdin(const std::string& prompt_name);
Object read_from_file(const std::string& filename);
std::string get_next_dir();
SymbolTable symbolTable;
TextDb db;
private:
Object internal_read(std::shared_ptr<ITextFragment> text);
Object read_list(TextStream& stream, bool expect_close_paren = true);
void seek_past_whitespace_and_comments(TextStream& stream);
void display_error_info(TextStream& here, const std::string& err);
Token get_next_token(TextStream& stream);
bool try_token_as_symbol(const Token& tok, Object& obj);
bool try_token_as_float(const Token& tok, Object& obj);
bool try_token_as_binary(const Token& tok, Object& obj);
bool try_token_as_hex(const Token& tok, Object& obj);
bool try_token_as_integer(const Token& tok, Object& obj);
bool try_token_as_boolean(const Token& tok, Object& obj);
bool read_string(TextStream& stream, Object& obj);
char valid_symbols_chars[256];
};
#endif // COMPILER_READER_H
+75
View File
@@ -0,0 +1,75 @@
#include "goos/Object.h"
#include "TextDb.h"
void TextDb::insert(std::shared_ptr<ITextFragment> frag) {
fragments.push_back(frag);
}
void TextDb::link(Object o, std::shared_ptr<ITextFragment> frag, int offset) {
if (o.type == EMPTY_LIST)
return;
assert(o.type == PAIR);
TextRef ref;
ref.offset = offset;
ref.frag = frag;
map[o.alloc] = ref;
}
std::string TextDb::get_info_for(Object o) {
if (o.type == PAIR) {
auto kv = map.find(o.alloc);
if (kv != map.end()) {
// todo, get actual line stuff.
return get_info_for(kv->second.frag, kv->second.offset);
} else {
// return "object from untracked source:\n" + o.inspect() + "\n";
return "?";
}
} else {
return "?";
// return "object from untracked source:\n" + o.inspect() + "\n";
}
}
std::string TextDb::get_info_for(std::shared_ptr<ITextFragment> frag, int offset) {
std::string result = "text from " + frag->get_description() +
", line: " + std::to_string(frag->get_line_idx(offset) + 1) + "\n";
result += frag->get_line_containing_offset(offset) + "\n";
return result;
}
ITextFragment::ITextFragment(const std::string& r) : text(r) {
build_offsets();
}
void ITextFragment::build_offsets() {
offset_by_line.push_back(0);
for (uint32_t i = 0; i < text.size(); i++) {
if (text[i] == '\n') {
offset_by_line.push_back(i);
}
}
}
std::pair<int, int> ITextFragment::get_containing_line(int offset) {
for (uint32_t line = 0; line < offset_by_line.size() - 1; line++) {
if (offset >= offset_by_line[line] && offset <= offset_by_line[line + 1]) {
return std::make_pair(offset_by_line[line], offset_by_line[line + 1]);
}
}
return std::make_pair(0, text.size());
}
std::string ITextFragment::get_line_containing_offset(int offset) {
auto range = get_containing_line(offset);
return text.substr(range.first, range.second - range.first);
}
int ITextFragment::get_line_idx(int offset) {
for (uint32_t line = 0; line < offset_by_line.size() - 1; line++) {
if (offset >= offset_by_line[line] && offset <= offset_by_line[line + 1]) {
return line;
}
}
return -1;
}
+90
View File
@@ -0,0 +1,90 @@
#ifndef COMPILER_TEXTDB_H
#define COMPILER_TEXTDB_H
#include <string>
#include <vector>
#include <memory>
#include <unordered_map>
#include <fstream>
#include "goos/Object.h"
class ITextFragment {
public:
ITextFragment(const std::string& r);
ITextFragment() = default;
virtual const char* get_text() = 0;
virtual int get_size() = 0;
virtual std::string get_description() = 0;
virtual ~ITextFragment(){};
std::string get_line_containing_offset(int offset);
int get_line_idx(int offset);
protected:
std::string text;
std::vector<int> offset_by_line;
void build_offsets();
std::pair<int, int> get_containing_line(int offset);
};
class ReplText : public ITextFragment {
public:
ReplText(const std::string& text_) : ITextFragment(text_) {}
const char* get_text() { return text.c_str(); }
int get_size() { return text.size(); }
std::string get_description() { return "REPL"; }
~ReplText() = default;
};
class ProgramString : public ITextFragment {
public:
ProgramString(const std::string& text_) : ITextFragment(text_) {}
const char* get_text() { return text.c_str(); }
int get_size() { return text.size(); }
std::string get_description() { return "Program string"; }
~ProgramString() = default;
};
class FileText : public ITextFragment {
public:
FileText(const std::string& filename_) : filename(filename_) {
std::ifstream file(filename);
if (file.fail()) {
printf("Unable to open file %s\n", filename.c_str());
throw std::runtime_error("File can't be opened\n");
}
file.seekg(0, std::ios::end);
text.reserve((unsigned long)file.tellg());
file.seekg(0, std::ios::beg);
text.assign((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
build_offsets();
}
const char* get_text() { return text.c_str(); }
int get_size() { return text.size(); }
std::string get_description() { return filename; }
~FileText() = default;
private:
std::string filename;
};
struct TextRef {
int offset;
std::shared_ptr<ITextFragment> frag;
};
class TextDb {
public:
void insert(std::shared_ptr<ITextFragment> frag);
void link(Object o, std::shared_ptr<ITextFragment> frag, int offset);
std::string get_info_for(Object o);
std::string get_info_for(std::shared_ptr<ITextFragment> frag, int offset);
private:
std::vector<std::shared_ptr<ITextFragment>> fragments;
std::unordered_map<std::shared_ptr<AllocObject>, TextRef> map;
};
#endif // COMPILER_TEXTDB_H
+1
View File
@@ -0,0 +1 @@
add_library(regalloc SHARED RegAllocInstr.cpp RegAllocProgram.cpp)
@@ -0,0 +1 @@
#include "RegAllocInstr.h"
+92
View File
@@ -0,0 +1,92 @@
#ifndef JAK_REGALLOCINSTR_H
#define JAK_REGALLOCINSTR_H
#include <string>
#include <vector>
#include <utility>
//#include "goal/GoalEnv.h"
#include "codegen/ColoringAssignment.h"
struct RegAllocInstr {
// std::vector<ColoringInput> clobber;
// order of ops:
// read, clobber, write,
std::vector<ColoringAssignment> clobber;
std::vector<ColoringAssignment> exclusive;
std::vector<ColoringInput> write;
std::vector<ColoringInput> read;
std::vector<int> jumps;
bool fallthrough = true;
bool is_move = false;
std::string print() {
std::string result = "(";
bool first = true;
if (!write.empty()) {
first = false;
result += "(write";
for (auto& i : write) {
result += " " + i.print();
}
result += ")";
}
if (!read.empty()) {
if (!first) {
result += " ";
}
first = false;
result += "(read";
for (auto& i : read) {
result += " " + i.print();
}
result += ")";
}
if (!clobber.empty()) {
if (!first) {
result += " ";
}
first = false;
result += "(clobber";
for (auto& i : clobber) {
result += " " + i.print();
}
result += ")";
}
if (!jumps.empty()) {
if (!first) {
result += " ";
}
first = false;
result += "(jumps";
for (auto& i : jumps) {
result += " " + std::to_string(i);
}
result += ")";
}
result += ")";
return result;
}
bool reads(int id) {
for (const auto& x : read) {
if (x.id == id)
return true;
}
return false;
}
bool writes(int id) {
for (const auto& x : write) {
if (x.id == id)
return true;
}
return false;
}
};
#endif // JAK_REGALLOCINSTR_H
@@ -0,0 +1,750 @@
#include <algorithm>
#include <cassert>
#include "RegAllocProgram.h"
#include "logger/Logger.h"
#include "codegen/x86.h"
//#define LOG(...) gLogger.log(MSG_WARN, __VA_ARGS__)
#define LOG(...) \
do { \
} while (0)
void RegAllocProgram::find_basic_blocks() {
std::vector<int> dividers;
dividers.push_back(0);
dividers.push_back(instructions.size());
// loop over instructions, finding jump targets
for (uint32_t i = 0; i < instructions.size(); i++) {
auto& instr = instructions[i];
if (!instr.jumps.empty()) {
dividers.push_back(i + 1);
for (auto dest : instr.jumps) {
dividers.push_back(dest);
}
}
}
// sort dividers, and make blocks
std::sort(dividers.begin(), dividers.end(), [](int a, int b) { return a < b; });
for (uint32_t i = 0; i < dividers.size() - 1; i++) {
if (dividers[i] != dividers[i + 1]) {
// new basic block!
RegAllocBasicBlock block;
for (int j = dividers[i]; j < dividers[i + 1]; j++) {
block.instr_idx.push_back(j);
}
block.idx = basic_blocks.size();
basic_blocks.push_back(block);
}
}
if (!basic_blocks.empty()) {
basic_blocks.front().is_entry = true;
basic_blocks.back().is_exit = true;
}
auto find_basic_block_to_target = [&](int instr) {
bool found = false;
uint32_t result = -1;
for (uint32_t i = 0; i < basic_blocks.size(); i++) {
if (!basic_blocks[i].instr_idx.empty() && basic_blocks[i].instr_idx.front() == instr) {
assert(!found);
found = true;
result = i;
}
}
if (!found) {
printf("couldn't find baisc block beginning with instr %d of %ld\n", instr,
instructions.size());
}
assert(found);
return result;
};
// link blocks
for (auto& block : basic_blocks) {
assert(!block.instr_idx.empty());
auto& last_instr = instructions.at(block.instr_idx.back());
if (last_instr.fallthrough) {
// try to link to next block:
int next_idx = block.idx + 1;
if (next_idx < (int)basic_blocks.size()) {
basic_blocks.at(next_idx).pred.push_back(block.idx);
block.succ.push_back(next_idx);
}
}
for (auto target : last_instr.jumps) {
basic_blocks.at(find_basic_block_to_target(target)).pred.push_back(block.idx);
block.succ.push_back(find_basic_block_to_target(target));
}
}
}
void RegAllocProgram::analyze_block_liveliness(int n_vars) {
max_var = n_vars;
was_colored.resize(n_vars, false);
coloring_input.resize(n_vars);
for (auto& instr : instructions) {
for (auto& wr : instr.write) {
coloring_input.at(wr.id) = wr;
}
for (auto& rd : instr.read) {
coloring_input.at(rd.id) = rd;
}
}
// phase 1
for (auto& block : basic_blocks) {
block.live.resize(block.instr_idx.size());
block.dead.resize(block.instr_idx.size());
block.analyze_liveliness_phase1(instructions);
}
// phase 2
bool changed = false;
do {
changed = false;
for (auto& block : basic_blocks) {
if (block.analyze_liveliness_phase2(basic_blocks, instructions)) {
changed = true;
}
}
} while (changed);
// phase 3
for (auto& block : basic_blocks) {
block.analyze_liveliness_phase3(basic_blocks, instructions);
}
// phase 4
compute_live_ranges();
}
template <typename T>
bool in_set(std::set<T>& set, const T& obj) {
return set.find(obj) != set.end();
}
template <typename T>
bool in_vec(const std::vector<T>& vec, const T& obj) {
for (const auto& x : vec) {
if (x == obj)
return true;
}
return false;
}
template <typename T>
void print_set(std::set<T>& set) {
for (auto x : set) {
LOG("%s ", std::to_string(x).c_str());
}
}
void RegAllocBasicBlock::analyze_liveliness_phase1(std::vector<RegAllocInstr>& instructions) {
for (int i = instr_idx.size(); i-- > 0;) {
auto ii = instr_idx.at(i);
auto& instr = instructions.at(ii);
auto& lv = live.at(i);
auto& dd = dead.at(i);
// make all read live out
lv.clear();
for (auto& x : instr.read) {
lv.insert(x.id);
}
// kill things which are overwritten
dd.clear();
for (auto& x : instr.write) {
if (!in_set(lv, x.id)) {
dd.insert(x.id);
}
}
// b.use = i.liveout
std::set<int> use_old = use;
use.clear();
for (auto& x : lv) {
use.insert(x);
}
// | (bu.use & !i.dead)
for (auto& x : use_old) {
if (!in_set(dd, x)) {
use.insert(x);
}
}
// b.defs = i.dead
std::set<int> defs_old = defs;
defs.clear();
for (auto& x : dd) {
defs.insert(x);
}
// | b.defs & !i.lv
for (auto& x : defs_old) {
if (!in_set(lv, x)) {
defs.insert(x);
}
}
}
}
bool RegAllocBasicBlock::analyze_liveliness_phase2(std::vector<RegAllocBasicBlock>& blocks,
std::vector<RegAllocInstr>& instructions) {
(void)instructions;
bool changed = false;
auto out = defs;
for (auto s : succ) {
for (auto in : blocks.at(s).input) {
out.insert(in);
}
}
std::set<int> in = use;
for (auto x : out) {
if (!in_set(defs, x)) {
in.insert(x);
}
}
if (in != input || out != output) {
changed = true;
input = in;
output = out;
}
return changed;
}
void RegAllocBasicBlock::analyze_liveliness_phase3(std::vector<RegAllocBasicBlock>& blocks,
std::vector<RegAllocInstr>& instructions) {
(void)instructions;
std::set<int> live_local;
for (auto s : succ) {
for (auto i : blocks.at(s).input) {
live_local.insert(i);
}
}
for (int i = instr_idx.size(); i-- > 0;) {
auto& lv = live.at(i);
auto& dd = dead.at(i);
std::set<int> new_live = lv;
for (auto x : live_local) {
if (!in_set(dd, x)) {
new_live.insert(x);
}
}
lv = live_local;
live_local = new_live;
}
}
void RegAllocProgram::compute_live_ranges() {
// then resize live ranges to the correct size
live_ranges.resize(max_var, LiveRange(instructions.size(), 0));
// now compute the ranges
for (auto& block : basic_blocks) {
// from var use
for (auto instr_id : block.instr_idx) {
auto& inst = instructions.at(instr_id);
for (auto& lst : {inst.read, inst.write}) {
for (auto& x : lst) {
live_ranges.at(x.id).add_live_instruction(instr_id);
}
}
}
// and liveliness analysis
assert(block.live.size() == block.instr_idx.size());
for (uint32_t i = 0; i < block.live.size(); i++) {
for (auto& x : block.live[i]) {
live_ranges.at(x).add_live_instruction(block.instr_idx.at(i));
}
}
}
for (auto& con : constraints) {
live_ranges.at(con.var_id).add_live_instruction(con.instr_id);
}
}
void RegAllocProgram::do_constrained_allocations() {
for (auto& constr : constraints) {
auto var_id = constr.var_id;
LOG("DO CONSTRAINED ALLOC VAR %d ASS %s\n", constr.var_id, constr.ass.print().c_str());
LOG(" var %d, instr %d\n", var_id, constr.instr_id);
live_ranges.at(var_id).constrain_at_one(constr.instr_id, constr.ass);
}
}
void RegAllocProgram::check_constrained_allocations() {
for (auto& constr : constraints) {
if (!live_ranges.at(constr.var_id).conflicts_at(constr.instr_id, constr.ass)) {
LOG("[ERROR] There are multiple conflicting coloring restraints on variable %d\n",
constr.var_id);
coloring_error = true;
}
}
for (uint32_t i = 0; i < instructions.size(); i++) {
for (auto& lr1 : live_ranges) {
if (!lr1.seen || !lr1.is_live_at_instr(i))
continue;
for (auto& lr2 : live_ranges) {
if (!lr2.seen || !lr2.is_live_at_instr(i) || (&lr1 == &lr2))
continue;
// if lr1 is assigned...
auto& ass1 = lr1.get(i);
if (ass1.kind != UNASSIGNED) {
auto& ass2 = lr2.get(i);
if (ass1.occupies_same_reg(ass2)) {
LOG("[ERROR] There is an impossible constraint at instruction %d between var %d and "
"%d\n",
i, lr1.var, lr2.var);
coloring_error = true;
}
}
}
}
}
}
void RegAllocProgram::allocate() {
// here we allocate
std::vector<int> allocation_order;
for (uint32_t i = 0; i < live_ranges.size(); i++) {
if (live_ranges.at(i).seen && live_ranges.at(i).has_constraint) {
allocation_order.push_back(i);
}
}
for (uint32_t i = 0; i < live_ranges.size(); i++) {
if (live_ranges.at(i).seen && !live_ranges.at(i).has_constraint) {
allocation_order.push_back(i);
}
}
for (int var : allocation_order) {
do_allocation_for_var(var);
}
}
//// todo consider adding r13
// std::vector<int> RegAllocProgram::get_default_reg_alloc_order() {
// return {RAX, RCX, RDX, RSI, RDI, R8, R9, R10, R11, R12, RBX};
//}
std::vector<int> RegAllocProgram::get_default_alloc_order_for_var_spill(int v) {
auto& info = coloring_input.at(v);
assert(info.kind != UNASSIGNED_REG);
if (info.kind == REG_GPR) {
return {RAX, RCX, RDX, RSI, RDI, R8, R9, R10, R11, /*R12,*/ RBX};
} else if (info.kind == REG_XMM_FLOAT) {
// return {XMM0, XMM1, XMM2};
return {XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7,
XMM8, XMM9, XMM10, XMM11, XMM12, XMM13, XMM14, XMM15};
} else {
throw std::runtime_error("unknown reg kind in get_default_alloc_order_for_var");
}
}
std::vector<int> RegAllocProgram::get_default_alloc_order_for_var(int v) {
auto& info = coloring_input.at(v);
assert(info.kind != UNASSIGNED_REG);
if (info.kind == REG_GPR) {
return {RAX, RCX, RDX, RSI, RDI, R8, R9, R10, /*R11,*/ RBX};
} else if (info.kind == REG_XMM_FLOAT) {
// return {XMM0, XMM1, XMM2};
return {XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7,
XMM8, XMM9, XMM10, XMM11, XMM12, XMM13, XMM14};
} else {
throw std::runtime_error("unknown reg kind in get_default_alloc_order_for_var");
}
}
void RegAllocProgram::do_allocation_for_var(int var) {
// first, let's see if there's a hint...
auto& lr = live_ranges.at(var);
bool colored = false;
if (lr.best_hint.is_assigned()) {
colored = try_assignment_for_var(var, lr.best_hint);
LOG("var %d reg %s ? %d\n", var, lr.best_hint.print().c_str(), colored);
}
auto reg_order = get_default_alloc_order_for_var(var);
// todo, try other regs..
if (!colored && move_eliminator) {
auto& first_instr = instructions.at(lr.min);
auto& last_instr = instructions.at(lr.max);
if (first_instr.is_move) {
auto& possible_coloring = live_ranges.at(first_instr.read.front().id).get(lr.min);
if (possible_coloring.is_assigned() && in_vec(reg_order, possible_coloring.reg_id)) {
colored = try_assignment_for_var(var, possible_coloring);
}
}
if (!colored && last_instr.is_move) {
auto& possible_coloring = live_ranges.at(last_instr.write.front().id).get(lr.max);
if (possible_coloring.is_assigned() && in_vec(reg_order, possible_coloring.reg_id)) {
colored = try_assignment_for_var(var, possible_coloring);
}
}
}
// auto reg_order = get_default_reg_alloc_order();
for (auto reg : reg_order) {
if (colored)
break;
ColoringAssignment ass;
ass.kind = REGISTER;
ass.reg_id = reg;
colored = try_assignment_for_var(var, ass);
LOG("var %d reg %s ? %d\n", var, ass.print().c_str(), colored);
}
if (!colored) {
colored = try_spill_coloring(var);
if (colored)
used_stack = true;
}
// todo, try spilling
if (!colored) {
LOG("[ERROR] var %d could not be colored:\n%s\n", var, live_ranges.at(var).print().c_str());
coloring_error = true;
} else {
LOG("Colored var %d\n", var);
was_colored.at(var) = true;
}
}
int RegAllocProgram::get_stack_slot_for_var(int var) {
auto kv = var_to_stack_slot.find(var);
if (kv == var_to_stack_slot.end()) {
auto slot = current_stack_slot++;
var_to_stack_slot[var] = slot;
return slot;
} else {
return kv->second;
}
}
bool RegAllocProgram::try_spill_coloring(int var) {
LOG("---- SPILL VAR %d ----\n", var);
auto& lr = live_ranges.at(var);
// possibly get a hint assignment
ColoringAssignment hint_assignment;
hint_assignment.kind = UNASSIGNED;
// loop over live range
for (int instr = lr.min; instr <= lr.max; instr++) {
// bonus_instructions.at(instr).clear();
BonusOp bonus;
// we may have a constaint in here
auto& current_assignment = lr.assignment.at(instr - lr.min);
auto& op = instructions.at(instr);
bool is_read = op.reads(var);
bool is_written = op.writes(var);
// we have a constraint!
if (current_assignment.is_assigned()) {
LOG(" [%02d] already assigned %s\n", instr, current_assignment.print().c_str());
// remember this assignment as a hint for later
hint_assignment = current_assignment;
// check that this assignment is ok
if (!assignment_ok_at(var, instr, current_assignment)) {
// this shouldn't be possible with feasible constraints
printf("-- SPILL FAILED -- IMPOSSIBLE CONSTRAINT @ %d %s\n", instr,
current_assignment.print().c_str());
assert(false);
return false;
}
// flag it as spilled, but currently in a GPR.
current_assignment.spilled = true;
bonus.ass = current_assignment;
} else {
// not assigned.
LOG(" [%02d] nya rd? %d wr? %d\n", instr, is_read, is_written);
// We'd like to keep it on the stack if possible
ColoringAssignment spill_assignment;
spill_assignment.spilled = true;
spill_assignment.kind = STACK;
spill_assignment.reg_id = -1; // for now
// needs a temp register
if (is_read || is_written) {
// we need to put it in a register here!
// first check if the hint works?
// todo floats?
if (hint_assignment.kind == AssignmentKind::REGISTER) {
LOG(" try hint %s\n", hint_assignment.print().c_str());
if (assignment_ok_at(var, instr, hint_assignment)) {
// it's ok!
LOG(" it worked!\n");
spill_assignment.reg_id = hint_assignment.reg_id;
}
}
// hint didn't work
// auto reg_order = get_default_reg_alloc_order();
auto reg_order = get_default_alloc_order_for_var_spill(var);
if (spill_assignment.reg_id == -1) {
for (auto reg : reg_order) {
ColoringAssignment ass;
ass.kind = REGISTER;
ass.reg_id = reg;
LOG(" try %s\n", ass.print().c_str());
if (assignment_ok_at(var, instr, ass)) {
LOG(" it worked!\n");
spill_assignment.reg_id = ass.reg_id;
break;
}
}
}
if (spill_assignment.reg_id == -1) {
LOG("SPILLING FAILED BECAUSE WE COULDN'T FIND A TEMP REGISTER!\n");
assert(false);
// std::vector<bool> can_try_spilling;
// for(uint32_t other_spill = 0; other_spill < was_colored.size(); other_spill++)
// {
// if((int)other_spill != var && was_colored.at(other_spill)) {
// LOG("TRY SPILL %d?\n", other_spill);
// if(try_spill_coloring(other_spill)) {
// LOG("SPILL OK.\n");
// if(try_spill_coloring(var)) {
// return true;
// }
// } else {
// LOG("SPILL %d failed.\n", other_spill);
// }
// }
// }
return false;
}
// mark that it's in a GPR!
spill_assignment.kind = REGISTER;
} // end need temp reg
spill_assignment.stack_slot = get_stack_slot_for_var(var);
lr.assignment.at(instr - lr.min) = spill_assignment;
bonus.ass = spill_assignment;
} // end not constrained
bonus.stack_slot = get_stack_slot_for_var(var);
bonus.load_from_stack = is_read;
bonus.store_into_stack = is_written;
bonus_instructions.at(instr).ops.push_back(bonus);
}
return true;
}
bool RegAllocProgram::try_assignment_for_var(int var, ColoringAssignment ass) {
if (can_var_be_assigned(var, ass)) {
assign_var_no_check(var, ass);
return true;
}
return false;
}
bool RegAllocProgram::assignment_ok_at(int var, int idx, ColoringAssignment ass) {
auto& lr = live_ranges.at(var);
for (auto& other_lr : live_ranges) {
if (other_lr.var == var /*|| !other_lr.seen*/)
continue;
if (other_lr.is_live_at_instr(idx)) {
if (/*(idx != other_lr.max) &&*/ other_lr.conflicts_at(idx, ass)) {
bool allowed_by_move_eliminator = false;
if (move_eliminator) {
if (enable_fancy_coloring) {
if (lr.dies_next_at_instr(idx) && other_lr.becomes_live_at_instr(idx) &&
instructions.at(idx).is_move) {
allowed_by_move_eliminator = true;
}
if (lr.becomes_live_at_instr(idx) && other_lr.dies_next_at_instr(idx) &&
instructions.at(idx).is_move) {
allowed_by_move_eliminator = true;
}
} else {
// case to allow rename (from us to them)
if (idx == lr.max && idx == other_lr.min && instructions.at(idx).is_move) {
allowed_by_move_eliminator = true;
}
if (idx == lr.min && idx == other_lr.min && instructions.at(idx).is_move) {
allowed_by_move_eliminator = true;
}
}
}
if (!allowed_by_move_eliminator) {
LOG("at idx %d, %s conflicts\n", idx, other_lr.print().c_str());
return false;
}
}
}
}
// check we aren't violating a clobber
if (idx != lr.min && idx != lr.max) {
for (auto clobber : instructions.at(idx).clobber) {
if (clobber.occupies_same_reg(ass)) {
LOG("at idx %d clobber\n", idx);
return false;
}
}
}
for (auto exclusive : instructions.at(idx).exclusive) {
if (exclusive.occupies_same_reg(ass)) {
LOG("at idx %d exclusive conflict\n", idx);
return false;
}
}
// check we aren't violating ourselves
if (lr.assignment.at(idx - lr.min).is_assigned()) {
if (!(ass.occupies_same_reg(lr.assignment.at(idx - lr.min)))) {
LOG("at idx %d self bad\n", idx);
return false;
}
}
return true;
}
bool RegAllocProgram::can_var_be_assigned(int var, ColoringAssignment ass) {
// our live range:
auto& lr = live_ranges.at(var);
// check against all other live ranges:
for (auto& other_lr : live_ranges) {
if (other_lr.var == var /*|| !other_lr.seen*/)
continue; // but not us!
for (int instr = lr.min; instr <= lr.max; instr++) {
if (other_lr.is_live_at_instr(instr)) {
// LR's overlap
if (/*(instr != other_lr.max) && */ other_lr.conflicts_at(instr, ass)) {
bool allowed_by_move_eliminator = false;
if (move_eliminator) {
if (enable_fancy_coloring) {
if (lr.dies_next_at_instr(instr) && other_lr.becomes_live_at_instr(instr) &&
instructions.at(instr).is_move) {
allowed_by_move_eliminator = true;
}
if (lr.becomes_live_at_instr(instr) && other_lr.dies_next_at_instr(instr) &&
instructions.at(instr).is_move) {
allowed_by_move_eliminator = true;
}
} else {
// case to allow rename (from us to them)
if (instr == lr.max && instr == other_lr.min && instructions.at(instr).is_move) {
allowed_by_move_eliminator = true;
}
if (instr == lr.min && instr == other_lr.min && instructions.at(instr).is_move) {
allowed_by_move_eliminator = true;
}
}
}
if (!allowed_by_move_eliminator) {
LOG("at idx %d, %s conflicts\n", instr, other_lr.print().c_str());
return false;
}
}
}
}
}
// can clobber on the last one or first one - check that we don't interfere with a clobber
for (int instr = lr.min + 1; instr <= lr.max - 1; instr++) {
for (auto clobber : instructions.at(instr).clobber) {
if (clobber.occupies_same_reg(ass)) {
LOG("at idx %d clobber\n", instr);
return false;
}
}
}
for (int instr = lr.min; instr <= lr.max; instr++) {
for (auto exclusive : instructions.at(instr).exclusive) {
if (exclusive.occupies_same_reg(ass)) {
LOG("at idx %d exclusive conflict\n", instr);
return false;
}
}
}
// check we don't violate any others.
for (int instr = lr.min; instr <= lr.max; instr++) {
if (lr.has_constraint && lr.assignment.at(instr - lr.min).is_assigned()) {
if (!(ass.occupies_same_reg(lr.assignment.at(instr - lr.min)))) {
LOG("at idx %d self bad\n", instr);
return false;
}
}
}
return true;
}
void RegAllocProgram::assign_var_no_check(int var, ColoringAssignment ass) {
live_ranges.at(var).assign_no_overwrite(ass);
}
std::pair<int, int> RegAllocProgram::get_move_stats() {
int total_moves = 0;
int eliminated_moves = 0;
for (size_t i = 0; i < instructions.size(); i++) {
auto& instr = instructions[i];
if (instr.is_move) {
total_moves++;
auto dst = live_ranges.at(instr.write.front().id).get(i);
auto src = live_ranges.at(instr.read.front().id).get(i);
if (dst.occupies_same_reg(src)) {
eliminated_moves++;
}
}
}
return std::make_pair(eliminated_moves, total_moves);
}
int RegAllocProgram::get_spill_count() {
int count = 0;
for (auto& x : bonus_instructions) {
for (auto& y : x.ops) {
if (y.load_from_stack || y.store_into_stack) {
count++;
}
}
}
return count;
}
+154
View File
@@ -0,0 +1,154 @@
#ifndef JAK_REGALLOCPROGRAM_H
#define JAK_REGALLOCPROGRAM_H
#include <unordered_map>
#include <vector>
#include <string>
#include <set>
#include <cassert>
#include "regalloc/RegAllocInstr.h"
struct RegAllocBasicBlock {
std::vector<int> instr_idx, succ, pred;
std::vector<std::set<int>> live, dead;
std::set<int> use, defs;
std::set<int> input, output;
bool is_entry = false;
bool is_exit = false;
int idx;
std::string print_summary() {
std::string result = "block " + std::to_string(idx) + "\nsucc: ";
for (auto s : succ)
result += std::to_string(s) + " ";
result += "\npred: ";
for (auto p : pred)
result += std::to_string(p) + " ";
result += "\nuse: ";
for (auto x : use)
result += std::to_string(x) + " ";
result += "\ndef: ";
for (auto x : defs)
result += std::to_string(x) + " ";
result += "\ninput: ";
for (auto x : input)
result += std::to_string(x) + " ";
result += "\noutput: ";
for (auto x : output)
result += std::to_string(x) + " ";
return result;
}
std::string print_detailed(std::vector<RegAllocInstr>& insts) {
std::string result = print_summary() + "\n";
int k = 0;
for (auto instr : instr_idx) {
std::string line = insts.at(instr).print();
constexpr int pad_len = 30;
if (line.length() < pad_len) {
// line.insert(line.begin(), pad_len - line.length(), ' ');
line.append(pad_len - line.length(), ' ');
}
result += " " + line + " live: ";
for (auto j : live.at(k)) {
result += std::to_string(j) + " ";
}
result += "\n";
k++;
}
return result;
}
void analyze_liveliness_phase1(std::vector<RegAllocInstr>& instructions);
bool analyze_liveliness_phase2(std::vector<RegAllocBasicBlock>& blocks,
std::vector<RegAllocInstr>& instructions);
void analyze_liveliness_phase3(std::vector<RegAllocBasicBlock>& blocks,
std::vector<RegAllocInstr>& instructions);
};
class RegAllocProgram {
public:
RegAllocProgram() = default;
int add_instruction(RegAllocInstr& i) {
instructions.push_back(i);
return instructions.size() - 1;
}
void find_basic_blocks();
void analyze_block_liveliness(int n_vars);
void do_constrained_allocations();
void check_constrained_allocations();
void allocate();
void prepare_for_allocation(size_t code_size) {
for (uint32_t i = 0; i < live_ranges.size(); i++) {
live_ranges.at(i).prepare_for_allocation(i);
}
bonus_instructions.resize(code_size);
}
std::vector<int> get_default_alloc_order_for_var(int v);
std::vector<int> get_default_alloc_order_for_var_spill(int v);
std::vector<RegAllocInstr> instructions;
std::vector<RegAllocBasicBlock> basic_blocks;
std::vector<LiveRange> live_ranges;
std::vector<RegAllocBonusInstruction> bonus_instructions;
std::vector<bool> was_colored;
std::vector<ColoringInput> coloring_input;
std::string print_all_instrs() {
std::string result;
for (auto& instr : instructions) {
result += instr.print() + "\n";
}
return result;
}
std::string print_block_summary() {
std::string result;
for (auto& b : basic_blocks) {
result += b.print_summary() + "\n";
}
return result;
}
std::string print_block_detailed() {
std::string result;
for (auto& b : basic_blocks) {
result += b.print_detailed(instructions) + "\n";
}
return result;
}
int max_var = 0;
std::vector<RegConstraint> constraints;
bool coloring_error = false;
int get_stack_slot_count() { return current_stack_slot; }
std::pair<int, int> get_move_stats();
int get_spill_count();
bool used_stack = false;
private:
void compute_live_ranges();
void do_allocation_for_var(int var);
bool try_assignment_for_var(int var, ColoringAssignment ass);
bool can_var_be_assigned(int var, ColoringAssignment ass);
void assign_var_no_check(int var, ColoringAssignment ass);
bool try_spill_coloring(int var);
bool assignment_ok_at(int var, int idx, ColoringAssignment ass);
int get_stack_slot_for_var(int var);
int current_stack_slot = 0;
std::unordered_map<int, int> var_to_stack_slot;
};
#endif // JAK_REGALLOCPROGRAM_H
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
#ifndef JAK_V2_UTIL_H
#define JAK_V2_UTIL_H
#include <memory>
template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
template <typename T>
T align(T current, T alignment, T offset) {
while ((current % alignment) != 0) {
current++;
}
return current + offset;
}
#endif // JAK_V2_UTIL_H
+44
View File
@@ -0,0 +1,44 @@
;-*-Scheme-*-
;; This file is loaded as part of goal-lib.gc.
;; It should generate no code.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; COMPILER CONTROL
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TODO change me!!!
(defglobalconstant *compiler-output-path* "obj/")
;; a macro to compile the test file
(defmacro :t ()
`(asm-file "game/test.gc" :color)
)
;; a macro to compile and load the test file
(defmacro :tl ()
`(asm-file "game/test.gc" :color :load)
)
;; compile the gcommon code.
(defmacro :g ()
;`(asm-file "game/kernel/gcommon.gc" :color :write)
`(asm-file "game/kernel/gcommon.gc" :color)
)
;; compile and load the gcommon code.
(defmacro :gl ()
`(begin
(asm-file "old_compiler/goal_kernel/gcommon.gc" :color :load)
)
)
;; compile, color, and save a file
(defmacro m (file)
`(asm-file ,file :color :write)
)
;; compile, color, load and save a file
(defmacro ml (file)
`(asm-file ,file :color :load :write)
)
+7
View File
@@ -0,0 +1,7 @@
;-*-Scheme-*-
;; This file is loaded as part of goal-lib.gc.
;; It should generate no code.
(defglobalconstant M_PI 3.1415926589932)
(defglobalconstant *gtype-basic-offset* 4)
+96
View File
@@ -0,0 +1,96 @@
;-*-Scheme-*-
;; This file is loaded as part of goal-lib.gc.
;; It should generate no code.
;; This is used to extern define all C Kernel functions and types.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Forward declare C Kernel Fixed Syms
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; '()
;; booleans
(define-extern #f boolean)
(define-extern #t boolean)
;; types
(define-extern function type)
(define-extern symbol type)
(define-extern basic type)
(define-extern string type)
(define-extern type type)
(define-extern object type)
(define-extern link-block type)
(define-extern integer type)
(define-extern sinteger type)
(define-extern uinteger type)
(define-extern binteger type)
(define-extern int8 type)
(define-extern int16 type)
(define-extern int32 type)
(define-extern int64 type)
(define-extern int128 type)
(define-extern uint8 type)
(define-extern uint16 type)
(define-extern uint32 type)
(define-extern uint64 type)
(define-extern uint128 type)
(define-extern float type)
(define-extern process-tree type)
(define-extern process type)
(define-extern thread type)
(define-extern structure type)
(define-extern pair type)
(define-extern pointer type)
(define-extern number type)
(define-extern array type)
(define-extern vu-function type)
(define-extern connectable type)
(define-extern stack-frame type)
(define-extern file-stream type)
(define-extern kheap type)
;; functions
(defun-extern nothing () none)
;; del basic
(define-extern static symbol)
(define-extern global object)
;;(define-extern debug kheap)
(define-extern loading-level symbol)
(define-extern loading-package symbol)
(define-extern process-level-heap symbol)
(define-extern stack symbol)
(define-extern scratch symbol)
;;(define-extern *scratch-top* pointer)
(defun-extern zero-func () int32)
(defun-extern method-set! ((x type) (y integer) (z function)) object)
;; todo - change allocation to a kheap
(defun-extern dgo-load ((name string) (allocation object) (flag integer) (buffer-size integer)) none)
(defun-extern *listener-function* () object)
(define-extern *enable-method-set* int32)
;; asizeo-of-bsic
;; copy-basic
;; level
;; art group
;; tx page dir
;; tx page
;; sound
;; dgo
;; top level
(defun-extern string->symbol ((x string)) symbol)
(defun-extern print ((x object)) object)
(defun-extern inspect ((x object)) object)
(define-extern test-function function)
(define-extern _format function)
;; for the compiler
(define-extern format function)
;; TODO - some others...
(define-extern *kernel-boot-message* symbol)
(define-extern *debug-segment* boolean)
;; for use by the compiler.
(defun-extern malloc ((allocation symbol) (size integer)) pointer)
+100
View File
@@ -0,0 +1,100 @@
;-*-Scheme-*-
;; THE GOAL COMMON LIBRARY
;; before this is loaded into the compiler, GOOS needs to have loaded
;; goos-lib.gs. The goos-lib will insert some macros into GOAL's macro space
;; required for this to work.
;; WARNING - this file should generate NO CODE!
;; Any code which would be generated by this file is thrown out without warning!
;; Any "common" code should go in gcommon instead.
(asm-file "old_compiler/gc/goal-test-defs.gc")
(asm-file "old_compiler/gc/goal-target-control.gc")
(asm-file "old_compiler/gc/goal-compiler-control.gc")
(asm-file "old_compiler/gc/goal-syntax.gc")
(asm-file "old_compiler/gc/goal-test-utils.gc")
(asm-file "old_compiler/gc/goal-externs.gc")
(asm-file "old_compiler/gc/goal-constants.gc")
(asm-file "old_compiler/gc/goal-macros.gc")
;(asm-file "builder/gc/builder.gc")
;; Ideally this file only contains the above asm-file statements
;; but below is a good spot for temporary hacks:
;; HACKS!
;; temp hack, this gets you the wrong type, and doesn't do good typechecking on the inputs
; (defmacro &+ (v1 v2)
; `(the pointer (+ (the integer ,v1) (the integer ,v2)))
; )
(defmacro &+ (v1 &rest args)
(if (null? args)
`(the pointer ,v1)
`(&+ (+ (the integer ,v1) (the integer ,(first args))) ,@(cdr args))
)
)
(defmacro &- (v1 v2)
`(the pointer (- (the integer ,v1) (the integer ,v2)))
)
(defmacro &+! (v1 v2)
`(set! ,v1 (&+ ,v1 ,v2))
)
;; macro to print a float.
(defmacro pf (flt)
`(format #t "~f~%" ,flt)
)
(defmacro ct ()
;; compiler test
`(begin
(build-game)
(set! fancy-listener-print #f)
(test)
(set! fancy-listener-print #t)
)
)
(defmacro tt ()
`(begin
(lt)
(asm-file "game/test.gc" :color :load)
)
)
(defmacro tn ()
`(begin
(asm-file "game/test.gc" :color)
)
)
(defmacro lm ()
`(begin
(build-game)
(asm-file "game/engine/math/math.gc" :color :load)
(set! fancy-listener-print #t)
)
)
(defmacro lg ()
`(begin
(dgo-load "game" global #xf #x200000)
))
(defmacro e ()
`(:exit)
)
;(test)
;; uncomment to run tests automatically on startup.
;; Useful for running the compiler in GDB where you can't easily type stuff.
;;:(:t)
;(build-game)
; (set-config! debug-print-obj #t)
+23
View File
@@ -0,0 +1,23 @@
;-*-Scheme-*-
;; This file is loaded as part of goal-lib.gc.
;; It should generate no code.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; BIT STUFF
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro align16 (value)
`(logand #xfffffff0 (+ (the integer ,value) 15))
)
(defmacro &-> (&rest args)
`(& (-> ,@args))
)
(defmacro new-with-method (alloc type &rest args)
`(the ,type ((-> ,type methods 0) ,alloc ,type ,@args))
)
(defmacro symbol? (basic-obj)
`(eq? (-> ,basic-obj type) symbol)
)
+355
View File
@@ -0,0 +1,355 @@
;-*-Scheme-*-
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; LEXICAL STUFF
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Bind vars in body
(defmacro let (bindings &rest body)
`((lambda :inline-only #t ,(apply first bindings) ,@body)
,@(apply second bindings)))
;; Let, but recursive, allowing you to define variables in terms of others.
(defmacro let* (bindings &rest body)
(if (null? bindings)
`(begin ,@body)
`((lambda :inline-only #t (,(caar bindings))
(let* ,(cdr bindings) ,@body))
,(car (cdar bindings))
)
)
)
;; Backup some values, and restore after executing body.
;; Non-dynamic (nonlocal jumps out of body will skip restore)
(defmacro protect (defs &rest body)
(if (null? defs)
;; nothing to backup, just insert body (base case)
`(begin ,@body)
;; a unique name for the thing we are backing up
(with-gensyms (backup)
;; store the original value of the first def in backup
`(let ((,backup ,(first defs)))
;; backup any other things which need backing up
(protect ,(cdr defs)
;; execute the body
,@body
)
;; restore the first thing
(set! ,(first defs) ,backup)
)
)
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; DEFINE STUFF
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Define a new function
(defmacro defun (name bindings &rest body)
(if (and
(> (length body) 1) ;; more than one thing in function
(string? (first body)) ;; first thing is a string
)
;; then it's a docstring and we ignore it.
`(define ,name (lambda :name ,name ,bindings ,@(cdr body)))
;; otherwise don't ignore it.
`(define ,name (lambda :name ,name ,bindings ,@body))
)
)
;; Define a new function, but only if we're debugging.
;; TODO - should place the function in the debug segment!
(defmacro defun-debug (name &rest args)
`(if *debug-segment*
(defun ,name ,@args) ;; debug data is loaded, define function in symbol table
(define ,name nothing) ;; function not loaded, set function to the nothing function.
)
)
;; By default, recursive functions don't work because the compiler doesn't
;; know the return type of a function until after the function is fully defined.
;; To get around this, this macro allows you to define a function + give a return type.
;; it simply forward declares the function with the given return, then defines the function as normal
;; if you got the return type wrong, the function definition conflicts with the forward dec
;; and throws an error.
(defmacro defun-recursive (name bindings return-type &rest body)
`(begin
(defun-extern ,name ,bindings ,return-type)
(define ,name (lambda :name ,name ,bindings
;; omit the doc-string if needed
,@(if (and (> (length body) 1) (string? (first body)))
(cdr body)
body
)
)
)
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; CONDITIONAL COMPILATION
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro #when (clause &rest body)
`(#cond (,clause ,@body))
)
(defmacro #unless (clause &rest body)
`(#cond ((not ,clause) ,@body))
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; MATH STUFF
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro +1 (var)
`(+ ,var 1)
)
(defmacro +! (place amount)
`(set! ,place (+ ,place ,amount))
)
(defmacro +1! (place)
`(set! ,place (+ 1 ,place))
)
(defmacro -! (place amount)
`(set! ,place (- ,place ,amount))
)
(defmacro *! (place amount)
`(set! ,place (* ,place ,amount))
)
(defmacro 1- (var)
`(- ,var 1)
)
(defmacro fabs (x)
`(if (> 0.0 ,x) (- ,x) ,x)
)
(defmacro fmin (a b)
`(if (> ,a ,b) ,b ,a)
)
(defmacro fmax (a b)
`(if (> ,a ,b) ,a ,b)
)
(defmacro true! (place)
`(set! ,place #t)
)
(defmacro false! (place)
`(set! ,place #f)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; CONTROL FLOW STUFF
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro if (condition true-case &rest others)
(if (null? others)
`(cond (,condition ,true-case))
`(cond (,condition ,true-case)
(else ,(first others))
)
)
)
(defmacro when (condition &rest body)
`(if ,condition
(begin ,@body)
)
)
(defmacro unless (condition &rest body)
`(if (not ,condition)
(begin ,@body)
)
)
; (defmacro while (test &rest body)
; (with-gensyms (reloop test-exit)
; `(begin
; (goto ,test-exit)
; (label ,reloop)
; ,@body
; (label ,test-exit)
; (when ,test
; (goto ,reloop)
; )
; )
; )
; )
(defmacro while (test &rest body)
(with-gensyms (reloop test-exit)
`(begin
(goto ,test-exit)
(label ,reloop)
,@body
(label ,test-exit)
(when-goto ,test ,reloop)
#f
)
)
)
(defmacro and (&rest args)
(with-gensyms (result end)
`(begin
(let ((,result (the object #f)))
,@(apply (lambda (x)
`(begin
(set! ,result ,x)
(if (eq? ,result #f)
(goto ,end)
)
)
)
args
)
(label ,end)
,result
)
)
)
)
(defmacro or (&rest args)
(with-gensyms (result end)
`(begin
(let ((,result (the object #f)))
,@(apply (lambda (x)
`(begin
(set! ,result ,x)
(if (not (eq? ,result #f))
(goto ,end)
)
)
)
args
)
(label ,end)
,result
)
)
)
)
(defmacro zero? (thing)
`(eq? ,thing 0)
)
(defmacro until (test &rest body)
(with-gensyms (reloop)
`(begin
(label ,reloop)
,@body
(when-goto (not ,test) ,reloop)
; (when (not ,test)
; (goto ,reloop)
; )
)
)
)
(defmacro dotimes (var &rest body)
`(let (( ,(first var) 0))
(while (< ,(first var) ,(second var))
,@body
(+1! ,(first var))
)
,@(cddr var)
)
)
(defmacro countdown (var &rest body)
`(let ((,(first var) ,(second var)))
(while (!= ,(first var) 0)
(set! ,(first var) (- ,(first var) 1))
,@body
)
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TYPE STUFF
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro basic? (obj)
;; todo, make this more efficient
`(= 4 (logand (the integer ,obj) #b111))
)
(defmacro pair? (obj)
;; todo, make this more efficient
`(= 2 (logand (the integer ,obj) #b111))
)
(defmacro binteger? (obj)
`(zero? (logand (the integer ,obj) #b111))
)
(defmacro rtype-of (obj)
`(cond ((binteger? ,obj) binteger)
((pair? ,obj) pair)
(else (-> (the basic ,obj) type))
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; PAIR STUFF
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro cons (a b)
`(new 'global 'pair ,a ,b)
)
(defmacro list (&rest args)
(if (null? args)
(quote '())
`(cons ,(car args) (list ,@(cdr args)))
)
)
(defmacro null? (arg)
;; todo, make this better
`(if (eq? ,arg '())
#t
#f
)
)
(defmacro caar (arg)
`(car (car ,arg))
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; METHOD STUFF
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro object-new (&rest sz)
(if (null? sz)
`(the ,(current-method-type) ((-> object methods 0) allocation type-to-make (-> type-to-make asize)))
`(the ,(current-method-type) ((-> object methods 0) allocation type-to-make ,@sz))
)
)
+30
View File
@@ -0,0 +1,30 @@
;-*-Scheme-*-
;; GOAL Macros for interfacing with the target.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TARGET CONTROL
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro lt (&rest args)
;; shortcut for listen-to-target. also sends a :status command to make sure
;; all buffers on the target are flushed.
`(begin
(listen-to-target ,@args)
(:status)
)
)
(defmacro :r (&rest args)
;; shortcut to completely reset the target and connect, regardless of current state
`(begin
;; connect, so we can send reset. if we're already connected, does nothing
(listen-to-target ,@args)
;; send a reset message, disconnecting us
(reset-target)
;; establish connection again
(listen-to-target ,@args)
;; flush buffers
(:status)
)
)
+134
View File
@@ -0,0 +1,134 @@
;; Definition of all GOAL tests
(defglobalconstant *goal-test-prefix* "old_compiler/gc/tests/")
(defglobalconstant *goal-test-files*
(
"test-return-integer.gc"
"test-return-integer1.gc"
"test-return-integer2.gc"
"test-return-integer3.gc"
"test-return-integer4.gc"
"test-return-integer5.gc"
"test-return-integer6.gc"
"test-return-negative-integer.gc"
"test-conditional-compilation.gc"
"test-conditional-compilation-2.gc"
"test-define-1.gc"
"test-nested-blocks-1.gc"
;; up to here has been hand-checked for good code.
"test-nested-blocks-2.gc"
"test-nested-blocks-3.gc"
"test-goto-1.gc"
"test-defglobalconstant-1.gc"
"test-defglobalconstant-2.gc"
"test-simple-function-call.gc"
"test-application-lambda-1.gc"
"test-let-1.gc"
"test-let-star-1.gc"
"test-string-constant-1.gc"
"test-string-constant-2.gc"
"test-defun-return-constant.gc"
"test-defun-return-symbol.gc"
"test-function-return-arg-1.gc"
"test-nested-function-call.gc"
"test-add-int-constants.gc"
"test-add-int-vars.gc"
"test-add-int-multiple.gc"
"test-add-int-multiple-2.gc"
"test-add-function-returns.gc"
"test-sub-1.gc"
"test-sub-2.gc"
"test-mul-1.gc"
"test-declare-inline.gc"
"test-inline-call.gc"
"test-with-inline.gc"
"test-three-reg-add.gc"
"test-three-reg-sub.gc"
"test-three-reg-mult.gc"
"test-mlet.gc"
"test-set-symbol.gc"
"test-defun-extern.gc"
"test-defsmacro-defgmacro.gc"
"test-desfun.gc"
"test-factorial-recursive.gc"
"test-factorial-iterative.gc"
"test-div-1.gc"
"test-div-2.gc"
"test-protect.gc"
"test-shiftvs.gc"
"test-ash.gc"
"test-negative-integer-symbol.gc"
"test-mod.gc"
"test-nested-function-call-2.gc"
"test-load-gcommon.gc"
"test-quote-symbol.gc"
"test-min-max.gc"
"test-format-1.gc"
"test-float-product.gc"
"test-float-in-symbol.gc"
"test-function-return-constant-float.gc"
"test-float-function.gc"
"test-float-pow-function.gc"
"test-bfloat-1.gc"
"test-align16-1.gc"
"test-align16-2.gc"
"test-basic-type-check.gc"
"test-return-from-f.gc"
"test-return-from-f-tricky-color.gc"
"test-signed-int-compare.gc"
"test-condition-boolean.gc"
"test-return-value-of-if.gc"
"test-inline-array-field.gc"
"test-access-inline-array.gc"
"test-find-parent-method.gc"
"test-empty-pair.gc"
"test-pairp.gc"
"test-cons.gc"
"test-list.gc"
"test-car-cdr-get.gc"
"test-car-cdr-set.gc"
"test-nested-car-cdr-set.gc"
"test-dotimes.gc"
"test-ref.gc"
"test-pair-asize.gc"
"test-last.gc"
"test-pair-length.gc"
"test-member-1.gc"
"test-member-2.gc"
"test-assoc-1.gc"
"test-assoc-2.gc"
"test-assoce-1.gc"
"test-assoce-2.gc"
"test-append.gc"
"test-delete-list.gc"
"test-delete-car.gc"
"test-insert-cons.gc"
"test-sort.gc"
"test-new-inline-array-class.gc"
"test-pointer-as-array-numbers.gc"
"test-memcpy.gc"
"test-qmemcpy-down.gc"
"test-qmemcpy-up.gc"
"test-memset.gc"
"test-print-binteger.gc"
"test-type-arrays.gc"
"test-number-comparison.gc"
"test-approx-pi.gc"
"test-dynamic-type.gc"
"test-string-type.gc"
"test-new-string.gc"
"test-static-new-integer-field.gc"
"test-addr-of.gc"
"test-set-self.gc"
"test-asm-func.gc"
"test-methods.gc"
"test-bitfield-enums.gc"
"test-packed-inline-array.gc"
"test-fixed-shifts.gc"
"test-add-binteger.gc"
"test-bitfield-access.gc"
"test-bitfield-set1.gc"
)
)
+39
View File
@@ -0,0 +1,39 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; COMPILER TEST
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; macro to set up the target and compiler for a test
(defmacro test-setup (expected-value reset-required)
`(begin
;; first, reboot and connect
;; this uses a "compile time" conditional, as (:r) does the reset when it is _compiled_.
;; so we want to compile the (:r) only if the reset is wanted.
(#when ,reset-required (:r))
;; set expected value in GOOS
(seval (define *test-expected* ,expected-value))
;; make sure that *test-result* is an object to avoid typing errors with future tests.
(define-extern *test-result* object)
)
)
(defmacro test-result (value)
value)
(defmacro expect (v1 v2)
`(if (not (eq? ,v1 ,v2))
(format #t "TEST FAILURE!~%")
)
)
(defmacro expect-true (value)
`(if (not (eq? ,value #t))
(format #t "TEST FAILTURE!~%")
)
)
(defmacro expect-false (value)
`(if (not (eq? ,value #f))
(format #t "TEST FAILTURE!~%")
)
)

Some files were not shown because too many files have changed in this diff Show More