Skip to content

Commit

Permalink
Handling 'proc' and associated machinery for multi-level exit
Browse files Browse the repository at this point in the history
  • Loading branch information
vidarh committed Sep 9, 2014
1 parent a93f95e commit e39ae72
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 9 deletions.
53 changes: 51 additions & 2 deletions compiler.rb
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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


Expand Down
53 changes: 46 additions & 7 deletions transform.rb
Expand Up @@ -8,24 +8,53 @@
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
# see how the code gets transformed.
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
Expand Down Expand Up @@ -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
Expand All @@ -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]
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit e39ae72

Please sign in to comment.