diff --git a/compiler.rb b/compiler.rb index f09da08..3231640 100644 --- a/compiler.rb +++ b/compiler.rb @@ -35,7 +35,8 @@ class Compiler :assign, :while, :index, :bindex, :let, :case, :ternif, :hash, :return,:sexp, :module, :rescue, :incr, :block, :required, :add, :sub, :mul, :div, :eq, :ne, - :lt, :le, :gt, :ge,:saveregs, :and, :or + :lt, :le, :gt, :ge,:saveregs, :and, :or, + :preturn, :proc, :stackframe ] Keywords = @@keywords @@ -88,6 +89,16 @@ def intern(scope,sym) Value.new(get_arg(scope,[:sexp,[:call,:__get_symbol, sym.to_s]]),:object) end + # For our limited typing we will in some cases need to do proper lookup. + # For now, we just want to make %s(index __env__ xxx) mostly treated as + # objects, in order to ensure that variables accesses that gets rewritten + # to indirect via __env__ gets treated as object. The exception is + # for now __env__[0] which contains a stackframe pointer used by + # :preturn. + def lookup_type(var, index = nil) + (var == :__env__ && index != 0) ? :object : nil + end + # Returns an argument with its type identifier. # # If an Array is given, we have a subexpression, which needs to be compiled first. @@ -420,6 +431,44 @@ def compile_lambda(scope, args=nil, body=nil) end + def compile_stackframe(scope) + @e.comment("Stack frame") + Value.new([:reg,:ebp]) + end + + # "Special" return for `proc` and bare blocks + # to exit past Proc#call. + def compile_preturn(scope, arg = nil) + @e.comment("preturn") + + @e.save_result(compile_eval_arg(scope, arg)) if arg + @e.pushl(:eax) + + # We load the return address pre-saved in __stackframe__ on creation of the proc. + # __stackframe__ is automatically added to __env__ in `rewrite_let_env` + + ret = compile_eval_arg(scope,[:index,:__env__,0]) + + @e.movl(ret,:ebp) + @e.popl(:eax) + @e.leave + @e.ret + @e.evict_all + return Value.new([:subexpr]) + end + + # To compile `proc`, and anonymous blocks + # See also #compile_lambda + def compile_proc(scope, args=nil, body=nil) + e = @e.get_local + body ||= [] + args ||= [] + + r = compile_defun(scope, e, [:self,:__closure__]+args,[:let,[]]+body) + r + end + + # Compiles and evaluates a given argument within a given scope. def compile_eval_arg(scope, arg) if arg.respond_to?(:position) && arg.position != nil @@ -709,7 +758,7 @@ def compile_index(scope, arr, index) @e.popl(reg) @e.addl(@e.result_value, reg) end - return Value.new([:indirect, r]) + return Value.new([:indirect, r], lookup_type(arr,index)) end diff --git a/transform.rb b/transform.rb index 406ca30..58c4ad0 100644 --- a/transform.rb +++ b/transform.rb @@ -8,6 +8,29 @@ class Compiler include AST + # For 'bare' blocks, or "Proc" objects created with 'proc', we + # replace the standard return with ":preturn", which ensures the + # return is forced to exit the defining scope, instead of "just" + # exiting the block itself and then Proc#call. + # + # FIXME: Note that this does *not* attempt to detect an "escaped" + # block that is returning outside of where it should. At some point + # we need to add a way of handling this (e.g. MRI raises a LocalJumpError), + # but that is trickier to do in a sane way (one option would be + # to keep track of any blocks that get defined, and for any return + # from a scope that have defined this to mark the created "Proc" + # objets accordingly). + # + def rewrite_proc_return(exp) + exp.depth_first do |e| + next :skip if e[0] == :sexp + if e[0] == :return + e[0] = :preturn + end + end + exp + end + # This replaces the old lambda handling with a rewrite. # The advantage of handling it as a rewrite phase is that it's # much easier to debug - it can be turned on and off to @@ -15,17 +38,23 @@ class Compiler def rewrite_lambda(exp) exp.depth_first do |e| next :skip if e[0] == :sexp - if e[0] == :lambda + if e[0] == :lambda || e[0] == :proc args = e[1] || E[] body = e[2] || nil + + if e[0] == :proc && body + body = rewrite_proc_return(body) + end + e.clear e[0] = :do - e[1] = E[:assign, :__tmp_proc, + e[1] = E[:assign, [:index, :__env__,0], [:stackframe]] + e[2] = E[:assign, :__tmp_proc, E[:defun, @e.get_local, E[:self,:__closure__,:__env__]+args, body] ] - e[2] = E[exp.position,:sexp, E[:call, :__new_proc, E[:__tmp_proc, :__env__, :self]]] + e[3] = E[exp.position,:sexp, E[:call, :__new_proc, E[:__tmp_proc, :__env__, :self]]] end end end @@ -131,7 +160,7 @@ def find_vars(e, scopes, env, freq, in_lambda = false, in_assign = false) env = env1 + env2 vars = vars1+vars2 vars.each {|v| push_var(scopes,env,v) if !is_special_name?(v) } - elsif n[0] == :lambda + elsif n[0] == :lambda || n[0] == :proc vars, env = find_vars(n[2], scopes + [Set.new],env, freq, true) n[2] = E[n.position,:let, vars, *n[2]] if n[2] else @@ -146,6 +175,13 @@ def find_vars(e, scopes, env, freq, in_lambda = false, in_assign = false) env += env2 end end + + # If a block is provided, we need to find variables there too + if n[4] + vars3, env3 = find_vars([n[4]], scopes, env, freq, in_lambda) + vars += vars3 + env += env3 + end else if n[0] == :call sub = n[2..-1] @@ -212,17 +248,20 @@ def rewrite_let_env(exp) vars,env= find_vars(e[3],scopes,Set.new, freq) + # For "preturn". see Compiler#compile_preturn + aenv = [:__stackframe__] + env.to_a + env << :__stackframe__ + vars -= args.to_a if env.size > 0 body = e[3] - rewrite_env_vars(body, env.to_a) + rewrite_env_vars(body, aenv) notargs = env - Set[*e[2]] - aenv = env.to_a extra_assigns = (env - notargs).to_a.collect do |a| E[e.position,:assign, E[e.position,:index, :__env__, aenv.index(a)], a] end - e[3] = [E[:sexp,E[:assign, :__env__, E[:call, :malloc, [env.size * 4]]]]] + e[3] = [E[:sexp,E[:assign, :__env__, E[:call, :malloc, [aenv.size * 4]]]]] e[3].concat(extra_assigns) e[3].concat(body) end