add some basic symbol stuff

This commit is contained in:
water 2020-09-07 13:28:16 -04:00
parent 1de0cbb6f6
commit ee4eb9f128
35 changed files with 841 additions and 81 deletions

19
doc/goal_todo.md Normal file
View file

@ -0,0 +1,19 @@
Done (has documentation)
- `e`
- `:exit`
- `asm-file`, `m`, `ml`
- `listen-to-target`, `reset-target`, `:status`, `lt`, `r`
Done (needs documentation)
- `top-level`
- `begin`
- `seval`
- `#cond`, `#when`, `#unless`
- Macro System
Todo
- Type System

View file

@ -121,9 +121,9 @@ _call_goal_asm_linux:
;; set GOAL function pointer
mov r13, rcx
;; offset
mov r15, r8
mov r14, r8
;; symbol table
mov r14, r9
mov r15, r9
;; call GOAL by function pointer
call r13
@ -165,8 +165,8 @@ _call_goal_asm_win32:
mov rsi, rdx ;; rsi is GOAL second argument, rdx is windows second argument
mov rdx, r8 ;; rdx is GOAL third argument, r8 is windows third argument
mov r13, r9 ;; r13 is GOAL fp, r9 is windows fourth argument
mov r15, [rsp + 144] ;; symbol table
mov r14, [rsp + 152] ;; offset
mov r14, [rsp + 144] ;; symbol table
mov r15, [rsp + 152] ;; offset
call r13

View file

@ -12,6 +12,7 @@
#include "kscheme.h"
#include "ksocket.h"
#include "klisten.h"
#include "kprint.h"
#ifdef _WIN32
#include "Windows.h"
@ -133,7 +134,24 @@ void KernelCheckAndDispatch() {
auto old_listener = ListenerFunction->value;
// dispatch the kernel
//(**kernel_dispatcher)();
call_goal(Ptr<Function>(kernel_dispatcher->value), 0, 0, 0, s7.offset, g_ee_main_mem);
// todo remove. this is added while KERNEL.CGO is broken.
if (MasterUseKernel) {
call_goal(Ptr<Function>(kernel_dispatcher->value), 0, 0, 0, s7.offset, g_ee_main_mem);
} else {
if (ListenerFunction->value != s7.offset) {
auto cptr = Ptr<u8>(ListenerFunction->value).c();
for (int i = 0; i < 40; i++) {
printf("%x ", cptr[i]);
}
printf("\n");
auto result =
call_goal(Ptr<Function>(ListenerFunction->value), 0, 0, 0, s7.offset, g_ee_main_mem);
cprintf("%ld\n", result);
ListenerFunction->value = s7.offset;
}
}
// TODO-WINDOWS
#ifdef __linux__
ClearPending();

View file

@ -73,4 +73,6 @@ void KernelCheckAndDispatch();
*/
void KernelShutdown();
constexpr bool MasterUseKernel = false;
#endif // RUNTIME_KBOOT_H

View file

@ -601,7 +601,8 @@ void InitMachineScheme() {
intern_from_c("*kernel-boot-level*")->value = intern_from_c(DebugBootLevel).offset;
}
if (DiskBoot) {
// todo remove MasterUseKernel
if (DiskBoot && MasterUseKernel) {
*EnableMethodSet = (*EnableMethodSet) + 1;
load_and_link_dgo_from_c("game", kglobalheap,
LINK_FLAG_OUTPUT_LOAD | LINK_FLAG_EXECUTE | LINK_FLAG_PRINT_LOGIN,

View file

@ -958,7 +958,10 @@ uint64_t _call_goal_asm_win32(u64 a0, u64 a1, u64 a2, void* fptr, void* st_ptr,
* Wrapper around _call_goal_asm for calling a GOAL function from C.
*/
u64 call_goal(Ptr<Function> f, u64 a, u64 b, u64 c, u64 st, void* offset) {
auto st_ptr = (void*)((uint8_t*)(offset) + st);
// auto st_ptr = (void*)((uint8_t*)(offset) + st); updated for the new compiler!
void* st_ptr = (void*)st;
printf("st is 0x%x\n", st);
void* fptr = f.c();
#ifdef __linux__
return _call_goal_asm_linux(a, b, c, fptr, st_ptr, offset);
@ -1828,25 +1831,28 @@ s32 InitHeapAndSymbol() {
intern_from_c("*boot-video-mode*")->value = 0;
// load the kernel!
method_set_symbol->value++;
load_and_link_dgo_from_c("kernel", kglobalheap,
LINK_FLAG_OUTPUT_LOAD | LINK_FLAG_EXECUTE | LINK_FLAG_PRINT_LOGIN,
0x400000);
method_set_symbol->value--;
// todo, remove MasterUseKernel
if (MasterUseKernel) {
method_set_symbol->value++;
load_and_link_dgo_from_c("kernel", kglobalheap,
LINK_FLAG_OUTPUT_LOAD | LINK_FLAG_EXECUTE | LINK_FLAG_PRINT_LOGIN,
0x400000);
method_set_symbol->value--;
// check the kernel version!
auto kernel_version = intern_from_c("*kernel-version*")->value;
if (!kernel_version || ((kernel_version >> 0x13) != KERNEL_VERSION_MAJOR)) {
MsgErr("\n");
MsgErr(
"dkernel: compiled C kernel version is %d.%d but the goal kernel is %d.%d\n\tfrom the "
"goal> prompt (:mch) then mkee your kernel in linux.\n",
KERNEL_VERSION_MAJOR, KERNEL_VERSION_MINOR, kernel_version >> 0x13,
(kernel_version >> 3) & 0xffff);
return -1;
} else {
printf("Got correct kernel version %d.%d\n", kernel_version >> 0x13,
(kernel_version >> 3) & 0xffff);
// check the kernel version!
auto kernel_version = intern_from_c("*kernel-version*")->value;
if (!kernel_version || ((kernel_version >> 0x13) != KERNEL_VERSION_MAJOR)) {
MsgErr("\n");
MsgErr(
"dkernel: compiled C kernel version is %d.%d but the goal kernel is %d.%d\n\tfrom the "
"goal> prompt (:mch) then mkee your kernel in linux.\n",
KERNEL_VERSION_MAJOR, KERNEL_VERSION_MINOR, kernel_version >> 0x13,
(kernel_version >> 3) & 0xffff);
return -1;
} else {
printf("Got correct kernel version %d.%d\n", kernel_version >> 0x13,
(kernel_version >> 3) & 0xffff);
}
}
// setup deci2count for message counter.

View file

@ -7,3 +7,47 @@
(defmacro ml (file)
`(asm-file ,file :color :load :write)
)
(defmacro e ()
`(:exit)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; CONDITIONAL COMPILATION
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro #when (clause &rest body)
`(#cond (,clause ,@body))
)
(defmacro #unless (clause &rest body)
`(#cond ((not ,clause) ,@body))
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 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)
)
)

View file

@ -0,0 +1,12 @@
;; test the use of #cond to evaluate goos expressions at compile time
(#cond
((> 2 (+ 2 1))
1
(invalid-code)
)
((< 2 (+ 1 2))
3
)
)

View file

@ -0,0 +1,10 @@
(define first-var 1)
(define second-var 2)
(define first-var 12)
(begin
(define first-var 13)
(define second-var 12)
(define first-var 17)
second-var
first-var)

View file

@ -0,0 +1 @@
'#f

View file

@ -0,0 +1 @@
'#t

View file

@ -23,6 +23,7 @@ add_library(compiler
compiler/compilation/CompilerControl.cpp
compiler/compilation/Block.cpp
compiler/compilation/Macro.cpp
compiler/compilation/Define.cpp
compiler/Util.cpp
logger/Logger.cpp
regalloc/IRegister.cpp

View file

@ -19,7 +19,6 @@ Compiler::Compiler() {
}
void Compiler::execute_repl() {
m_listener.connect_to_target(); // todo, remove
while (!m_want_exit) {
try {
// 1). get a line from the user (READ)
@ -86,6 +85,10 @@ FileEnv* Compiler::compile_object_file(const std::string& name,
file_env->add_top_level_function(
compile_top_level_function("top-level", std::move(code), compilation_env));
if (!allow_emit && !file_env->is_empty()) {
throw std::runtime_error("Compilation generated code, but wasn't supposed to");
}
return file_env;
}
@ -160,29 +163,34 @@ std::vector<u8> Compiler::codegen_object_file(FileEnv* env) {
}
std::vector<std::string> Compiler::run_test(const std::string& source_code) {
if (!m_listener.is_connected()) {
for (int i = 0; i < 1000; i++) {
m_listener.connect_to_target();
usleep(10000);
if (m_listener.is_connected()) {
break;
try {
if (!m_listener.is_connected()) {
for (int i = 0; i < 1000; i++) {
m_listener.connect_to_target();
usleep(10000);
if (m_listener.is_connected()) {
break;
}
}
if (!m_listener.is_connected()) {
throw std::runtime_error("Compiler::run_test couldn't connect!");
}
}
if (!m_listener.is_connected()) {
throw std::runtime_error("Compiler::run_test couldn't connect!");
}
}
auto code = m_goos.reader.read_from_file(source_code);
auto compiled = compile_object_file("test-code", code, true);
color_object_file(compiled);
auto data = codegen_object_file(compiled);
m_listener.record_messages(ListenerMessageKind::MSG_PRINT);
m_listener.send_code(data);
if (!m_listener.most_recent_send_was_acked()) {
gLogger.log(MSG_ERR, "Runtime is not responding after sending test code. Did it crash?\n");
auto code = m_goos.reader.read_from_file(source_code);
auto compiled = compile_object_file("test-code", code, true);
color_object_file(compiled);
auto data = codegen_object_file(compiled);
m_listener.record_messages(ListenerMessageKind::MSG_PRINT);
m_listener.send_code(data);
if (!m_listener.most_recent_send_was_acked()) {
gLogger.log(MSG_ERR, "Runtime is not responding after sending test code. Did it crash?\n");
}
return m_listener.stop_recording_messages();
} catch (std::exception& e) {
fmt::print("[Compiler] Failed to compile test program {}: {}\n", source_code, e.what());
return {};
}
return m_listener.stop_recording_messages();
}
void Compiler::shutdown_target() {
@ -190,3 +198,10 @@ void Compiler::shutdown_target() {
m_listener.send_reset(true);
}
}
void Compiler::typecheck(const goos::Object& form,
const TypeSpec& expected,
const TypeSpec& actual,
const std::string& error_message) {
m_ts.typecheck(expected, actual, error_message, true, true);
}

View file

@ -35,6 +35,9 @@ class Compiler {
Val* compile_pair(const goos::Object& code, Env* env);
Val* compile_integer(const goos::Object& code, Env* env);
Val* compile_integer(s64 value, Env* env);
Val* compile_symbol(const goos::Object& form, Env* env);
Val* compile_get_symbol_value(const std::string& name, Env* env);
SymbolVal* compile_get_sym_obj(const std::string& name, Env* env);
void color_object_file(FileEnv* env);
std::vector<u8> codegen_object_file(FileEnv* env);
@ -48,6 +51,8 @@ class Compiler {
const std::vector<util::MatchParam<goos::ObjectType>>& unnamed,
const std::unordered_map<std::string, std::pair<bool, util::MatchParam<goos::ObjectType>>>&
named);
std::string as_string(const goos::Object& o);
std::string symbol_string(const goos::Object& o);
TypeSystem m_ts;
std::unique_ptr<GlobalEnv> m_global_env = nullptr;
@ -55,12 +60,36 @@ class Compiler {
bool m_want_exit = false;
listener::Listener m_listener;
goos::Interpreter m_goos;
std::unordered_map<std::string, TypeSpec> m_symbol_types;
std::unordered_map<std::shared_ptr<goos::SymbolObject>, goos::Object> m_global_constants;
std::unordered_map<std::shared_ptr<goos::SymbolObject>, LambdaVal*> m_inlineable_functions;
void typecheck(const goos::Object& form,
const TypeSpec& expected,
const TypeSpec& actual,
const std::string& error_message = "");
public:
Val* compile_exit(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_top_level(const goos::Object& form, const goos::Object& rest, Env* env);
// Atoms
// Block
Val* compile_begin(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_top_level(const goos::Object& form, const goos::Object& rest, Env* env);
// CompilerControl
Val* compile_seval(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_exit(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_asm_file(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_listen_to_target(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_reset_target(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_poke(const goos::Object& form, const goos::Object& rest, Env* env);
// Define
Val* compile_define(const goos::Object& form, const goos::Object& rest, Env* env);
// Macro
Val* compile_gscond(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_quote(const goos::Object& form, const goos::Object& rest, Env* env);
};
#endif // JAK_COMPILER_H

View file

@ -88,7 +88,6 @@ class FileEnv : public Env {
void debug_print_tl();
const std::vector<std::unique_ptr<FunctionEnv>>& functions() { return m_functions; }
// todo - is_empty
bool is_empty();
~FileEnv() = default;
@ -115,7 +114,7 @@ class DeclareEnv : public Env {
bool inline_by_default = false; // if a function, inline when possible?
bool save_code = true; // if a function, should we save the code?
bool allow_inline = false; // should we allow the user to use this an inline function
} m_settings;
} settings;
};
class FunctionEnv : public DeclareEnv {

View file

@ -1,4 +1,6 @@
#include "IR.h"
#include <utility>
#include "goalc/emitter/IGen.h"
using namespace emitter;
@ -11,6 +13,9 @@ Register get_reg(const RegVal* rv, const AllocationResult& allocs, emitter::IR_R
}
} // namespace
///////////
// Return
///////////
IR_Return::IR_Return(const RegVal* return_reg, const RegVal* value)
: m_return_reg(return_reg), m_value(value) {}
std::string IR_Return::print() {
@ -52,6 +57,9 @@ void IR_Return::do_codegen(emitter::ObjectGenerator* gen,
}
}
/////////////////////
// LoadConstant64
/////////////////////
IR_LoadConstant64::IR_LoadConstant64(const RegVal* dest, u64 value)
: m_dest(dest), m_value(value) {}
@ -71,3 +79,93 @@ void IR_LoadConstant64::do_codegen(emitter::ObjectGenerator* gen,
auto dest_reg = get_reg(m_dest, allocs, irec);
gen->add_instr(IGen::mov_gpr64_u64(dest_reg, m_value), irec);
}
/////////////////////
// LoadSymbolPointer
/////////////////////
IR_LoadSymbolPointer::IR_LoadSymbolPointer(const RegVal* dest, std::string name)
: m_dest(dest), m_name(std::move(name)) {}
std::string IR_LoadSymbolPointer::print() {
return fmt::format("mov-symptr {}, '{}", m_dest->print(), m_name);
}
RegAllocInstr IR_LoadSymbolPointer::to_rai() {
RegAllocInstr rai;
rai.write.push_back(m_dest->ireg());
return rai;
}
void IR_LoadSymbolPointer::do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) {
auto dest_reg = get_reg(m_dest, allocs, irec);
// todo, could be single lea opcode
gen->add_instr(IGen::mov_gpr64_gpr64(dest_reg, gRegInfo.get_st_reg()), irec);
auto add = gen->add_instr(IGen::add_gpr64_imm32s(dest_reg, 0x0afecafe), irec);
gen->link_instruction_symbol_ptr(add, m_name);
}
/////////////////////
// SetSymbolValue
/////////////////////
IR_SetSymbolValue::IR_SetSymbolValue(const SymbolVal* dest, const RegVal* src)
: m_dest(dest), m_src(src) {}
std::string IR_SetSymbolValue::print() {
return fmt::format("mov '{}, {}", m_dest->name(), m_src->print());
}
RegAllocInstr IR_SetSymbolValue::to_rai() {
RegAllocInstr rai;
rai.read.push_back(m_src->ireg());
return rai;
}
void IR_SetSymbolValue::do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) {
auto src_reg = get_reg(m_src, allocs, irec);
auto instr =
gen->add_instr(IGen::store32_gpr64_gpr64_plus_gpr64_plus_s32(
gRegInfo.get_st_reg(), gRegInfo.get_offset_reg(), src_reg, 0x0badbeef),
irec);
gen->link_instruction_symbol_mem(instr, m_dest->name());
}
/////////////////////
// GetSymbolValue
/////////////////////
IR_GetSymbolValue::IR_GetSymbolValue(const RegVal* dest, const SymbolVal* src, bool sext)
: m_dest(dest), m_src(src), m_sext(sext) {}
std::string IR_GetSymbolValue::print() {
return fmt::format("mov {}, '{}", m_dest->print(), m_src->name());
}
RegAllocInstr IR_GetSymbolValue::to_rai() {
RegAllocInstr rai;
rai.write.push_back(m_dest->ireg());
return rai;
}
void IR_GetSymbolValue::do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) {
auto dst_reg = get_reg(m_dest, allocs, irec);
if (m_sext) {
auto instr =
gen->add_instr(IGen::load32s_gpr64_gpr64_plus_gpr64_plus_s32(
dst_reg, gRegInfo.get_st_reg(), gRegInfo.get_offset_reg(), 0x0badbeef),
irec);
gen->link_instruction_symbol_mem(instr, m_src->name());
} else {
auto instr =
gen->add_instr(IGen::load32u_gpr64_gpr64_plus_gpr64_plus_s32(
dst_reg, gRegInfo.get_st_reg(), gRegInfo.get_offset_reg(), 0x0badbeef),
irec);
gen->link_instruction_symbol_mem(instr, m_src->name());
}
}

View file

@ -56,4 +56,47 @@ class IR_LoadConstant64 : public IR {
u64 m_value = 0;
};
class IR_LoadSymbolPointer : public IR {
public:
IR_LoadSymbolPointer(const RegVal* dest, std::string name);
std::string print() override;
RegAllocInstr to_rai() override;
void do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) override;
protected:
const RegVal* m_dest = nullptr;
std::string m_name;
};
class IR_SetSymbolValue : public IR {
public:
IR_SetSymbolValue(const SymbolVal* dest, const RegVal* src);
std::string print() override;
RegAllocInstr to_rai() override;
void do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) override;
protected:
const SymbolVal* m_dest = nullptr;
const RegVal* m_src = nullptr;
};
class IR_GetSymbolValue : public IR {
public:
IR_GetSymbolValue(const RegVal* dest, const SymbolVal* src, bool sext);
std::string print() override;
RegAllocInstr to_rai() override;
void do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) override;
protected:
const RegVal* m_dest = nullptr;
const SymbolVal* m_src = nullptr;
bool m_sext = false;
};
#endif // JAK_IR_H

View file

@ -96,3 +96,11 @@ void Compiler::for_each_in_list(const goos::Object& list,
throw_compile_error(list, "invalid list in for_each_in_list");
}
}
std::string Compiler::as_string(const goos::Object& o) {
return o.as_string()->data;
}
std::string Compiler::symbol_string(const goos::Object& o) {
return o.as_symbol()->name;
}

View file

@ -5,7 +5,7 @@
/*!
* Fallback to_gpr if a more optimized one is not provided.
*/
const RegVal* Val::to_gpr(FunctionEnv* fe) const {
RegVal* Val::to_gpr(Env* fe) {
auto rv = to_reg(fe);
if (rv->ireg().kind == emitter::RegKind::GPR) {
return rv;
@ -17,17 +17,17 @@ const RegVal* Val::to_gpr(FunctionEnv* fe) const {
/*!
* Fallback to_xmm if a more optimized one is not provided.
*/
const RegVal* Val::to_xmm(FunctionEnv* fe) const {
RegVal* Val::to_xmm(Env* fe) {
(void)fe;
throw std::runtime_error("Val::to_xmm NYI"); // todo
}
const RegVal* RegVal::to_reg(FunctionEnv* fe) const {
RegVal* RegVal::to_reg(Env* fe) {
(void)fe;
return this;
}
const RegVal* RegVal::to_gpr(FunctionEnv* fe) const {
RegVal* RegVal::to_gpr(Env* fe) {
(void)fe;
if (m_ireg.kind == emitter::RegKind::GPR) {
return this;
@ -36,7 +36,7 @@ const RegVal* RegVal::to_gpr(FunctionEnv* fe) const {
}
}
const RegVal* RegVal::to_xmm(FunctionEnv* fe) const {
RegVal* RegVal::to_xmm(Env* fe) {
(void)fe;
if (m_ireg.kind == emitter::RegKind::XMM) {
return this;
@ -45,8 +45,20 @@ const RegVal* RegVal::to_xmm(FunctionEnv* fe) const {
}
}
const RegVal* IntegerConstantVal::to_reg(FunctionEnv* fe) const {
RegVal* IntegerConstantVal::to_reg(Env* fe) {
auto rv = fe->make_gpr(m_ts);
fe->emit(std::make_unique<IR_LoadConstant64>(rv, m_value));
return rv;
}
RegVal* SymbolVal::to_reg(Env* fe) {
auto re = fe->make_gpr(m_ts);
fe->emit(std::make_unique<IR_LoadSymbolPointer>(re, m_name));
return re;
}
RegVal* SymbolValueVal::to_reg(Env* fe) {
auto re = fe->make_gpr(m_ts);
fe->emit(std::make_unique<IR_GetSymbolValue>(re, m_sym, m_sext));
return re;
}

View file

@ -15,6 +15,7 @@
#include "Lambda.h"
class RegVal;
class Env;
class FunctionEnv;
/*!
@ -31,12 +32,12 @@ class Val {
}
virtual std::string print() const = 0;
virtual const RegVal* to_reg(FunctionEnv* fe) const {
virtual RegVal* to_reg(Env* fe) {
(void)fe;
throw std::runtime_error("to_reg called on invalid Val: " + print());
}
virtual const RegVal* to_gpr(FunctionEnv* fe) const;
virtual const RegVal* to_xmm(FunctionEnv* fe) const;
virtual RegVal* to_gpr(Env* fe);
virtual RegVal* to_xmm(Env* fe);
const TypeSpec& type() const { return m_ts; }
void set_type(TypeSpec ts) { m_ts = std::move(ts); }
@ -64,9 +65,9 @@ class RegVal : public Val {
bool is_register() const override { return true; }
IRegister ireg() const override { return m_ireg; }
std::string print() const override { return m_ireg.to_string(); };
const RegVal* to_reg(FunctionEnv* fe) const override;
const RegVal* to_gpr(FunctionEnv* fe) const override;
const RegVal* to_xmm(FunctionEnv* fe) const override;
RegVal* to_reg(Env* fe) override;
RegVal* to_gpr(Env* fe) override;
RegVal* to_xmm(Env* fe) override;
protected:
IRegister m_ireg;
@ -79,13 +80,27 @@ class RegVal : public Val {
class SymbolVal : public Val {
public:
SymbolVal(std::string name, TypeSpec ts) : Val(std::move(ts)), m_name(std::move(name)) {}
const std::string& name() { return m_name; }
const std::string& name() const { return m_name; }
std::string print() const override { return "<" + m_name + ">"; }
RegVal* to_reg(Env* fe) override;
protected:
std::string m_name;
};
class SymbolValueVal : public Val {
public:
SymbolValueVal(const SymbolVal* sym, TypeSpec ts, bool sext)
: Val(std::move(ts)), m_sym(sym), m_sext(sext) {}
const std::string& name() const { return m_sym->name(); }
std::string print() const override { return "[<" + name() + ">]"; }
RegVal* to_reg(Env* fe) override;
protected:
const SymbolVal* m_sym = nullptr;
bool m_sext = false;
};
/*!
* A Val representing a GOAL lambda. It can be a "real" x86-64 function, in which case the
* FunctionEnv is set. Otherwise, just contains a Lambda.
@ -94,10 +109,10 @@ class LambdaVal : public Val {
public:
LambdaVal(TypeSpec ts, Lambda lam) : Val(ts), m_lam(lam) {}
std::string print() const override { return "lambda-" + m_lam.debug_name; }
FunctionEnv* func = nullptr;
protected:
Lambda m_lam;
FunctionEnv* fe = nullptr;
};
// Static
@ -111,7 +126,7 @@ class IntegerConstantVal : public Val {
public:
IntegerConstantVal(TypeSpec ts, s64 value) : Val(ts), m_value(value) {}
std::string print() const override { return "integer-constant-" + std::to_string(m_value); }
const RegVal* to_reg(FunctionEnv* fe) const override;
RegVal* to_reg(Env* fe) override;
protected:
s64 m_value = -1;

View file

@ -27,12 +27,15 @@ static const std::unordered_map<
// // COMPILER CONTROL
// {"gs", &Compiler::compile_gs},
{":exit", &Compiler::compile_exit},
// {"asm-file", &Compiler::compile_asm_file},
{"asm-file", &Compiler::compile_asm_file},
{"listen-to-target", &Compiler::compile_listen_to_target},
{"reset-target", &Compiler::compile_reset_target},
{":status", &Compiler::compile_poke},
// {"test", &Compiler::compile_test},
// {"in-package", &Compiler::compile_in_package},
//
// // CONDITIONAL COMPILATION
// {"#cond", &Compiler::compile_gscond},
{"#cond", &Compiler::compile_gscond},
// {"defglobalconstant", &Compiler::compile_defglobalconstant},
{"seval", &Compiler::compile_seval},
//
@ -41,7 +44,7 @@ static const std::unordered_map<
// {"when-goto", &Compiler::compile_when_goto},
//
// // DEFINITION
// {"define", &Compiler::compile_define},
{"define", &Compiler::compile_define},
// {"define-extern", &Compiler::compile_define_extern},
// {"set!", &Compiler::compile_set},
// {"defun-extern", &Compiler::compile_defun_extern},
@ -70,7 +73,7 @@ static const std::unordered_map<
//
// // MACRO
// {"print-type", &Compiler::compile_print_type},
// {"quote", &Compiler::compile_quote},
{"quote", &Compiler::compile_quote},
// {"defconstant", &Compiler::compile_defconstant},
//
// {"declare", &Compiler::compile_declare},
@ -126,9 +129,7 @@ static const std::unordered_map<
//
//
//
// {"listen-to-target", &Compiler::compile_listen_to_target},
// {"reset-target", &Compiler::compile_reset_target},
// {":status", &Compiler::compile_poke},
//
// // temporary testing hacks...
// {"send-test", &Compiler::compile_send_test_data},
@ -140,6 +141,8 @@ Val* Compiler::compile(const goos::Object& code, Env* env) {
return compile_pair(code, env);
case goos::ObjectType::INTEGER:
return compile_integer(code, env);
case goos::ObjectType::SYMBOL:
return compile_symbol(code, env);
default:
ice("Don't know how to compile " + code.print());
}
@ -180,3 +183,36 @@ Val* Compiler::compile_integer(s64 value, Env* env) {
auto fe = get_parent_env_of_type<FunctionEnv>(env);
return fe->alloc_val<IntegerConstantVal>(m_ts.make_typespec("int"), value);
}
SymbolVal* Compiler::compile_get_sym_obj(const std::string& name, Env* env) {
auto fe = get_parent_env_of_type<FunctionEnv>(env);
return fe->alloc_val<SymbolVal>(name, m_ts.make_typespec("symbol"));
}
Val* Compiler::compile_symbol(const goos::Object& form, Env* env) {
auto name = symbol_string(form);
if (name == "none") {
return get_none();
}
// todo mlet
// todo lexical
// todo global constant
return compile_get_symbol_value(name, env);
}
Val* Compiler::compile_get_symbol_value(const std::string& name, Env* env) {
auto existing_symbol = m_symbol_types.find(name);
if (existing_symbol == m_symbol_types.end()) {
throw std::runtime_error("The symbol " + name + " was not defined");
}
auto ts = existing_symbol->second;
auto sext = m_ts.lookup_type(ts)->get_load_signed();
auto fe = get_parent_env_of_type<FunctionEnv>(env);
auto sym = fe->alloc_val<SymbolVal>(name, m_ts.make_typespec("symbol"));
auto re = fe->alloc_val<SymbolValueVal>(sym, ts, sext);
return re;
}

View file

@ -1,5 +1,7 @@
#include "goalc/compiler/Compiler.h"
#include "goalc/compiler/IR.h"
#include "goalc/util/Timer.h"
#include "goalc/util/file_io.h"
Val* Compiler::compile_exit(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)env;
@ -23,3 +25,139 @@ Val* Compiler::compile_seval(const goos::Object& form, const goos::Object& rest,
}
return get_none();
}
Val* Compiler::compile_asm_file(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)env;
int i = 0;
std::string filename;
bool load = false;
bool color = false;
bool write = false;
bool no_code = false;
std::vector<std::pair<std::string, float>> timing;
Timer total_timer;
for_each_in_list(rest, [&](const goos::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 if (setting == ":no-code") {
no_code = true;
} else {
throw_compile_error(form, "invalid option " + setting + " in asm-file form");
}
}
i++;
});
Timer reader_timer;
auto code = m_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, !no_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 (m_listener.is_connected()) {
m_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*"));
// todo, change extension based on v3/v4
auto output_name = m_goos.reader.get_source_dir() + "/out/" + obj_file_name + ".o";
util::write_binary_file(output_name, (void*)data.data(), data.size());
}
} 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();
}
Val* Compiler::compile_listen_to_target(const goos::Object& form,
const goos::Object& rest,
Env* 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, [&](const goos::Object& o) {
if (o.is_string()) {
if (got_ip) {
throw_compile_error(form, "got multiple strings!");
}
got_ip = true;
ip = o.as_string()->data;
} else if (o.is_int()) {
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");
}
});
m_listener.connect_to_target(30, ip, port);
return get_none();
}
Val* Compiler::compile_reset_target(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)env;
bool shutdown = false;
for_each_in_list(rest, [&](const goos::Object& o) {
if (o.is_symbol() && symbol_string(o) == ":shutdown") {
shutdown = true;
} else {
throw_compile_error(form, "invalid argument to reset-target");
}
});
m_listener.send_reset(shutdown);
return get_none();
}
Val* Compiler::compile_poke(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)env;
auto args = get_va(form, rest);
va_check(form, args, {}, {});
m_listener.send_poke();
return get_none();
}

View file

@ -0,0 +1,41 @@
#include "goalc/compiler/Compiler.h"
Val* Compiler::compile_define(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
va_check(form, args, {goos::ObjectType::SYMBOL, {}}, {});
auto& sym = args.unnamed.at(0);
auto& val = args.unnamed.at(1);
// check we aren't duplicated a name as both a symbol and global constant
auto global_constant = m_global_constants.find(sym.as_symbol());
if (global_constant != m_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 fe = get_parent_env_of_type<FunctionEnv>(env);
auto sym_val = fe->alloc_val<SymbolVal>(symbol_string(sym), m_ts.make_typespec("symbol"));
auto compiled_val = compile_error_guard(val, env);
auto as_lambda = dynamic_cast<LambdaVal*>(compiled_val);
if (as_lambda) {
// 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->func && as_lambda->func->settings.allow_inline) || !as_lambda->func) {
m_inlineable_functions[sym.as_symbol()] = as_lambda;
}
}
auto in_gpr = compiled_val->to_gpr(fe);
auto existing_type = m_symbol_types.find(sym.as_symbol()->name);
if (existing_type == m_symbol_types.end()) {
m_symbol_types[sym.as_symbol()->name] = in_gpr->type();
} else {
typecheck(form, existing_type->second, in_gpr->type(), "define on existing symbol");
}
fe->emit(std::make_unique<IR_SetSymbolValue>(sym_val, in_gpr));
return in_gpr;
}

View file

@ -35,3 +35,55 @@ Val* Compiler::compile_goos_macro(const goos::Object& o,
m_goos.goal_to_goos.reset();
return compile_error_guard(goos_result, env);
}
Val* Compiler::compile_gscond(const goos::Object& form, const goos::Object& rest, Env* env) {
if (!rest.is_pair()) {
throw_compile_error(form, "#cond must have at least one clause, which must be a form");
}
Val* result = nullptr;
Object lst = rest;
for (;;) {
if (lst.is_pair()) {
Object current_case = lst.as_pair()->car;
if (!current_case.is_pair()) {
throw_compile_error(lst, "Bad case in #cond");
}
// check condition:
Object condition_result =
m_goos.eval_with_rewind(current_case.as_pair()->car, m_goos.global_environment.as_env());
if (m_goos.truthy(condition_result)) {
if (current_case.as_pair()->cdr.is_empty_list()) {
return get_none();
}
// got a match!
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.is_empty_list()) {
return get_none();
} else {
throw_compile_error(form, "malformed #cond");
}
}
}
Val* Compiler::compile_quote(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
va_check(form, args, {{}}, {});
auto thing = args.unnamed.at(0);
switch (thing.type) {
case goos::ObjectType::SYMBOL:
return compile_get_sym_obj(thing.as_symbol()->name, env);
default:
throw_compile_error(form, "Can't quote this");
}
return get_none();
}

View file

@ -14,6 +14,11 @@ std::vector<u8> ObjectFileData::to_vector() const {
// data (code + static objects, by segment)
for (int seg = N_SEG; seg-- > 0;) {
result.insert(result.end(), segment_data[seg].begin(), segment_data[seg].end());
// printf("seg %d data\n", seg);
// for (auto x : segment_data[seg]) {
// printf("%02x ", x);
// }
// printf("\n");
}
return result;
}

View file

@ -805,11 +805,9 @@ Object Interpreter::eval_quasiquote(const Object& form,
return quasiquote_helper(rest.as_pair()->car, env);
}
namespace {
bool truthy(const Object& o) {
bool Interpreter::truthy(const Object& o) {
return !(o.is_symbol() && o.as_symbol()->name == "#f");
}
} // namespace
/*!
* Scheme "cond" statement - tested by integrated tests only.

View file

@ -32,6 +32,7 @@ class Interpreter {
Object eval_list_return_last(const Object& form,
Object rest,
const std::shared_ptr<EnvironmentObject>& env);
bool truthy(const Object& o);
Reader reader;
Object global_environment;

View file

@ -51,9 +51,10 @@ bool Listener::is_connected() const {
* Attempt to connect to the target. If the target isn't running, this should fail quickly.
* Returns true if successfully connected.
*/
bool Listener::connect_to_target(const std::string& ip, int port) {
bool Listener::connect_to_target(int n_tries, const std::string& ip, int port) {
if (m_connected) {
throw std::runtime_error("attempted a Listener::connect_to_target when already connected!");
printf("already connected!\n");
return true;
}
if (socket_fd >= 0) {
@ -100,12 +101,21 @@ bool Listener::connect_to_target(const std::string& ip, int port) {
}
// connect!
int rv = connect(socket_fd, (sockaddr*)&server_address, sizeof(server_address));
int rv, i;
for (i = 0; i < n_tries; i++) {
rv = connect(socket_fd, (sockaddr*)&server_address, sizeof(server_address));
if (rv >= 0) {
break;
}
usleep(100000);
}
if (rv < 0) {
printf("[Listener] Failed to connect\n");
close(socket_fd);
socket_fd = -1;
return false;
} else {
printf("[Listener] Socket connected established! (took %d tries)\n", i);
}
// get the GOAL version number, to make sure we connected to the right thing
@ -303,6 +313,24 @@ void Listener::send_reset(bool shutdown) {
printf("closed connection to target\n");
}
void Listener::send_poke() {
if (!m_connected) {
printf("Not connected, so cannot poke target.\n");
return;
}
auto* header = (ListenerMessageHeader*)m_buffer;
header->deci2_header.rsvd = 0;
header->deci2_header.len = sizeof(ListenerMessageHeader);
header->deci2_header.proto = 0xe042; // todo don't hardcode
header->deci2_header.src = 'H';
header->deci2_header.dst = 'E';
header->msg_size = 0;
header->ltt_msg_kind = LTT_MSG_POKE;
header->u6 = 0;
header->u8 = 0;
send_buffer(sizeof(ListenerMessageHeader));
}
void Listener::send_buffer(int sz) {
int wrote = 0;

View file

@ -19,11 +19,14 @@ class Listener {
static constexpr int BUFFER_SIZE = 32 * 1024 * 1024;
Listener();
~Listener();
bool connect_to_target(const std::string& ip = "127.0.0.1", int port = DECI2_PORT);
bool connect_to_target(int n_tries = 1,
const std::string& ip = "127.0.0.1",
int port = DECI2_PORT);
void record_messages(ListenerMessageKind kind);
std::vector<std::string> stop_recording_messages();
bool is_connected() const;
void send_reset(bool shutdown);
void send_poke();
void disconnect();
void send_code(std::vector<uint8_t>& code);
bool most_recent_send_was_acked() { return got_ack; }

View file

@ -1 +1 @@
add_library(util SHARED text_util.cpp file_io.cpp)
add_library(util SHARED text_util.cpp file_io.cpp Timer.cpp)

54
goalc/util/Timer.cpp Normal file
View file

@ -0,0 +1,54 @@
#include "Timer.h"
#ifdef _WIN32
#include <Windows.h>
#define MS_PER_SEC 1000ULL // MS = milliseconds
#define US_PER_MS 1000ULL // US = microseconds
#define HNS_PER_US 10ULL // HNS = hundred-nanoseconds (e.g., 1 hns = 100 ns)
#define NS_PER_US 1000ULL
#define HNS_PER_SEC (MS_PER_SEC * US_PER_MS * HNS_PER_US)
#define NS_PER_HNS (100ULL) // NS = nanoseconds
#define NS_PER_SEC (MS_PER_SEC * US_PER_MS * NS_PER_US)
int Timer::clock_gettime_monotonic(struct timespec* tv) {
static LARGE_INTEGER ticksPerSec;
LARGE_INTEGER ticks;
double seconds;
if (!ticksPerSec.QuadPart) {
QueryPerformanceFrequency(&ticksPerSec);
if (!ticksPerSec.QuadPart) {
errno = ENOTSUP;
return -1;
}
}
QueryPerformanceCounter(&ticks);
seconds = (double)ticks.QuadPart / (double)ticksPerSec.QuadPart;
tv->tv_sec = (time_t)seconds;
tv->tv_nsec = (long)((ULONGLONG)(seconds * NS_PER_SEC) % NS_PER_SEC);
return 0;
}
#endif
void Timer::start() {
#ifdef __linux__
clock_gettime(CLOCK_MONOTONIC, &_startTime);
#elif _WIN32
clock_gettime_monotonic(&_startTime);
#endif
}
int64_t Timer::getNs() {
struct timespec now = {};
#ifdef __linux__
clock_gettime(CLOCK_MONOTONIC, &now);
#elif _WIN32
clock_gettime_monotonic(&now);
#endif
return (int64_t)(now.tv_nsec - _startTime.tv_nsec) +
1000000000 * (now.tv_sec - _startTime.tv_sec);
}

47
goalc/util/Timer.h Normal file
View file

@ -0,0 +1,47 @@
#ifndef JAK_V2_TIMER_H
#define JAK_V2_TIMER_H
#include <cassert>
#include <cstdint>
#include <ctime>
/*!
* Timer for measuring time elapsed with clock_monotonic
*/
class Timer {
public:
/*!
* Construct and start timer
*/
explicit Timer() { start(); }
#ifdef _WIN32
int clock_gettime_monotonic(struct timespec* tv);
#endif
/*!
* Start the timer
*/
void start();
/*!
* Get milliseconds elapsed
*/
double getMs() { return (double)getNs() / 1.e6; }
double getUs() { return (double)getNs() / 1.e3; }
/*!
* Get nanoseconds elapsed
*/
int64_t getNs();
/*!
* Get seconds elapsed
*/
double getSeconds() { return (double)getNs() / 1.e9; }
struct timespec _startTime = {};
};
#endif // JAK_V2_TIMER_H

View file

@ -29,4 +29,17 @@ std::string combine_path(std::vector<std::string> path) {
return result;
}
void write_binary_file(const std::string& name, void* data, size_t size) {
FILE* fp = fopen(name.c_str(), "wb");
if (!fp) {
throw std::runtime_error("couldn't open file " + name);
}
if (fwrite(data, size, 1, fp) != 1) {
throw std::runtime_error("couldn't write file " + name);
}
fclose(fp);
}
} // namespace util

View file

@ -8,6 +8,7 @@ namespace util {
std::string read_text_file(const std::string& path);
std::string combine_path(const std::string& parent, const std::string& child);
std::string combine_path(std::vector<std::string> path);
void write_binary_file(const std::string& name, void* data, size_t size);
} // namespace util
#endif // JAK1_FILE_IO_H

View file

@ -69,10 +69,10 @@ struct CompilerTestRunner {
int passed = 0;
for (auto& test : tests) {
if (test.expected == test.actual) {
fmt::print("[{:30}] PASS!\n", test.test_name);
fmt::print("[{:40}] PASS!\n", test.test_name);
passed++;
} else {
fmt::print("[{:30}] FAIL!\n", test.test_name);
fmt::print("[{:40}] FAIL!\n", test.test_name);
fmt::print("expected:\n");
for (auto& x : test.expected) {
fmt::print(" \"{}\"\n", escaped_string(x));
@ -102,6 +102,15 @@ TEST(CompilerAndRuntime, CompilerTests) {
runner.run_test("test-return-integer-5.gc", {"-2147483649\n"});
runner.run_test("test-return-integer-6.gc", {"0\n"});
runner.run_test("test-return-integer-7.gc", {"-123\n"});
runner.run_test("test-conditional-compilation-1.gc", {"3\n"});
// todo, test-conditional-compilation-2.gc
// these numbers match the game's memory layout for where the symbol table lives.
// it's probably not 100% needed to get this exactly, but it's a good sign that the global
// heap lives in the right spot because there should be no divergence in memory layout when its
// built. This also checks that #t, #f get "hashed" to the correct spot.
runner.run_test("test-get-symbol-1.gc", {"1342756\n"}); // 0x147d24 in hex
runner.run_test("test-get-symbol-2.gc", {"1342764\n"}); // 0x147d2c in hex
runner.run_test("test-define-1.gc", {"17\n"});
compiler.shutdown_target();
runtime_thread.join();