Browse files

Implement real closures with upvalues and scope

  • Loading branch information...
1 parent 1f6f222 commit fc06139d0d4ead7334a789635298216b35f6c1b0 @txus committed Dec 30, 2012
View
3 README.md
@@ -210,6 +210,9 @@ That's it! :)
* **PUSHLOCAL A**: pushes the local at index `A` to the stack.
* **SETLOCAL A**: sets the current top of the stack to the local variable `A`. Does not consume any stack.
+* **PUSHLOCALDEPTH A, B**: pushes the local at index `B` from an enclosing scope
+ (at depth `A`) to the stack.
+* **SETLOCALDEPTH A, B**: sets the current top of the stack to the local variable `B` in an enclosing scope (at depth `A`). Does not consume any stack.
#### Branching
View
BIN bin/tvm
Binary file not shown.
View
8 compiler/examples/functions.rb
@@ -1 +1,7 @@
-puts -> a { a + 3 }.apply(nil, 123)
+a = 123
+foo = 123
+self.fn = -> foo {
+ # foo is shadowed because it's a local argument
+ a + foo
+}
+puts fn(2) # => 125, not 246
View
39 compiler/lib/terror/generator.rb
@@ -1,16 +1,25 @@
require 'terror/instructions'
+require 'terror/scope'
require 'terror/branching'
module Terror
class Generator
include Instructions
include Branching
- attr_reader :literals, :locals, :ip
-
- Local = Struct.new(:name)
+ attr_reader :literals, :scope, :ip, :parent
+ attr_accessor :children
+
+ def initialize(parent=nil)
+ @children = []
+ @parent = parent
+
+ if parent
+ parent.children << self
+ @scope = Scope.new(parent.scope)
+ else
+ @scope = Scope.new
+ end
- def initialize
- @locals = []
@literals = []
@ip = 0
end
@@ -86,13 +95,22 @@ def pushself
def pushlocal(name)
@ip += 1
- _pushlocal local(name)
+ l = local(name)
+ if l.depth > 0
+ _pushlocaldepth l.depth, l.index
+ else
+ _pushlocal l.index
+ end
end
def setlocal(name)
@ip += 1
- idx = local(name)
- _setlocal idx
+ l = local(name)
+ if l.depth > 0
+ _setlocaldepth l.depth, l.index
+ else
+ _setlocal l.index
+ end
end
def getslot(name)
@@ -128,10 +146,7 @@ def ret
private
def local name
- @locals.index { |l| l.name == name } or begin
- @locals.push Local.new name
- @locals.size - 1
- end
+ @scope.search_local(name)
end
def literal value
View
6 compiler/lib/terror/instructions.rb
@@ -116,8 +116,10 @@ def emit(instruction)
op :pushfalse, 0x13, 0
op :pushnil, 0x14, 0
- op :pushlocal, 0x20, 1
- op :setlocal, 0x21, 1
+ op :pushlocal, 0x20, 1
+ op :setlocal, 0x21, 1
+ op :pushlocaldepth, 0x22, 2
+ op :setlocaldepth, 0x23, 2
op :jmp, 0x30, 1
op :jif, 0x31, 1
View
35 compiler/lib/terror/scope.rb
@@ -0,0 +1,35 @@
+module Terror
+ class Local
+ attr_reader :name, :depth, :index
+ def initialize(name, index=0, depth=0)
+ @name = name
+ @depth = depth
+ @index = index
+ end
+ end
+
+ class Scope
+ attr_reader :parent, :locals
+ def initialize(parent=nil)
+ @parent = parent
+ @locals = []
+ end
+
+ def search_local(name)
+ # Search in current scope
+ if idx = @locals.index { |l| l == name }
+ Local.new(name, idx, 0)
+ # Search in parent scope
+ elsif @parent and local = @parent.search_local(name)
+ Local.new(local.name, local.index, local.depth + 1)
+ else
+ new_local(name)
+ end
+ end
+
+ def new_local(name)
+ @locals.push name
+ Local.new(name, @locals.size - 1, 0)
+ end
+ end
+end
View
10 compiler/lib/terror/visitor.rb
@@ -4,8 +4,8 @@ module Terror
class Visitor
attr_reader :generator
- def initialize(g=Generator.new)
- @generator = g
+ def initialize(parent=nil)
+ @generator = Generator.new(parent)
@slots = {}
@fns = []
end
@@ -68,7 +68,11 @@ def send(node, parent)
end
def defn(block)
- visitor = Visitor.new
+ visitor = Visitor.new(g)
+ block.arguments.names.each do |arg|
+ # vivify arguments as locals
+ visitor.g.scope.new_local(arg)
+ end
block.body.lazy_visit(visitor)
@fns << visitor
g.defn visitor.name
View
28 compiler/test/terror/generator_test.rb
@@ -70,21 +70,39 @@ module Terror
end
end
-
-
describe '#pushlocal' do
it 'pushes a local variable' do
@g.pushlocal :foo
- @g.locals.first.name.must_equal :foo
+ @g.scope.locals.first.must_equal :foo
end
end
describe '#setlocal' do
it 'sets a local variable' do
@g.push 42
@g.setlocal :foo
- local = @g.locals[0]
- local.name.must_equal :foo
+ @g.scope.locals.first.must_equal :foo
+ end
+ end
+
+ describe '#pushlocaldepth' do
+ it 'pushes a local variable from an enclosing scope' do
+ @g.scope.search_local(:foo) # vivify the local in the parent scope
+ g = Generator.new(@g)
+ g.pushlocal :foo
+ g.scope.locals.first.must_equal nil
+ @g.scope.locals.first.must_equal :foo
+ end
+ end
+
+ describe '#setlocaldepth' do
+ it 'sets a local variable in an enclosing scope' do
+ @g.scope.search_local(:foo) # vivify the local in the parent scope
+ g = Generator.new(@g)
+ g.push 42
+ g.setlocal :foo
+ g.scope.locals.first.must_equal nil
+ @g.scope.locals.first.must_equal :foo
end
end
View
2 compiler/test/terror/instructions_test.rb
@@ -9,7 +9,7 @@ module Terror
end
describe 'instructions with 2 operands' do
- %w(send).each do |instruction|
+ %w(send pushlocaldepth setlocaldepth).each do |instruction|
describe instruction do
it "#{instruction} is encoded correctly" do
inst = @g.__send__ :"_#{instruction}", 2, 3
View
47 compiler/test/terror/scope_test.rb
@@ -0,0 +1,47 @@
+$: << 'lib'
+require 'terror/scope'
+require 'minitest/autorun'
+
+module Terror
+ describe Scope do
+ before do
+ @s = Scope.new
+ end
+
+ describe '#search_local' do
+ describe 'when the local exists in the current scope' do
+ it 'returns it' do
+ @s.instance_variable_set(:@locals, [:foo, :blah])
+ local = @s.search_local(:blah)
+ local.name.must_equal :blah
+ local.index.must_equal 1
+ local.depth.must_equal 0
+ end
+ end
+
+ describe 'when the local exists in a parent scope' do
+ it 'returns it with the correct depth' do
+ @s.instance_variable_set(:@locals, [:foo, :blah])
+ child = Scope.new(@s)
+ subchild = Scope.new(child)
+
+ local = subchild.search_local(:blah)
+ local.name.must_equal :blah
+ local.index.must_equal 1
+ local.depth.must_equal 2
+ end
+ end
+
+ describe 'when the local does not exist' do
+ it 'vivifies it in the current scope' do
+ local = @s.search_local(:foo)
+ local.name.must_equal :foo
+ local.index.must_equal 0
+ local.depth.must_equal 0
+
+ @s.locals.first.must_equal :foo
+ end
+ end
+ end
+ end
+end
View
21 compiler/test/terror/visitor_test.rb
@@ -19,6 +19,12 @@ def compiles(code, &block)
visit(code).instructions.must_equal g.instructions
end
+ def compiles_block(code, &block)
+ g = Generator.new
+ g.instance_eval(&block)
+ visit(code).children.first.instructions.must_equal g.instructions
+ end
+
it 'assigns local variables' do
compiles("a = 3; b = 3; c = 4") do
_push 0
@@ -144,6 +150,21 @@ def compiles(code, &block)
_setlocal 0
end
end
+
+ it 'are compiled with arguments' do
+ compiles("bar = 1; a = -> foo { bar }") do
+ _push 0
+ _setlocal 0
+ _defn 1
+ _setlocal 1
+ end
+
+ compiles_block("bar = 1; a = -> foo { foo + bar }") do
+ _pushlocal 0
+ _pushlocaldepth 1, 0
+ _send 0, 1
+ end
+ end
end
describe 'vectors' do
View
40 examples/functions.tvm
@@ -1,33 +1,45 @@
_main
-:4:14
-"block_508
+:5:25
123
-"apply
+"block_524
+"fn
+2
"puts
-16 PUSHSELF
-67 DEFN
+17 PUSH
+0
+33 SETLOCAL
0
-20 PUSHNIL
17 PUSH
+0
+33 SETLOCAL
1
-128 SEND
+16 PUSHSELF
+67 DEFN
+1
+65 SETSLOT
2
+16 PUSHSELF
+16 PUSHSELF
+17 PUSH
+3
+128 SEND
2
+1
128 SEND
-3
+4
1
20 PUSHNIL
144 RET
-_block_508
-:2:8
-3
+_block_524
+:1:9
"+
-32 PUSHLOCAL
+34 PUSHLOCALDEPTH
+1
0
-17 PUSH
+32 PUSHLOCAL
0
128 SEND
-1
+0
1
144 RET
View
2 src/terror/bootstrap.c
@@ -29,7 +29,7 @@ kernel_files()
return entries;
}
-#define DEFPRIM(M, N, F) DArray_push((M), String_new((N))); DArray_push((M), Closure_new(Function_native_new((F))))
+#define DEFPRIM(M, N, F) DArray_push((M), String_new((N))); DArray_push((M), Closure_new(Function_native_new((F)), NULL))
#define DEFVALUE(M, N, V) DArray_push((M), String_new((N))); DArray_push((M), (V));
static inline void
View
42 src/terror/call_frame.c
@@ -1,4 +1,5 @@
#include <stdlib.h>
+#include <terror/value.h>
#include <terror/call_frame.h>
CallFrame*
@@ -9,5 +10,46 @@ CallFrame_new(VALUE self, Function *fn, int *ret)
cf->fn = fn;
cf->ret = ret;
cf->locals = DArray_create(sizeof(VALUE), 10);
+ cf->parent = NULL;
return cf;
}
+
+VALUE
+CallFrame_getlocal(CallFrame *frame, int idx)
+{
+ VALUE local = (VALUE)DArray_at(frame->locals, idx);
+ check(local, "Couldn't retrieve local at index %i.", idx);
+ return local;
+error:
+ return NULL;
+}
+
+void
+CallFrame_setlocal(CallFrame *frame, int idx, VALUE value)
+{
+ DArray_set(frame->locals, idx, value);
+}
+
+VALUE
+CallFrame_getlocaldepth(CallFrame *frame, int depth, int idx)
+{
+ while (depth--) {
+ frame = frame->parent;
+ check(frame, "Tried to get local at too much depth: %i.", depth);
+ }
+ return CallFrame_getlocal(frame, idx);
+error:
+ return NULL;
+}
+
+void
+CallFrame_setlocaldepth(CallFrame *frame, int depth, int idx, VALUE value)
+{
+ while (depth--) {
+ frame = frame->parent;
+ check(frame, "Tried to set local at too much depth: %i.", depth);
+ }
+ return CallFrame_setlocal(frame, idx, value);
+error:
+ debug("Error.");
+}
View
13 src/terror/call_frame.h
@@ -1,19 +1,26 @@
#ifndef _tvm_call_frame_h_
#define _tvm_call_frame_h_
-#include <terror/value.h>
#include <terror/darray.h>
#include <terror/function.h>
+struct val_s;
+
struct call_frame_s {
- VALUE self;
+ struct val_s *self;
DArray *locals;
Function *fn;
int *ret;
+ struct call_frame_s *parent;
};
typedef struct call_frame_s CallFrame;
-CallFrame* CallFrame_new(VALUE self, Function *fn, int *ret);
+CallFrame* CallFrame_new(struct val_s* self, Function *fn, int *ret);
+
+struct val_s* CallFrame_getlocal(CallFrame *frame, int idx);
+void CallFrame_setlocal(CallFrame *frame, int idx, struct val_s* value);
+struct val_s* CallFrame_getlocaldepth(CallFrame *frame, int depth, int idx);
+void CallFrame_setlocaldepth(CallFrame *frame, int depth, int idx, struct val_s* value);
#endif
View
8 src/terror/function.c
@@ -10,6 +10,7 @@ Function_new(int *code, DArray *literals)
Function *fn = calloc(1, sizeof(Function));
fn->code = code;
fn->literals = literals;
+ fn->scope = NULL;
fn->c_fn = NULL;
return fn;
}
@@ -20,6 +21,7 @@ Function_native_new(native_fn c_fn)
Function *fn = calloc(1, sizeof(Function));
fn->code = NULL;
fn->literals = NULL;
+ fn->scope = NULL;
fn->c_fn = c_fn;
return fn;
}
@@ -54,6 +56,12 @@ Function_call(
// Normal dispatch
CallFrame *new_frame = CallFrame_new(receiver, fn, ret);
new_frame->locals = locals;
+
+ // If it is a closure, we nest the call frames.
+ if(fn->scope) {
+ new_frame->parent = fn->scope;
+ }
+
Stack_push(FRAMES, new_frame);
return new_frame->fn->code;
View
2 src/terror/function.h
@@ -6,11 +6,13 @@
struct state_s;
struct val_s;
+struct call_frame_s;
typedef struct val_s* (*native_fn)(struct state_s*, void*, void*, void*);
struct function_s {
int *code;
DArray *literals;
+ struct call_frame_s *scope;
native_fn c_fn;
};
typedef struct function_s Function;
View
2 src/terror/opcode.h
@@ -12,6 +12,8 @@ typedef enum {
PUSHLOCAL = 0x20, // 32
SETLOCAL, // 33
+ PUSHLOCALDEPTH, // 34
+ SETLOCALDEPTH, // 35
JMP = 0x30, // 48
JIF, // 49
View
2 src/terror/runtime.c
@@ -16,7 +16,7 @@ VALUE TrueObject = NULL;
VALUE FalseObject = NULL;
VALUE NilObject = NULL;
-#define DEFNATIVE(V, N, F) Value_set((V), (N), Closure_new(Function_native_new((F))))
+#define DEFNATIVE(V, N, F) Value_set((V), (N), Closure_new(Function_native_new((F)), NULL))
void Runtime_init() {
Object_bp = Value_new(ObjectType);
View
4 src/terror/value.c
@@ -3,6 +3,7 @@
#include <terror/bstrlib.h>
#include <terror/runtime.h>
#include <terror/primitives.h>
+#include <terror/call_frame.h>
#include <terror/gc.h>
VALUE Object_bp;
@@ -148,9 +149,10 @@ String_new(char* value)
}
VALUE
-Closure_new(Function *fn)
+Closure_new(Function *fn, CallFrame *scope)
{
VALUE val = Value_from_prototype(ClosureType, Closure_bp);
+ fn->scope = scope;
val->data.as_data = fn;
return val;
}
View
3 src/terror/value.h
@@ -4,6 +4,7 @@
#include <terror/value_type.h>
#include <terror/gc_header.h>
#include <terror/function.h>
+#include <terror/call_frame.h>
#include <terror/hashmap.h>
struct val_s {
@@ -34,7 +35,7 @@ VALUE Number_new(double);
VALUE String_new(char*);
#define VAL2STR(o) (o->data.as_str)
-VALUE Closure_new(Function*);
+VALUE Closure_new(Function*, CallFrame *scope);
#define VAL2FN(o) ((Function*)(o->data.as_data))
VALUE Vector_new(DArray *array);
View
24 src/terror/vm.c
@@ -32,8 +32,10 @@ static inline void dump(Stack* stack)
#define STATE_FN(A) (Function*)Hashmap_get(state->functions, bfromcstr((A)))
#define LITERAL(A) (VALUE)DArray_at(CURR_FRAME->fn->literals, (A))
-#define LOCAL(A) (VALUE)DArray_at(CURR_FRAME->locals, (A))
-#define LOCALSET(A, B) DArray_set(CURR_FRAME->locals, (A), (B))
+#define LOCAL(A) CallFrame_getlocal(CURR_FRAME, (A))
+#define LOCALSET(A, B) CallFrame_setlocal(CURR_FRAME, (A), (B))
+#define DEEPLOCAL(D, A) CallFrame_getlocaldepth(CURR_FRAME, (D), (A))
+#define DEEPLOCALSET(D, A, B) CallFrame_setlocaldepth(CURR_FRAME, (D), (A), (B))
void VM_start(bstring filename)
{
@@ -158,7 +160,7 @@ VALUE VM_run(STATE)
ip++;
debug("DEFN %i", *ip);
VALUE fn_name = LITERAL(*ip);
- VALUE closure = Closure_new(STATE_FN(VAL2STR(fn_name)));
+ VALUE closure = Closure_new(STATE_FN(VAL2STR(fn_name)), CURR_FRAME);
Stack_push(STACK, closure);
break;
}
@@ -249,12 +251,28 @@ VALUE VM_run(STATE)
debug("PUSHLOCAL %i", *ip);
break;
}
+ case PUSHLOCALDEPTH: {
+ ip++;
+ int depth = *ip;
+ ip++;
+ Stack_push(STACK, DEEPLOCAL(depth, *ip));
+ debug("PUSHLOCALDEPTH %i %i", depth, *ip);
+ break;
+ }
case SETLOCAL: {
ip++;
debug("SETLOCAL %i", *ip);
LOCALSET(*ip, Stack_peek(STACK));
break;
}
+ case SETLOCALDEPTH: {
+ ip++;
+ int depth = *ip;
+ ip++;
+ debug("SETLOCAL %i %i", depth, *ip);
+ DEEPLOCALSET(depth, *ip, Stack_peek(STACK));
+ break;
+ }
case POP: {
debug("POP");
Stack_pop(STACK);
View
91 tests/call_frame_tests.c
@@ -0,0 +1,91 @@
+#include "minunit.h"
+#include <terror/value.h>
+#include <terror/call_frame.h>
+#include <assert.h>
+
+static inline CallFrame*
+fixture()
+{
+ return CallFrame_new(NULL, NULL, NULL);
+}
+
+#define PUSH(F, L) DArray_push((F)->locals, (L))
+
+char *test_getlocal()
+{
+ VALUE one = Number_new(1);
+ VALUE two = Number_new(2);
+
+ CallFrame *frame = fixture();
+
+ PUSH(frame, one);
+ PUSH(frame, two);
+
+ mu_assert(CallFrame_getlocal(frame, 1) == two, "Getlocal failed.");
+
+ return NULL;
+}
+
+char *test_setlocal()
+{
+ VALUE one = Number_new(1);
+ VALUE two = Number_new(2);
+
+ CallFrame *frame = fixture();
+
+ PUSH(frame, one);
+
+ CallFrame_setlocal(frame, 1, two);
+
+ mu_assert(CallFrame_getlocal(frame, 1) == two, "Setlocal failed.");
+
+ return NULL;
+}
+
+char *test_getlocaldepth()
+{
+ VALUE one = Number_new(1);
+ VALUE two = Number_new(2);
+
+ CallFrame *parent = fixture();
+ PUSH(parent, one);
+ PUSH(parent, two);
+
+ CallFrame *frame = fixture();
+ frame->parent = parent;
+
+ mu_assert(CallFrame_getlocaldepth(frame, 1, 0) == one, "Getlocaldepth failed");
+
+ return NULL;
+}
+
+char *test_setlocaldepth()
+{
+ VALUE one = Number_new(1);
+ VALUE two = Number_new(2);
+
+ CallFrame *parent = fixture();
+ PUSH(parent, one);
+
+ CallFrame *frame = fixture();
+ frame->parent = parent;
+
+ CallFrame_setlocaldepth(frame, 1, 1, two);
+
+ mu_assert(CallFrame_getlocal(parent, 1) == two, "Setlocaldepth failed");
+
+ return NULL;
+}
+
+char *all_tests() {
+ mu_suite_start();
+
+ mu_run_test(test_getlocal);
+ mu_run_test(test_setlocal);
+ mu_run_test(test_getlocaldepth);
+ mu_run_test(test_setlocaldepth);
+
+ return NULL;
+}
+
+RUN_TESTS(all_tests);
View
2 tests/value_tests.c
@@ -25,7 +25,7 @@ char *test_string_new()
char *test_closure_new()
{
Function *fn = Function_new(NULL, NULL);
- VALUE closure = Closure_new(fn);
+ VALUE closure = Closure_new(fn, NULL);
mu_assert(closure->type == ClosureType, "failed creating closure");
mu_assert(VAL2FN(closure) == fn, "failed assigning function to closure");
View
37 tests/vm_tests.c
@@ -142,6 +142,41 @@ char *test_pushlocal()
TEARDOWN();
}
+char *test_pushlocaldepth()
+{
+ SETUP();
+
+ DEFN(
+ "foo",
+ PUSHLOCALDEPTH, 1, 0,
+ RET
+ );
+
+ PUSH_LITERAL(Number_new(123), integer);
+ PUSH_LITERAL(String_new("foo"), method);
+ PUSH_LITERAL(String_new("fn"), function);
+
+ // a = 123
+ // self.fn = -> { a }
+ // fn()
+ RUN(
+ PUSH, 0,
+ SETLOCAL, 0,
+
+ PUSHSELF,
+ DEFN, 1,
+ SETSLOT, 2,
+
+ PUSHSELF,
+ SEND, 2, 0,
+ RET
+ );
+
+ mu_assert(result == integer, "Pushlocaldepth failed.");
+
+ TEARDOWN();
+}
+
char *test_setlocal()
{
SETUP();
@@ -400,6 +435,8 @@ char *all_tests() {
mu_run_test(test_pushnil);
mu_run_test(test_pushlocal);
mu_run_test(test_setlocal);
+ mu_run_test(test_pushlocaldepth);
+ /* mu_run_test(test_setlocaldepth); */
mu_run_test(test_jmp);
mu_run_test(test_jif);
mu_run_test(test_jit);

0 comments on commit fc06139

Please sign in to comment.