Load from globals only at the top level, do rest with closures
This commit is contained in:
parent
4ab6142ca5
commit
5279b44441
5 changed files with 91 additions and 35 deletions
|
@ -6,10 +6,10 @@
|
||||||
struct Context {
|
struct Context {
|
||||||
Context() {}
|
Context() {}
|
||||||
|
|
||||||
Context(Value&& env, Array&& constants, Dict&& constants_dict,
|
Context(Value&& fname, Array&& constants, Dict&& constants_dict,
|
||||||
Dict&& variables_dict, Array&& closures, Dict&& closures_dict,
|
Dict&& variables_dict, Array&& closures, Dict&& closures_dict,
|
||||||
Dict&& globals_dict, Context* parent, bool toplevel)
|
Dict&& globals_dict, Context* parent, bool toplevel)
|
||||||
: env(std::move(env)),
|
: fname(std::move(fname)),
|
||||||
constants(std::move(constants)),
|
constants(std::move(constants)),
|
||||||
constants_dict(std::move(constants_dict)),
|
constants_dict(std::move(constants_dict)),
|
||||||
variables_dict(std::move(variables_dict)),
|
variables_dict(std::move(variables_dict)),
|
||||||
|
@ -21,7 +21,7 @@ struct Context {
|
||||||
toplevel(toplevel) {}
|
toplevel(toplevel) {}
|
||||||
|
|
||||||
static Result<Context> create() {
|
static Result<Context> create() {
|
||||||
auto env = TRY(Nil::create());
|
auto fname = TRY(Nil::create());
|
||||||
auto constants = TRY(Array::create());
|
auto constants = TRY(Array::create());
|
||||||
auto constants_dict = TRY(Dict::create());
|
auto constants_dict = TRY(Dict::create());
|
||||||
auto variables_dict = TRY(Dict::create());
|
auto variables_dict = TRY(Dict::create());
|
||||||
|
@ -29,14 +29,14 @@ struct Context {
|
||||||
auto closures_dict = TRY(Dict::create());
|
auto closures_dict = TRY(Dict::create());
|
||||||
auto globals_dict = TRY(Dict::create());
|
auto globals_dict = TRY(Dict::create());
|
||||||
|
|
||||||
return Context(std::move(env), std::move(constants),
|
return Context(std::move(fname), std::move(constants),
|
||||||
std::move(constants_dict), std::move(variables_dict),
|
std::move(constants_dict), std::move(variables_dict),
|
||||||
std::move(closures), std::move(closures_dict),
|
std::move(closures), std::move(closures_dict),
|
||||||
std::move(globals_dict), 0, true);
|
std::move(globals_dict), 0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Result<Context> create(Context& parent) {
|
static Result<Context> create(Context& parent) {
|
||||||
auto env = TRY(Nil::create());
|
auto fname = TRY(Nil::create());
|
||||||
auto constants = TRY(Array::create());
|
auto constants = TRY(Array::create());
|
||||||
auto constants_dict = TRY(Dict::create());
|
auto constants_dict = TRY(Dict::create());
|
||||||
auto variables_dict = TRY(Dict::create());
|
auto variables_dict = TRY(Dict::create());
|
||||||
|
@ -44,7 +44,7 @@ struct Context {
|
||||||
auto closures_dict = TRY(Dict::create());
|
auto closures_dict = TRY(Dict::create());
|
||||||
auto globals_dict = TRY(Dict::create());
|
auto globals_dict = TRY(Dict::create());
|
||||||
|
|
||||||
return Context(std::move(env), std::move(constants),
|
return Context(std::move(fname), std::move(constants),
|
||||||
std::move(constants_dict), std::move(variables_dict),
|
std::move(constants_dict), std::move(variables_dict),
|
||||||
std::move(closures), std::move(closures_dict),
|
std::move(closures), std::move(closures_dict),
|
||||||
std::move(globals_dict), &parent, false);
|
std::move(globals_dict), &parent, false);
|
||||||
|
@ -123,14 +123,18 @@ struct Context {
|
||||||
return idx.value().to<Int64>()->value();
|
return idx.value().to<Int64>()->value();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parent) return ERROR(KeyError);
|
// We need to try and look up a symbol in the parent scope. If there's no
|
||||||
|
// parent scope, we will still create a closure, but that closure will be
|
||||||
|
// resolved dynamically at runtime.
|
||||||
|
if (parent) {
|
||||||
auto maybe_var = parent->get_var(sym);
|
auto maybe_var = parent->get_var(sym);
|
||||||
// If parent has no variable with name <sym>, then we need to propagate the
|
// If parent has no variable with name <sym>, then we need to propagate
|
||||||
// closure to it. The variable may be defined somewhere in outer scope.
|
// the closure to it. The variable may be defined somewhere in outer
|
||||||
|
// scope.
|
||||||
if (!maybe_var.has_value()) {
|
if (!maybe_var.has_value()) {
|
||||||
TRY(parent->get_closure(sym));
|
TRY(parent->get_closure(sym));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int64_t i = closures.size();
|
int64_t i = closures.size();
|
||||||
closures = TRY(closures.append(sym));
|
closures = TRY(closures.append(sym));
|
||||||
|
@ -144,7 +148,7 @@ struct Context {
|
||||||
return Result<void>();
|
return Result<void>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Value env;
|
Value fname;
|
||||||
Array constants;
|
Array constants;
|
||||||
Dict constants_dict;
|
Dict constants_dict;
|
||||||
Dict variables_dict;
|
Dict variables_dict;
|
||||||
|
@ -580,6 +584,7 @@ Result<Expression> Compiler::compile_fn(Context& context, Symbol& op,
|
||||||
|
|
||||||
if (maybe_name.is<Symbol>()) {
|
if (maybe_name.is<Symbol>()) {
|
||||||
name = TRY(maybe_name.copy());
|
name = TRY(maybe_name.copy());
|
||||||
|
ctx.fname = TRY(name.copy());
|
||||||
|
|
||||||
first = TRY(first.rest());
|
first = TRY(first.rest());
|
||||||
|
|
||||||
|
@ -665,7 +670,7 @@ Result<Expression> Compiler::compile_fn(Context& context, Symbol& op,
|
||||||
// (such as in case of 3 nested functions where a var we close over is
|
// (such as in case of 3 nested functions where a var we close over is
|
||||||
// defined in the outermost function.
|
// defined in the outermost function.
|
||||||
auto maybe_closure = context.get_closure(sym);
|
auto maybe_closure = context.get_closure(sym);
|
||||||
if (maybe_closure.has_value()) {
|
if (maybe_closure.has_value() && !context.toplevel) {
|
||||||
int64_t closure_id = maybe_closure.value();
|
int64_t closure_id = maybe_closure.value();
|
||||||
uint64_t vr = context.alloc_reg();
|
uint64_t vr = context.alloc_reg();
|
||||||
TRY(ex_res.add_opcode(Oc::ClosureLoad, {0, (int64_t)vr},
|
TRY(ex_res.add_opcode(Oc::ClosureLoad, {0, (int64_t)vr},
|
||||||
|
@ -673,7 +678,10 @@ Result<Expression> Compiler::compile_fn(Context& context, Symbol& op,
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ERROR(NotImplemented);
|
int64_t c = TRY(context.add_const(sym));
|
||||||
|
|
||||||
|
uint64_t vr = context.alloc_reg();
|
||||||
|
TRY(ex_res.add_opcode(Oc::GlobalLoad, {0, (int64_t)vr}, {1, (int64_t)c}));
|
||||||
}
|
}
|
||||||
|
|
||||||
TRY(ex_res.add_opcode(Oc::MakeClosure, {0, (int64_t)reg},
|
TRY(ex_res.add_opcode(Oc::MakeClosure, {0, (int64_t)reg},
|
||||||
|
@ -780,8 +788,17 @@ Result<Expression> Compiler::compile_function_call(Context& context,
|
||||||
auto first = TRY(expr.first());
|
auto first = TRY(expr.first());
|
||||||
auto param = TRY(expr.rest());
|
auto param = TRY(expr.rest());
|
||||||
|
|
||||||
|
bool is_self_call = false;
|
||||||
|
uint64_t firstreg = context.maxreg;
|
||||||
|
if (first.is<Symbol>() && TRY(first.cmp(context.fname)) == 0) {
|
||||||
|
int64_t r = context.alloc_reg();
|
||||||
|
int64_t c = TRY(context.add_const(TRY(Nil::create())));
|
||||||
|
TRY(ex.add_opcode(Oc::Mov, {0, (int64_t)r}, {1, (int64_t)c}));
|
||||||
|
is_self_call = true;
|
||||||
|
} else {
|
||||||
auto fun_ex = TRY(compile_expr(context, first));
|
auto fun_ex = TRY(compile_expr(context, first));
|
||||||
TRY(ex.add_code(fun_ex.code));
|
TRY(ex.add_code(fun_ex.code));
|
||||||
|
}
|
||||||
|
|
||||||
int64_t maxreg = context.maxreg;
|
int64_t maxreg = context.maxreg;
|
||||||
while (!param.is<Nil>()) {
|
while (!param.is<Nil>()) {
|
||||||
|
@ -796,10 +813,15 @@ Result<Expression> Compiler::compile_function_call(Context& context,
|
||||||
param = TRY(param.rest());
|
param = TRY(param.rest());
|
||||||
}
|
}
|
||||||
|
|
||||||
TRY(ex.add_opcode(Oc::Call, {0, (int64_t)fun_ex.reg},
|
if (is_self_call) {
|
||||||
|
TRY(ex.add_opcode(Oc::SelfCall, {0, (int64_t)firstreg},
|
||||||
{0, (int64_t)context.maxreg}));
|
{0, (int64_t)context.maxreg}));
|
||||||
|
} else {
|
||||||
|
TRY(ex.add_opcode(Oc::Call, {0, (int64_t)firstreg},
|
||||||
|
{0, (int64_t)context.maxreg}));
|
||||||
|
}
|
||||||
|
|
||||||
ex.reg = fun_ex.reg;
|
ex.reg = firstreg;
|
||||||
context.maxreg = maxreg;
|
context.maxreg = maxreg;
|
||||||
return ex;
|
return ex;
|
||||||
}
|
}
|
||||||
|
@ -864,19 +886,6 @@ Result<Expression> Compiler::compile_symbol(Context& context,
|
||||||
return std::move(ex);
|
return std::move(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto maybe_closure = context.get_closure(TRY(value.copy()));
|
|
||||||
|
|
||||||
if (!maybe_closure.has_error()) {
|
|
||||||
auto var_closure = maybe_closure.value();
|
|
||||||
|
|
||||||
uint64_t reg = context.alloc_reg();
|
|
||||||
TRY(ex.add_opcode(Oc::ClosureLoad, {0, (int64_t)reg},
|
|
||||||
{0, (int64_t)var_closure}));
|
|
||||||
|
|
||||||
ex.reg = reg;
|
|
||||||
return std::move(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto maybe_stdlib_fun = get_stdlib_function(*value.to<Symbol>());
|
auto maybe_stdlib_fun = get_stdlib_function(*value.to<Symbol>());
|
||||||
|
|
||||||
if (!maybe_stdlib_fun.has_error()) {
|
if (!maybe_stdlib_fun.has_error()) {
|
||||||
|
@ -890,8 +899,25 @@ Result<Expression> Compiler::compile_symbol(Context& context,
|
||||||
return std::move(ex);
|
return std::move(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Most variable references in function bodies will fall back to loading the
|
||||||
|
// accessed variables from parent closures.
|
||||||
|
if (!context.toplevel) {
|
||||||
|
auto maybe_closure = context.get_closure(TRY(value.copy()));
|
||||||
|
|
||||||
|
if (!maybe_closure.has_error()) {
|
||||||
|
auto var_closure = maybe_closure.value();
|
||||||
|
|
||||||
|
uint64_t reg = context.alloc_reg();
|
||||||
|
TRY(ex.add_opcode(Oc::ClosureLoad, {0, (int64_t)reg},
|
||||||
|
{0, (int64_t)var_closure}));
|
||||||
|
|
||||||
|
ex.reg = reg;
|
||||||
|
return std::move(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Otherwise treat unknown symbol as a global and try to load it from the
|
// Otherwise treat unknown symbol as a global and try to load it from the
|
||||||
// global scope
|
// global scope. This usually happens in toplevel expressions.
|
||||||
|
|
||||||
int64_t c = TRY(context.add_const(TRY(value.copy())));
|
int64_t c = TRY(context.add_const(TRY(value.copy())));
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,8 @@ op_t get_op(Oc op) {
|
||||||
return op_t{"less-equal", OpcodeType::Reg2I};
|
return op_t{"less-equal", OpcodeType::Reg2I};
|
||||||
case Oc::Call:
|
case Oc::Call:
|
||||||
return op_t{"call", OpcodeType::Reg2};
|
return op_t{"call", OpcodeType::Reg2};
|
||||||
|
case Oc::SelfCall:
|
||||||
|
return op_t{"selfcall", OpcodeType::Reg2};
|
||||||
case Oc::TailCall:
|
case Oc::TailCall:
|
||||||
return op_t{"tailcall", OpcodeType::Reg1I};
|
return op_t{"tailcall", OpcodeType::Reg1I};
|
||||||
case Oc::Ret:
|
case Oc::Ret:
|
||||||
|
|
|
@ -33,6 +33,7 @@ enum class Oc : uint8_t {
|
||||||
LessEqual,
|
LessEqual,
|
||||||
// Function calls
|
// Function calls
|
||||||
Call,
|
Call,
|
||||||
|
SelfCall,
|
||||||
TailCall,
|
TailCall,
|
||||||
// Return from function call
|
// Return from function call
|
||||||
Ret,
|
Ret,
|
||||||
|
|
26
src/vm.cpp
26
src/vm.cpp
|
@ -156,6 +156,29 @@ Result<void> VM::vm_call(Opcode& oc) {
|
||||||
return ERROR(TypeMismatch);
|
return ERROR(TypeMismatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result<void> VM::vm_selfcall(Opcode& oc) {
|
||||||
|
uint64_t reg_start = (uint64_t)oc.arg1().arg;
|
||||||
|
uint64_t reg_end = (uint64_t)oc.arg2().arg;
|
||||||
|
|
||||||
|
if (_fun.arity() != (reg_end - reg_start - 1)) {
|
||||||
|
return ERROR(ArgumentCountMismatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t old_base = _base;
|
||||||
|
|
||||||
|
Value fun_val = TRY(_fun.copy());
|
||||||
|
Value oldbase_val = TRY(Int64::create(old_base));
|
||||||
|
Value pc_val = TRY(Int64::create(_pc));
|
||||||
|
_callstack.set(_callstack.gettop(), fun_val);
|
||||||
|
_callstack.set(_callstack.gettop(), oldbase_val);
|
||||||
|
_callstack.set(_callstack.gettop(), pc_val);
|
||||||
|
|
||||||
|
_pc = 0;
|
||||||
|
_base = _base + reg_start;
|
||||||
|
|
||||||
|
return Result<void>();
|
||||||
|
}
|
||||||
|
|
||||||
Result<void> VM::vm_ret(Opcode& oc) {
|
Result<void> VM::vm_ret(Opcode& oc) {
|
||||||
if (_callstack.gettop() == 0) {
|
if (_callstack.gettop() == 0) {
|
||||||
_res = TRY(getreg((uint64_t)oc.arg1().arg));
|
_res = TRY(getreg((uint64_t)oc.arg1().arg));
|
||||||
|
@ -267,6 +290,9 @@ Result<void> VM::step() {
|
||||||
case Oc::Call:
|
case Oc::Call:
|
||||||
TRY(vm_call(oc));
|
TRY(vm_call(oc));
|
||||||
break;
|
break;
|
||||||
|
case Oc::SelfCall:
|
||||||
|
TRY(vm_selfcall(oc));
|
||||||
|
break;
|
||||||
case Oc::MakeClosure:
|
case Oc::MakeClosure:
|
||||||
TRY(vm_make_closure(oc));
|
TRY(vm_make_closure(oc));
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -26,6 +26,7 @@ class VM {
|
||||||
Result<void> vm_sub(Opcode& oc);
|
Result<void> vm_sub(Opcode& oc);
|
||||||
Result<void> vm_div(Opcode& oc);
|
Result<void> vm_div(Opcode& oc);
|
||||||
Result<void> vm_call(Opcode& oc);
|
Result<void> vm_call(Opcode& oc);
|
||||||
|
Result<void> vm_selfcall(Opcode& oc);
|
||||||
Result<void> vm_call_lisp(Opcode& oc, Function& fun);
|
Result<void> vm_call_lisp(Opcode& oc, Function& fun);
|
||||||
Result<void> vm_call_stdlib(Opcode& oc, StdlibFunction& fun);
|
Result<void> vm_call_stdlib(Opcode& oc, StdlibFunction& fun);
|
||||||
Result<void> vm_ret(Opcode& oc);
|
Result<void> vm_ret(Opcode& oc);
|
||||||
|
|
Loading…
Reference in a new issue