-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathcompile_control.rb
188 lines (162 loc) · 5.53 KB
/
compile_control.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
class Compiler
def compile_jmp_on_false(scope, r, target)
if r && r.type == :object
@e.save_result(r)
@e.evict_all
@e.cmpl(@e.result_value, "nil")
@e.je(target)
@e.cmpl(@e.result_value, "false")
@e.je(target)
else
@e.evict_all
@e.jmp_on_false(target, r)
end
end
# Changes to make #compile_if comply with real-life requirements
# makes it hard to use it to implement 'or' without introducing a
# temporarily variable. First we did that using a global, as a
# hack. This does things more "properly" as a first stage to
# either refactoring out the commonalities with compile_if or
# create a "lower level" more generic method to handle conditions
#
# (for "or" we really only need a way to explicitly say that
# the return value of the condition should be left untouched
# if the "true" / if-then part of the the if condition should remain
#
def compile_or scope, left, right
@e.comment("compile_or: #{left.inspect} || #{right.inspect}")
ret = compile_eval_arg(scope,left)
l_or = @e.get_local + "_or"
compile_jmp_on_false(scope, ret, l_or)
l_end_or = @e.get_local + "_end_or"
@e.jmp(l_end_or)
@e.comment(".. or:")
@e.local(l_or)
or_ret = compile_eval_arg(scope,right)
@e.local(l_end_or)
@e.evict_all
combine_types(ret,or_ret)
end
# or_assign is "x ||= y", which translates to x = y if !x
def compile_or_assign scope, left, right
@e.comment("compile_or_assign: #{left.inspect} ||= #{right.inspect}")
ret = compile_eval_arg(scope,left)
l_or = @e.get_local + "_or"
compile_jmp_on_false(scope, ret, l_or)
l_end_or = @e.get_local + "_end_or"
@e.jmp(l_end_or)
@e.comment(".. or:")
@e.local(l_or)
or_ret = compile_assign(scope, left, right)
@e.local(l_end_or)
@e.evict_all
combine_types(ret,or_ret)
end
# Compiles an if expression.
# Takes the current (outer) scope and two expressions representing
# the if and else arm.
# If no else arm is given, it defaults to nil.
def compile_if(scope, cond, if_arm, else_arm = nil)
@e.comment("if: #{cond.inspect}")
res = compile_eval_arg(scope, cond)
l_else_arm = @e.get_local + "_else"
compile_jmp_on_false(scope, res, l_else_arm)
@e.comment("then: #{if_arm.inspect}")
ifret = compile_eval_arg(scope, if_arm)
l_end_if_arm = @e.get_local + "_endif"
@e.evict_all
@e.jmp(l_end_if_arm) if else_arm
@e.comment("else: #{else_arm.inspect}")
@e.local(l_else_arm)
@e.evict_all
# FIXME: Workaround for missing initialisation of local vars
elseret = nil
elseret = compile_eval_arg(scope, else_arm) if else_arm
@e.evict_all
@e.local(l_end_if_arm) if else_arm
# At the moment, we're not keeping track of exactly what might have gone on
# in the if vs. else arm, so we need to assume all bets are off.
@e.evict_all
combine_types(ifret, elseret)
end
def compile_return(scope, arg = :nil)
@e.save_result(compile_eval_arg(scope, arg)) if arg
@e.movl("-4(%ebp)",:ebx)
@e.evict_all
reload_self(scope)
@e.leave
@e.ret
Value.new([:subexpr])
end
# Compiles a while loop.
# Takes the current scope, a condition expression as well as the body of the function.
def compile_while(scope, cond, body)
@e.loop do |br,l|
var = compile_eval_arg(scope, cond)
compile_jmp_on_false(scope, var, br)
compile_exp(ControlScope.new(scope, br,l), body)
end
compile_eval_arg(scope, :nil)
return Value.new([:subexpr])
end
# "next" acts differently in a control structure vs. block
#
# In "while" etc, "next" jumps to the next iteration.
# In a block, "next" exits the block.
#
def compile_next(scope, arg = :nil)
l = scope.loop_label
if l
@e.jmp(l)
@e.evict_all
return Value.new([:subexpr])
end
compile_return(scope,arg)
end
# "break" has different complexity in different contexts:
#
# 1) Lexically inside constructs like "while", break "just" jumps out of the loop
# This is handled using "controlscope", which intercept requests for a "break label"
#
# 2) Inside bare blocks, a break is a potentially non-local jump up the stack to the
# first instruction *following* the method call that the bare block is attached to.
#
# 3) FIXME: ? Verify behaviour for *lambda* as opposed to *proc* and bare blocks.
#
# Case #2 is handled by saving the stack frame (which we also need for "preturn")
# of the location the block is defined. But unlike preturn, where we put this stack
# frame in place and "leave", thus triggering a return *from* the point we defined
# the block, for "break" we unwind the stack until "leave" leaves the stack frame
# in question in %ebp. Then we "ret". This causes us to return to the instruction
# after the "call" that brought us into the method that took the block as an argument
# - just where we want to be.
#
# See also controlscope.rb
#
def compile_break(scope)
br = scope.break_label
@e.comment("BREAK")
if br
@e.jmp(br)
else
# Handling lexical break from block/proc's.
#
# If after leave, %ebp == __stackframe__
# then we're where we want to be.
ret = compile_eval_arg(scope,[:index,:__env__,0])
@e.movl(ret,:eax) if ret != :eax
l = @e.local
r = @e.get_local
@e.leave
@e.cmpl(:eax, :ebp)
@e.jz r
@e.addl(4,:esp)
@e.jmp l
@e.local(r)
@e.movl("-4(%ebp)",:ebx)
@e.ret
end
@e.evict_all
return Value.new([:subexpr])
end
end