From d5fb25046e06430ec23ee83ec93d72c5bb711440 Mon Sep 17 00:00:00 2001 From: bog Date: Tue, 12 Sep 2023 14:32:14 +0200 Subject: [PATCH] ADD: closure capture direct parent environment. --- examples/fun.gri | 22 ++++++++++++++++++++++ lib/core.cpp | 10 +++++++++- src/Compiler.cpp | 8 ++++++-- src/Function.cpp | 11 +++++++++++ src/Function.hpp | 4 ++++ src/VM.cpp | 27 +++++++++++++++++++++++++++ src/VM.hpp | 1 + src/Value.cpp | 22 ++++++++++++++++++++++ src/Value.hpp | 3 +++ src/opcodes.hpp | 2 ++ 10 files changed, 107 insertions(+), 3 deletions(-) diff --git a/examples/fun.gri b/examples/fun.gri index ea31b0b..ce3b68e 100644 --- a/examples/fun.gri +++ b/examples/fun.gri @@ -50,3 +50,25 @@ (* 3 (- n 2)))) (assert-eq? 15 ( (h) 7 )) + +;; closure +($ (i init) + ($ counter (- init 1)) + (-> () + (set! counter (+ counter 1)) + counter)) + +($ j (i 0)) +($ k (i 16)) + +(assert-eq? 0 (j)) +(assert-eq? 16 (k)) +(assert-eq? 17 (k)) +(assert-eq? 1 (j)) +(assert-eq? 2 (j)) +(assert-eq? 18 (k)) +(assert-eq? 19 (k)) +(assert-eq? 3 (j)) +(assert-eq? 4 (j)) +(assert-eq? 5 (j)) +(assert-eq? 20 (k)) \ No newline at end of file diff --git a/lib/core.cpp b/lib/core.cpp index e67810e..ac1a613 100644 --- a/lib/core.cpp +++ b/lib/core.cpp @@ -358,7 +358,15 @@ extern "C" void lib(grino::Loader& loader) if (entry) { compiler.compile(expr, program, sym); - program.push_instr(grino::OPCODE_STORE_LOCAL, entry->addr); + + if (entry->scope == compiler.scope()-1) + { + program.push_instr(grino::OPCODE_STORE_CLOSURE, entry->addr); + } + else + { + program.push_instr(grino::OPCODE_STORE_LOCAL, entry->addr); + } } program.push_value(grino::Value::make_nil(node->loc())); diff --git a/src/Compiler.cpp b/src/Compiler.cpp index 5bc2436..677d2eb 100644 --- a/src/Compiler.cpp +++ b/src/Compiler.cpp @@ -157,8 +157,12 @@ namespace grino ss << "undefined variable '" << ident << "'"; m_logger.log(LOG_ERROR, node->loc(), ss.str()); } - - if (entry->is_object) + else if (entry->is_object == false + && entry->scope < scope()) + { + program.push_instr(OPCODE_LOAD_CLOSURE, entry->addr); + } + else if (entry->is_object) { program.push_instr(OPCODE_LOAD_OBJ, entry->addr); } diff --git a/src/Function.cpp b/src/Function.cpp index 37d40f7..e4d7638 100644 --- a/src/Function.cpp +++ b/src/Function.cpp @@ -33,4 +33,15 @@ namespace grino assert(m_native); return m_native(args); } + + std::shared_ptr Function::env(size_t addr) const + { + return m_env.at(addr); + } + + void Function::set_env(size_t addr, std::shared_ptr value) + { + m_env[addr] = value; + } + } diff --git a/src/Function.hpp b/src/Function.hpp index 8b71d55..326ee0d 100644 --- a/src/Function.hpp +++ b/src/Function.hpp @@ -23,9 +23,13 @@ namespace grino std::shared_ptr program() const; value_t call(args_t args); + std::shared_ptr env(size_t addr) const; + void set_env(size_t addr, std::shared_ptr value); + private: native_t m_native; std::shared_ptr m_prog; + std::unordered_map> m_env; }; } diff --git a/src/VM.cpp b/src/VM.cpp index 4d432a1..b6c9dfa 100644 --- a/src/VM.cpp +++ b/src/VM.cpp @@ -10,6 +10,7 @@ namespace grino Frame frame; frame.pc = m_pc; frame.sp = m_sp; + frame.function = nullptr; frame.program = &program; m_frames.push_back(frame); } @@ -30,10 +31,35 @@ namespace grino switch (instr.opcode) { + case OPCODE_LOAD_CLOSURE: { + auto val = m_frames.back().function->env(*instr.param); + push(program().push_constant(val)); + m_pc++; + } break; + + case OPCODE_STORE_CLOSURE: { + auto val = program().constant(pop()); + m_frames.back().function->set_env(*instr.param, val); + m_pc++; + } break; + case OPCODE_MK_FUN: { auto prog_val = program().constant(pop()); auto prog = prog_val->as_program(); + auto fun_val = Value::make_function(prog_val->loc(), prog); + + // closure + if (m_frames.size() > 0) + { + for (auto const& entry: m_frames.at(m_frames.size() - 1).locals) + { + fun_val->as_function() + ->set_env(entry.first, + Value::make_copy(fun_val->loc(), entry.second)); + } + } + size_t addr = m_heap.size(); set_heap(addr, fun_val); @@ -142,6 +168,7 @@ namespace grino Frame frame; frame.pc = m_pc; frame.sp = m_sp; + frame.function = fun; frame.program = fun->program().get(); m_frames.push_back(frame); diff --git a/src/VM.hpp b/src/VM.hpp index 9512432..92767ea 100644 --- a/src/VM.hpp +++ b/src/VM.hpp @@ -15,6 +15,7 @@ namespace grino size_t pc; size_t sp; Program* program; + std::shared_ptr function; std::unordered_map> locals; }; diff --git a/src/Value.cpp b/src/Value.cpp index 17307e2..83c2089 100644 --- a/src/Value.cpp +++ b/src/Value.cpp @@ -64,6 +64,28 @@ namespace grino return value; } + /*static*/ + std::shared_ptr Value::make_copy(Loc const& loc, + std::shared_ptr val) + { + switch (val->type()) + { + case TYPE_INT: { + return Value::make_int(loc, val->as_int()); + } break; + + case TYPE_REF: { + return Value::make_ref(loc, val->as_ref()); + } break; + + default: + std::cerr << "cannot copy unknown value " << + TypeTypeStr[val->type()] << std::endl; + abort(); + } + } + + std::shared_ptr Value::as_program() const { return m_program_val; diff --git a/src/Value.hpp b/src/Value.hpp index 95ef1c4..1680582 100644 --- a/src/Value.hpp +++ b/src/Value.hpp @@ -26,6 +26,9 @@ namespace grino static std::shared_ptr make_program(Loc const& loc, std::shared_ptr val); + static std::shared_ptr make_copy(Loc const& loc, + std::shared_ptr val); + explicit Value(Loc const& loc); virtual ~Value() = default; diff --git a/src/opcodes.hpp b/src/opcodes.hpp index d852cae..6548762 100644 --- a/src/opcodes.hpp +++ b/src/opcodes.hpp @@ -11,6 +11,8 @@ G(OPCODE_STORE_LOCAL), \ G(OPCODE_LOAD_OBJ), \ G(OPCODE_STORE_OBJ), \ + G(OPCODE_LOAD_CLOSURE), \ + G(OPCODE_STORE_CLOSURE), \ G(OPCODE_CALL), \ G(OPCODE_BRF), \ G(OPCODE_BR), \