Re-enable spec/library for full CI runs.
[rbx.git] / lib / compiler / plugins.rb
blobc05f0dc134452e6805e4ffb58d44418281bb0c31
1 require 'compiler/execute'
3 class Compiler
5 ##
6 # A namespace for compiler plugins.  A plugin emits custom bytecode for an
7 # expression
9 module Plugins
11   @plugins = {}
13   def self.add_plugin(name, cls)
14     @plugins[name] = cls
15   end
17   def self.find_plugin(name)
18     @plugins[name]
19   end
21   class Plugin
22     def initialize(compiler)
23       @compiler = compiler
24     end
26     def self.plugin(name, kind=:call)
27       @kind = kind
28       Plugins.add_plugin name, self
29     end
31     def self.kind
32       @kind
33     end
35     def call_match(c, const, method)
36       return false unless c.call?
37       return false unless c.method == method
38       return false unless c.object.kind_of? Node::ConstFind
39       return false unless c.object.name == const
40       return true
41     end
42   end
44   ##
45   # Handles block_given?
47   class BlockGiven < Plugin
49     plugin :block_given
51     def handle(g, call)
52       if call.fcall?
53         if call.method == :block_given? or call.method == :iterator?
54           g.push_block
55           return true
56         end
57       end
59       return false
60     end
61   end
63   ##
64   # Handles Ruby.primitive
66   class PrimitiveDeclaration < Plugin
68     plugin :primitive
70     def handle(g, call)
71       return false unless call_match(call, :Ruby, :primitive)
73       prim = call.arguments.first.value
75       g.as_primitive prim
77       return true
78     end
79   end
81   ##
82   # Handles Rubinius.asm
84   class InlineAssembly < Plugin
86     plugin :assembly
88     def handle(g, call)
89       return false unless call_match(call, :Rubinius, :asm)
90       return false unless call.block
92       exc = ExecuteContext.new(g)
93       i = 0
94       args = call.arguments
96       call.block.arguments.names.each do |name|
97         exc.set_local name, args[i]
98         i += 1
99       end
101       exc.execute call.block.body
103       return true
104     end
106   end
108   ##
109   # Handles __METHOD__
111   class CurrentMethod < Plugin
113     plugin :current_method
115     def handle(g, call)
116       return false unless call.kind_of? Node::VCall
117       if call.method == :__METHOD__
118         g.push_context
119         g.send :method, 0
120         return true
121       end
123       return false
124     end
125   end
127   ##
128   # Handles emitting fast VM instructions for certain math operations.
130   class FastMathOperators < Plugin
132     plugin :fastmath
134     MetaMath = {
135       :+ =>    :meta_send_op_plus,
136       :- =>    :meta_send_op_minus,
137       :== =>   :meta_send_op_equal,
138       :"!=" => :meta_send_op_nequal,
139       :=== =>  :meta_send_op_tequal,
140       :< =>    :meta_send_op_lt,
141       :> =>    :meta_send_op_gt
142     }
144     def handle(g, call)
145       name = MetaMath[call.method]
147       if name and call.argcount == 1
148         call.emit_args(g)
149         call.receiver_bytecode(g)
150         g.add name
151         return true
152       end
154       return false
155     end
156   end
158   ##
159   # Handles emitting fast VM instructions for certain methods.
161   class FastGenericMethods < Plugin
163     plugin :fastgeneric
165     Methods = {
166       :call => :meta_send_call
167     }
169     def handle(g, call)
170       # Don't handle send's with a block or non static args.
171       return false if call.block or call.argcount.nil?
173       if name = Methods[call.method]
174         call.emit_args(g)
175         call.receiver_bytecode(g)
176         g.add name, call.argcount
177         return true
178       end
180       return false
181     end
182   end
184   ##
185   # Handles emitting save VM instructions for redifinition of math operators.
187   class SafeMathOperators < Plugin
189     plugin :safemath
191     MathOps = {
192       :/ => :divide
193     }
195     def handle(g, call)
196       name = MathOps[call.method]
197       if name and call.argcount == 1
198         call.emit_args(g)
199         call.receiver_bytecode(g)
200         g.send name, 1, false
201         return true
202       end
203       return false
204     end
205   end
207   ##
208   # Handles constant folding
210   class ConstantExpressions < Plugin
212     plugin :const_epxr
214     MathOps = [:+, :-, :*, :/, :**]
216     def handle(g, call)
217       op = call.method
218       return false unless MathOps.include? op and call.argcount == 1
220       obj = call.object
221       arg = call.arguments.first
222       if call.object.kind_of? Node::NumberLiteral and call.arguments.first.kind_of? Node::NumberLiteral
223         res = obj.value.__send__(op, arg.value)
224         if res.kind_of? Fixnum
225           g.push_int res
226         else
227           g.push_literal res
228         end
229         return true
230       end
231       return false
232     end
233   end
235   ##
236   # Prototype plugin for handling inlining.  Not in use.
238   class CompilerInlining < Plugin
239     plugin :inline
241     Methods = {
242       :times => :fixnum_times
243     }
245     def handle(g, call)
246       return false unless call.block.kind_of? Node::Iter
247       if handler = Methods[call.method]
248         return __send__(handler, g, call)
249       end
251       return false
252     end
254     def fixnum_times(g, call)
255       return false unless call.no_args?
256       return false unless call.block.arguments.names.empty?
258       do_call = g.new_label
260       # Since there are no args, this just makes sure that the internals
261       # are setup properly.
262       call.emit_args(g)
264       call.receiver_bytecode(g)
265       g.dup
266       g.is_fixnum
267       g.gif do_call
268       g.dup
269       g.check_serial :times, 0
270       g.gif do_call
272       done = g.new_label
274       desc = MethodDescription.new @compiler.generator_class, call.block.locals
275       desc.name = :__inlined_block__
276       desc.required, desc.optional = call.block.argument_info
277       sub = desc.generator
279       sub.set_line g.line, g.file
281       @compiler.show_errors(sub) do
282         sub.push_modifiers
284         top = sub.new_label
285         fin = sub.new_label
286         nxt = sub.new_label
288         sub.next = nxt
289         # TODO: break needs to be handled differently. See the specs for
290         # a nested while with a complex break expression in the Integer#times specs.
291         sub.break = fin
292         sub.redo = sub.new_label
294         # Get rid of the block args.
295         sub.unshift_tuple
296         sub.dup
298         # Create the loop counter
299         sub.dup
300         sub.push 1
301         sub.meta_send_op_plus
302         sub.push :nil
304         # Loop starts here
305         top.set!
307         # Descrement the loop counter
308         sub.pop
309         sub.push 1
310         sub.swap
311         sub.meta_send_op_minus
313         # Check if the loop counter is 0
314         sub.dup
315         sub.push 0
316         sub.equal
317         sub.git fin
319         # To the times logic now, calling block.body
320         sub.redo.set!
322         call.block.body.bytecode(sub)
324         # Loop ends here
325         sub.goto top
327         nxt.set!
328         sub.push :nil
329         sub.goto top
331         sub.pop_modifiers
332         fin.set!
333         sub.pop
334         sub.soft_return
335         sub.close
336       end
338       g.push_literal desc
339       g.create_block2
340       g.send :call, 1
342       g.goto done
343       do_call.set!
345       call.block.bytecode(g)
346       g.swap
347       call.block_bytecode(g)
349       done.set!
350     end
351   end
353   ##
354   # Maps various methods to VM instructions
356   class SystemMethods < Plugin
358     plugin :fastsystem
360     Methods = {
361       :__kind_of__ =>      :kind_of,
362       :__instance_of__ =>  :instance_of,
363       :__nil__ =>          :is_nil,
364       :__equal__ =>        :equal,
365       :__class__ =>        :class,
366       :__fixnum__ =>       :is_fixnum,
367       :__symbol__ =>       :is_symbol,
368       :__nil__ =>          :is_nil
369     }
371     # How many arguments each method takes.
372     Args = {
373       :__kind_of__     => 1,
374       :__instance_of__ => 1,
375       :__nil__         => 0,
376       :__equal__       => 1,
377       :__class__       => 0,
378       :__fixnum__      => 0,
379       :__symbol__      => 0,
380       :__nil__         => 0
381     }
383     def handle(g, call)
384       return false if call.block
386       name = Methods[call.method]
388       return false unless name
389       return false unless Args[call.method] == call.argcount
391       call.emit_args(g)
392       call.receiver_bytecode(g)
393       g.add name
394       return true
395     end
397   end
399   ##
400   # Detects common simple expressions and simplifies them
402   class AutoPrimitiveDetection < Plugin
403     plugin :auto_primitive, :method
405     SingleInt = [[:check_argcount, 0, 0], [:push_int, :any], [:sret]]
406     Literal = [[:check_argcount, 0, 0], [:push_literal, 0], [:sret]]
407     Self = [[:check_argcount, 0, 0], [:push_self], [:sret]]
408     Ivar = [[:check_argcount, 0, 0], [:push_ivar, 0], [:sret]]
409     Field = [[:check_argcount, 0, 0], [:push_my_field, :any], [:sret]]
411     def handle(g, obj, meth)
412       ss = meth.generator.stream
414       return true unless ss.size == 3
416       gen = meth.generator
418       if gen === SingleInt
419         meth.generator.literals[0] = ss[1][1]
420         meth.generator.as_primitive :opt_push_literal
421       elsif gen === Literal
422         # The value we want is already in literal 0
423         meth.generator.as_primitive :opt_push_literal
424       elsif gen === Self
425         meth.generator.as_primitive :opt_push_self
426       elsif gen === Ivar
427         meth.generator.as_primitive :opt_push_ivar
428       elsif gen === Field
429         meth.generator.literals[0] = ss[1][1]
430         meth.generator.as_primitive :opt_push_my_field
431       else
432         case ss[1].first
433         when :push_nil
434           lit = nil
435         when :push_true
436           lit = true
437         when :push_false
438           lit = false
439         when :meta_push_0
440           lit = 0
441         when :meta_push_1
442           lit = 1
443         when :meta_push_2
444           lit = 2
445         else
446           return true
447         end
449         meth.generator.literals[0] = lit
450         meth.generator.as_primitive :opt_push_literal
451       end
453       true
454     end
456   end
458   ##
459   # Conditional compilation
461   class ConditionalCompilation < Plugin
462     plugin :conditional_compilation, :conditional_compilation
464     ##
465     # Matches on the special form Rubinius.compile_if.
466     #
467     # If the form is not matched, returns nil. If the form does match,
468     # however, then its argument is checked. If the arg evaluates to true, the
469     # contained code should be compiled and if false, it should be omitted. To
470     # achieve this, we throw a symbol in both cases (:iter for former,
471     # :newline for latter.) The symbols are caught at the appropriate spots
472     # further up the processing branch and those nodes then appropriately
473     # slice up the sexp to produce the desired result.
474     #
475     # See Node#consume and Iter#consume for those actions.
477     def handle(g, call_node, sexp)
478       # The sexp should look something like
479       #
480       #   [[:const, :Rubinius], :compile_if, [:array, [<type>, ...]]]
481       #
482       # Currently we only check global variables but stuff like strings
483       # to eval or even actual composable method calls are possible.
484       if sexp[1] == :compile_if and sexp[0].kind_of? Array and sexp[0][1] == :Rubinius
485         throw(eval(sexp[2][1][1].to_s) ? :iter : :newline)
486       end
487     end
488   end
489 end # Plugins
490 end # Compiler