4 ## Really simple struct for our bytecode transformations below. Somewhere
5 ## between the [[instr, args], ...] and a sequence of
6 ## CompiledMethod::Instruction objects. In particular, can have multiple
13 def initialize op, args=[], labels=[]
19 def size; args.size + 1 end
21 ## sets of opcodes we perform transformations on
22 OPCODES_WITH_LITERAL_0 = InstructionSet::OpCodes.select { |o| o.args[0] == :literal }.map { |o| o.opcode }
23 OPCODES_WITH_LOCAL_0 = InstructionSet::OpCodes.select { |o| [:local, :block_local].include? o.args[0] }.map { |o| o.opcode }
24 OPCODES_WITH_LOCAL_1 = InstructionSet::OpCodes.select { |o| [:local, :block_local].include? o.args[1] }.map { |o| o.opcode }
25 OPCODES_WITH_IP_0 = InstructionSet::OpCodes.select { |o| o.args[0] == :ip }.map { |o| o.opcode }
27 ## we have to subtrace this from OPCODES_WITH_LITERAL_0 when we're assuming
28 ## sender == receipient, because we don't want to rewrite ivar access, but do
29 ## want to rewrite other literals.
30 OPCODES_WITH_IVAR_0 = [:push_ivar, :set_ivar]
32 def literal0?; OPCODES_WITH_LITERAL_0.include? @op end
33 def local0?; OPCODES_WITH_LOCAL_0.include? @op end
34 def local1?; OPCODES_WITH_LOCAL_1.include? @op end
35 def ip0?; OPCODES_WITH_IP_0.include? @op end
36 def literal_not_ivar0?; (OPCODES_WITH_LITERAL_0 - OPCODES_WITH_IP_0).include? @op end
39 (@labels.empty? ? "" : "[#{@labels * ','}:] ") + "#@op(#{@args * ', '})"
43 class Rejection < StandardError; end
50 def normalize_method m
54 ## TODO: handle m being a DelegatedMethod
56 ## Right now this depends on AccessVarMethod#name, which seems a bit
57 ## tenuous of a link. But we have to get the setter vs getter aspect of
58 ## it somewhere, and that's the only place it's encoded right now.
63 ops = [[:check_argcount, 1, 1],
64 [:set_local_from_fp, 0, 0],
68 CompiledMethod.from_bytecodes ops, 1, 1, Tuple["@#{ivar}".intern]
70 ops = [[:check_argcount, 0, 0],
73 CompiledMethod.from_bytecodes ops, 0, 0, Tuple["@#{m.name}".intern]
76 reject "DelegatedMethods not yet supported"
84 puts (s ? "## #{s}" : "")
87 ## simple debug dumper for Inst sequences
90 m.inject(offset) do |ip, inst|
91 printf "%6s %3d %s %s\n", (inst.labels.empty? ? "" : (inst.labels * ',') + ":"), ip, inst.op, (inst.args * ', ')
98 m.inject(0) do |ip, (op, *args)|
99 printf "%3d %s %s\n", ip, op, (args * ", ")
104 ## rewrite any srets as jumps to a target label
105 def rewrite_srets_as_jumps_to m, target
109 debug "rewriting #{inst} to goto(#{target})"
110 Inst.new :goto, target
117 ## rewrite a bytecode sequence to update all literal and local references by
118 ## adding the given offsets
119 def rewrite_locs_and_lits m, local_offset, literal_offset
122 debug "rewriting local0 #{inst} +#{local_offset}"
123 inst.args[0] += local_offset
125 debug "rewriting local1 #{inst} +#{local_offset}"
126 inst.args[1] += local_offset
127 elsif inst.literal_not_ivar0?
128 debug "rewriting literal0 #{inst} +#{literal_offset}"
129 inst.args[0] += literal_offset
130 elsif inst.op == :set_local_from_fp
131 reject "unexpeced set_local_from_fp: #{op.inspect}"
132 elsif inst.op == :activate_method
133 reject "no idea what to do with :activate_method"
138 ## Transform CompiledMethods into sequences of Insts. Also takes all IP
139 ## references (jumps, exception-handling regions, line-to-ip regions) and
140 ## adds them as pseudo-labels to the target Inst.
141 def decompose method, start_label=0
143 next_label = start_label
145 ## make insts objects, and record jump targets
146 insts = method.bytecodes.decode.map do |op, *args|
147 i = Inst.new op, args
149 if i.ip0? # a jump of some sort
151 l = (labels[dest] ||= (next_label += 1) - 1)
157 ## store exception handler targets
158 excs = method.exceptions.map do |tuple|
159 lowip, highip, handleip = tuple.to_a
161 lowl = (labels[lowip] ||= (next_label += 1) - 1)
162 highl = (labels[highip] ||= (next_label += 1) - 1)
163 handlel = (labels[handleip] ||= (next_label += 1) - 1)
164 debug "mapped exception: #{[lowip, highip, handleip].inspect} => #{[lowl, highl, handlel].inspect}"
165 [lowl, highl, handlel]
168 ## finally, store labels on the insts themselves
171 ## bah. apparently exception high-ip markers are not aligned at
172 ## instruction boundaries. so we search for labels assigned to
173 ## intra-intruction ips and add them to the lower instruction.
175 ## we'll have to reverse this process at recomposition time, of course.
176 (ip ... (ip + i.size)).each do |ipfreely|
177 i.labels << labels[ipfreely] if labels[ipfreely]
182 [insts, excs, next_label]
185 ## transform a sequence of Insts into bytecodes
186 def recompose m, excs
189 ## first pass: get labels
190 m.inject(0) do |ip, inst|
191 inst.labels.each { |l| labels[l] = [ip, inst] }
195 ## second pass: rewrite
197 if inst.ip0? # a jump of some sort
198 l = labels[inst.args.first] or reject "can't map label for #{inst} (inliner bug!)"
201 [inst.op, *inst.args]
205 ## rewrite exceptions. for the high IP, shift to right before the next instruction
206 ## boundary. not sure if that's actually important, but that's how they come in
207 ## from the compiler...
208 excs = excs.map do |low, high, handle|
209 lowl, lowip = labels[low]
210 reject "can't map low label #{low} for exception (inliner bug!)" unless lowl
211 highl, highip = labels[high]
212 reject "can't map high label #{high} for exception (inliner bug!)" unless highl
213 handlel, handleip = labels[handle]
214 reject "can't map handle label #{handle} for exception (inliner bug!)" unless handlel
215 [lowl, highl + highip.size - 1, handlel]
216 end.sort_by { |low, high, handle| high - low } # smallest first
222 ## Inliner for the simple case where you have the following calling
226 ## send_method X OR send_stack X Y
228 ## In other words, where the callee is guaranteed to be self, and the number of
229 ## arguments is fixed (no default arguments and no splats).
230 class SimpleSelfCallInliner < Base
231 def inline sender, receiver, sendsite_point
232 sender = normalize_method sender
233 receiver = normalize_method receiver
234 sops, sexcs, num_labels = decompose sender
235 rops, rexcs, num_labels = decompose receiver, num_labels
244 ## find & verify sendsite instructions based on sendsite_point
245 injp, injp_size = verify_inject_point sops, sendsite_point
246 debug "injection point is #{injp}...#{injp + injp_size}"
248 ## determine number of arguments (currently, must be fixed)
249 inst = sops[injp + injp_size - 1]
250 num_args = case inst.op
252 when :send_stack; inst.args[1]
254 reject "injection point #{p} instruction not send_stack or send_method: #{sops[p].inspect}"
256 debug "declared num args is #{num_args}"
258 ## split sender around injection point
259 sops_top = sops[0 ... injp]
260 sops_bot = sops[(injp + injp_size) .. -1]
262 ## rewrite receiver for argument passing. this elides the final
263 ## instruction, so if it was a jump target, we need to attach that label
265 rops, final_labels = rewrite_arguments rops, num_args
267 ## make our own label for the final instruction, in case there wasn't one
269 final_labels << num_labels
270 debug "final labels are #{final_labels.inspect}"
272 ## attach final labels to beginning of sender bottom
273 sops_bot.first.labels += final_labels
275 ## find any labels on the injection point and move them to the receiver's
276 ## first instruction, since the injection point opcodes will be replaced by
278 inject_labels = sops[injp ... (injp + injp_size)].map { |i| i.labels }.flatten
279 rops.first.labels += inject_labels
280 debug "injection point labels are #{inject_labels.inspect}"
282 ## rewrite receiver local and literal references. just shift them past the
283 ## end of the sender counts.
284 debug "sender has #{sender.local_count} locals and #{sender.literals.size} literals"
285 rops = rewrite_locs_and_lits rops, sender.local_count, sender.literals.size
287 ## rewrite receiver srets -> jumps to the receiver final label
288 rops = rewrite_srets_as_jumps_to rops, final_labels.first
291 offset = dump sops_top
292 debug "<<< insplice >>>"
293 offset = dump rops, offset
294 debug "<<< outsplice >>>"
295 offset = dump sops_bot, offset
297 ## create bytecodes and exception tuple for the compiledmethod
298 bytecodes, excs, = recompose((sops_top + rops + sops_bot), (sexcs + rexcs))
301 debug "exceptions: #{excs.inspect}"
304 excs = Tuple[*excs.map { |e| Tuple[*e] }]
306 c = CompiledMethod.from_bytecodes bytecodes, sender.required,
307 (sender.local_count + receiver.local_count),
308 (sender.literals + receiver.literals),
310 c.file = sender.file # well, what am i supposed to do?
316 def verify_inject_point sops, sendsite_point
317 p = (0 ... sops.length).inject(0) do |ip, inum|
318 break inum if ip == sendsite_point
323 reject "couldn't find injection point"
326 if p < 2 # must have at least two instructions beforehand
327 reject "injection point #{p} too early"
331 reject "injection point #{p} too late for a #{sops.size}-instruction method"
334 ## here we determine the number of instructions (not the bytecode size!)
335 ## that make up this call sequence we start with 1 for the send instruction
339 if sops[p - size].op == :set_call_flags
340 size += 1 # consume this op
343 unless sops[p - size].op == :push_self
344 reject "injection point #{p}-#{size} is not [:push_self]: #{sops[p - size].inspect}"
348 [p - size, size + 1] # right bound is exclusive
351 def rewrite_arguments m, num_args
352 ## drop argcount check
353 unless m.first.op == :check_argcount && m.first.args == [num_args, num_args]
354 reject "first opcode of m is not [:check_argcount, #{num_args}, #{num_args}]"
359 unless m.last.op == :sret
360 reject "last opcode of method is not [:sret]"
362 ## we save the label and return it because we'll reattach it to the bottom
363 ## half of the enclosing method
364 final_labels = m.last.labels
367 ## rewrite set_local_from_fp statements
368 if m[num_args].op == :set_local_from_fp
369 reject "unexpected :set_local_from_fp at m position #{num_args}: #{m[num_args.inspect]}"
375 m[0 ... num_args].each do |inst|
376 unless inst.op == :set_local_from_fp && inst.args == [next_fp, next_fp]
377 reject "expecting set_local_from_fp for #{next_fp}, found #{inst} instead"
379 new_header << Inst.new(:set_local, next_fp)
380 new_header << Inst.new(:pop)
384 [(new_header + m[num_args .. -1]), final_labels]
389 end # module Rubinius