mirror of
https://github.com/open-goal/jak-project
synced 2026-09-12 20:56:09 -04:00
add the old compiler!
This commit is contained in:
+4
-1
@@ -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)
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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, ¯o_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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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?
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -0,0 +1,2 @@
|
||||
add_library(goos_old SHARED Object.cpp Goos.cpp GoosBuiltins.cpp GoosTest.cpp)
|
||||
#target_link_libraries(goos reader)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
add_library(listener_old SHARED Listener.cpp)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
add_library(logger SHARED Logger.cpp)
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
add_library(reader_old SHARED Reader.cpp TextDb.cpp)
|
||||
@@ -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};
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
add_library(regalloc SHARED RegAllocInstr.cpp RegAllocProgram.cpp)
|
||||
@@ -0,0 +1 @@
|
||||
#include "RegAllocInstr.h"
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
+2533
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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))
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
)
|
||||
@@ -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"
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user