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 {
|
||||
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&& globals_dict, Context* parent, bool toplevel)
|
||||
: env(std::move(env)),
|
||||
: fname(std::move(fname)),
|
||||
constants(std::move(constants)),
|
||||
constants_dict(std::move(constants_dict)),
|
||||
variables_dict(std::move(variables_dict)),
|
||||
|
@ -21,7 +21,7 @@ struct Context {
|
|||
toplevel(toplevel) {}
|
||||
|
||||
static Result<Context> create() {
|
||||
auto env = TRY(Nil::create());
|
||||
auto fname = TRY(Nil::create());
|
||||
auto constants = TRY(Array::create());
|
||||
auto constants_dict = TRY(Dict::create());
|
||||
auto variables_dict = TRY(Dict::create());
|
||||
|
@ -29,14 +29,14 @@ struct Context {
|
|||
auto closures_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(closures), std::move(closures_dict),
|
||||
std::move(globals_dict), 0, true);
|
||||
}
|
||||
|
||||
static Result<Context> create(Context& parent) {
|
||||
auto env = TRY(Nil::create());
|
||||
auto fname = TRY(Nil::create());
|
||||
auto constants = TRY(Array::create());
|
||||
auto constants_dict = TRY(Dict::create());
|
||||
auto variables_dict = TRY(Dict::create());
|
||||
|
@ -44,7 +44,7 @@ struct Context {
|
|||
auto closures_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(closures), std::move(closures_dict),
|
||||
std::move(globals_dict), &parent, false);
|
||||
|
@ -123,14 +123,18 @@ struct Context {
|
|||
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);
|
||||
// If parent has no variable with name <sym>, then we need to propagate the
|
||||
// closure to it. The variable may be defined somewhere in outer scope.
|
||||
// If parent has no variable with name <sym>, then we need to propagate
|
||||
// the closure to it. The variable may be defined somewhere in outer
|
||||
// scope.
|
||||
if (!maybe_var.has_value()) {
|
||||
TRY(parent->get_closure(sym));
|
||||
}
|
||||
}
|
||||
|
||||
int64_t i = closures.size();
|
||||
closures = TRY(closures.append(sym));
|
||||
|
@ -144,7 +148,7 @@ struct Context {
|
|||
return Result<void>();
|
||||
}
|
||||
|
||||
Value env;
|
||||
Value fname;
|
||||
Array constants;
|
||||
Dict constants_dict;
|
||||
Dict variables_dict;
|
||||
|
@ -580,6 +584,7 @@ Result<Expression> Compiler::compile_fn(Context& context, Symbol& op,
|
|||
|
||||
if (maybe_name.is<Symbol>()) {
|
||||
name = TRY(maybe_name.copy());
|
||||
ctx.fname = TRY(name.copy());
|
||||
|
||||
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
|
||||
// defined in the outermost function.
|
||||
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();
|
||||
uint64_t vr = context.alloc_reg();
|
||||
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;
|
||||
}
|
||||
|
||||
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},
|
||||
|
@ -780,8 +788,17 @@ Result<Expression> Compiler::compile_function_call(Context& context,
|
|||
auto first = TRY(expr.first());
|
||||
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));
|
||||
TRY(ex.add_code(fun_ex.code));
|
||||
}
|
||||
|
||||
int64_t maxreg = context.maxreg;
|
||||
while (!param.is<Nil>()) {
|
||||
|
@ -796,10 +813,15 @@ Result<Expression> Compiler::compile_function_call(Context& context,
|
|||
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}));
|
||||
} 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;
|
||||
return ex;
|
||||
}
|
||||
|
@ -864,19 +886,6 @@ Result<Expression> Compiler::compile_symbol(Context& context,
|
|||
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>());
|
||||
|
||||
if (!maybe_stdlib_fun.has_error()) {
|
||||
|
@ -890,8 +899,25 @@ Result<Expression> Compiler::compile_symbol(Context& context,
|
|||
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
|
||||
// global scope
|
||||
// global scope. This usually happens in toplevel expressions.
|
||||
|
||||
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};
|
||||
case Oc::Call:
|
||||
return op_t{"call", OpcodeType::Reg2};
|
||||
case Oc::SelfCall:
|
||||
return op_t{"selfcall", OpcodeType::Reg2};
|
||||
case Oc::TailCall:
|
||||
return op_t{"tailcall", OpcodeType::Reg1I};
|
||||
case Oc::Ret:
|
||||
|
|
|
@ -33,6 +33,7 @@ enum class Oc : uint8_t {
|
|||
LessEqual,
|
||||
// Function calls
|
||||
Call,
|
||||
SelfCall,
|
||||
TailCall,
|
||||
// Return from function call
|
||||
Ret,
|
||||
|
|
26
src/vm.cpp
26
src/vm.cpp
|
@ -156,6 +156,29 @@ Result<void> VM::vm_call(Opcode& oc) {
|
|||
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) {
|
||||
if (_callstack.gettop() == 0) {
|
||||
_res = TRY(getreg((uint64_t)oc.arg1().arg));
|
||||
|
@ -267,6 +290,9 @@ Result<void> VM::step() {
|
|||
case Oc::Call:
|
||||
TRY(vm_call(oc));
|
||||
break;
|
||||
case Oc::SelfCall:
|
||||
TRY(vm_selfcall(oc));
|
||||
break;
|
||||
case Oc::MakeClosure:
|
||||
TRY(vm_make_closure(oc));
|
||||
break;
|
||||
|
|
|
@ -26,6 +26,7 @@ class VM {
|
|||
Result<void> vm_sub(Opcode& oc);
|
||||
Result<void> vm_div(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_stdlib(Opcode& oc, StdlibFunction& fun);
|
||||
Result<void> vm_ret(Opcode& oc);
|
||||
|
|
Loading…
Reference in a new issue