Re-enable spec/library for full CI runs.
[rbx.git] / kernel / core / compiled_method.rb
blob1cbcb5b32940bedef1a58550375660648860f2d8
1 # depends on: class.rb array.rb
3 ##
4 # A wrapper for a calling a function in a shared library that has been
5 # attached via rb_define_method().
7 # The primitive slot for a NativeMethod points to the nmethod_call primitive
8 # which dispatches to the underlying C function.
10 class NativeMethod
11   def lines
12     nil
13   end
15   def exceptions
16     nil
17   end
19   def literals
20     nil
21   end
23   def line_from_ip(i)
24     0
25   end
26 end
29 # A linked list that details the static, lexical scope the method was created
30 # in.
32 # You can access it this way:
34 #   MethodContext.current.method.staticscope
36 # Here is a simple example:
38 #   module Fruits
39 #     class Pineapple
40 #       attr_reader :initialize_scope
41 #   
42 #       def initialize(weight)
43 #         @initialize_scope = MethodContext.current.method.staticscope
44 #         @weight = weight
45 #       end
46 #     end
47 #   end
49 # Static scope members are shown below:
51 #   irb(main):> pineapple.initialize_scope.script
52 #   => nil
53 #   irb(main):> pineapple.initialize_scope.parent
54 #   => #<StaticScope:0x1c9>
55 #   irb(main):> pineapple.initialize_scope.module
56 #   => Fruits::Pineapple
57 #   irb(main):> pineapple.initialize_scope.parent.module
58 #   => Fruits
59 #   irb(main):> pineapple.initialize_scope.parent.parent.module
60 #   => Object
61 #   irb(main):> pineapple.initialize_scope.parent.parent.parent.module
62 #   => Object
64 class StaticScope
65   ivar_as_index :__ivars__ => 0, :module => 1, :parent => 2
67   def initialize(mod, par=nil)
68     @module = mod
69     @parent = par
70   end
72   attr_accessor :script
74   # Source code of this scope.
75   def script
76     @script
77   end
79   # Module or class this lexical scope enclosed into.
80   def module
81     @module
82   end
84   # Static scope object this scope enclosed into.
85   def parent
86     @parent
87   end
89   def inspect
90     "#<#{self.class.name}:0x#{self.object_id.to_s(16)} parent=#{@parent} module=#{@module}>"
91   end
93   def to_s
94     self.inspect
95   end
96 end
99 # CompiledMethod represents source code method compiled into VM bytecodes.
100 # Its instruction set is then executed by Shotgun's abstraction of CPU.
101 # CompiledMethods are not just sets of instructions though. They carry a lot
102 # of information about method: its lexical scope (static scope), name, file
103 # it has been defined in and so forth.
105 class CompiledMethod
106   # TODO: Delete/reuse cache (field 14) field from C structure
107   ivar_as_index :__ivars__ => 0,
108                 :primitive => 1,
109                 :required => 2,
110                 :serial => 3, 
111                 :bytecodes => 4, 
112                 :name => 5, 
113                 :file => 6, 
114                 :local_count => 7, 
115                 :literals => 8, 
116                 :args => 9, 
117                 :local_names => 10, 
118                 :exceptions => 11, 
119                 :lines => 12, 
120                 :path => 13, 
121                 :metadata_container => 15, 
122                 :compiled => 16, 
123                 :staticscope => 17
125   def __ivars__  ; @__ivars__  ; end
127   ##
128   # nil if the method does not have a primitive, otherwise the name of the
129   # primitive to run.
131   def primitive  ; @primitive  ; end
133   # number of arguments required by method
134   def required   ; @required   ; end
136   # Version of method: an incrementing integer.
137   # When you redefine method via re-opening
138   # a class this number is increased.
139   #
140   # Kernel methods have serial of 0
141   # %99.9 of the time.
142   def serial     ; @serial     ; end
144   # instructions set that VM executes
145   # instance of InstructionSequence
146   def bytecodes  ; @bytecodes  ; end
148   # method name as Symbol
149   def name       ; @name       ; end
151   # file in which this method has been defined
152   def file       ; @file       ; end
154   # number of local variables method uses
155   # note that locals are stored in slots
156   # in the context this CompiledMethod
157   # is executed in.
158   def local_count; @local_count; end
160   # literals tuple stores literals from
161   # source code like string literals and
162   # some extra stuff like SendSites,
163   # RegExp objects created from
164   # regexp literals and CompiledMethods
165   # of inner methods.
166   def literals   ; @literals   ; end
168   # Tuple holding the arguments defined on a method.
169   # Consists of 3 values:
170   # - a tuple of symbols naming required args (or nil if none)
171   # - a tuple of symbols naming optional args (or nil if none)
172   # - the symbol for any splat arg (or nil if none)
173   def args       ; @args       ; end
175   # Tuple holding the symbols for all local variable names used in the method.
176   def local_names; @local_names; end
178   # Tuple of tuples. Inner tuples contain
179   # low IP, high IP and IP of exception
180   # handler.
181   #
182   # When exception is raised this tuple is
183   # looked up by VM using context IP:
184   #
185   # Tuple which low/high IP fit in context
186   # IP is picked up and handling continues.
187   #
188   # TODO: double check this statement.
189   def exceptions ; @exceptions ; end
191   # Tuple of Tuples. Each inner Tuple
192   # stores the following information:
193   #
194   # low IP, high IP and line number as integer.
195   def lines      ; @lines      ; end
197   # Holds the path of a script CompiledMethod created using eval.
198   # Required for the proper functioning of __FILE__ under eval.
199   def path       ; @path       ; end
201   # Separate object for storing metadata; this way
202   # the metadata can change without changes to the
203   # CM itself.
204   def metadata_container      ; @metadata_container      ; end
206   # ByteArray of pointers to optcodes.
207   # This is only populated when CompiledMethod
208   # is loaded into VM and platform specific.
209   #
210   # You can think of it as of internal
211   # bytecode representation optimized
212   # for platform Rubinius runs on.
213   def compiled   ; @compiled   ; end
215   # lexical scope of method in source
216   # instance of StaticScope
217   def staticscope; @staticscope; end
219   ##
220   # This is runtime hints, added to the method by the VM to indicate how it's
221   # being used.
223   attr_accessor :hints
225   def inspect
226     "#<#{self.class.name}:0x#{self.object_id.to_s(16)} name=#{@name} file=#{@file}>"
227   end
229   def from_string(bc, lcls, req)
230     @bytecodes = bc
231     @primitive = -1
232     @local_count = lcls
233     @literals = Tuple.new(0)
234     @exceptions = nil
235     @lines = nil
236     @file = nil
237     @name = nil
238     @path = nil
239     @required = req
240     return self
241   end
243   def self.from_bytecodes bytecodes, arg_count, local_count, literals, exceptions=nil, lines=nil
244     c = CompiledMethod.new
245     c.bytecodes = InstructionSequence::Encoder.new.encode_stream bytecodes
246     c.primitive = false
247     c.local_count = local_count
248     c.required = arg_count
249     c.literals = literals
250     c.lines = lines || Tuple[Tuple[0, bytecodes.size, 0]]
251     c.exceptions = exceptions || []
252     c
253   end
255   def inherit_scope(other)
256     if ss = other.staticscope
257       @staticscope = ss
258     else
259       @staticscope = StaticScope.new(Object)
260     end
261   end
263   def staticscope=(val)
264     raise TypeError, "not a static scope: #{val.inspect}" unless val.kind_of? StaticScope
265     @staticscope = val
266   end
268   def exceptions=(tup)
269     @exceptions = tup
270   end
272   def local_count=(val)
273     @local_count = val
274   end
276   def required=(val)
277     @required = val
278   end
280   def literals=(tup)
281     @literals = tup
282   end
284   def args=(tup)
285     @args = tup
286   end
288   def file=(val)
289     @file = val
290   end
292   def name=(val)
293     @name = val
294   end
296   def lines=(val)
297     @lines = val
298   end
300   def path=(val)
301     @path = val
302   end
304   def primitive=(idx)
305     @primitive = idx
306   end
308   def serial=(ser)
309     @serial = ser
310   end
312   def metadata_container=(tup)
313     @metadata_container = tup
314   end
316   def args=(ary)
317     @args = ary
318   end
320   def local_names=(names)
321     return if names.nil?
323     unless names.kind_of? Tuple
324       raise ArgumentError, "only accepts a Tuple"
325     end
327     names.each do |n|
328       unless n.kind_of? Symbol
329         raise ArgumentError, "must be a tuple of symbols: #{n.inspect}"
330       end
331     end
333     @local_names = names
334   end
336   def activate(recv, mod, args, locals=nil, &prc)
337     sz = args.total
338     if prc
339       block = prc.block
340     else
341       block = nil
342     end
344     out = Rubinius.asm(args, block, locals, sz, mod, recv) do |a,b,l,s,m,r|
345       run a
346       push_array
347       run b
348       run l
349       run s
350       run m
351       push :self
352       run r
353       activate_method 0
354     end
356     return out
357   end
359   # Accessor for a hash of filenames (as per $" / $LOADED_FEATURES) to the
360   # script CompiledMethod.
361   def self.scripts
362     @scripts ||= {}
363   end
364   
365   # Helper function for searching for a CM given a file name; applies similar
366   # search and path expansion rules as load/require, so that the full path to
367   # the file need not be specified.
368   def self.script_for_file(filename)
369     if cm = self.scripts[filename]
370       return cm
371     end
372     # ./ ../ ~/ /
373     if filename =~ %r{\A(?:(\.\.?)|(~))?/}
374       if $2    # ~ 
375         filename.slice! '~/'
376         return scripts["#{ENV['HOME']}/#{filename}"]
377       else    # . or ..
378         return scripts["#{File.expand_path filename}"]
379       end
380     # Unqualified
381     else
382       scripts = self.scripts
383       $LOAD_PATH.each do |dir|
384         if cm = scripts["#{dir}/#{filename}"]
385           return cm
386         end
387       end
388     end
389     nil
390   end
392   class Script
393     attr_accessor :path
394   end
396   def as_script(script=nil)
397     script ||= CompiledMethod::Script.new
398     yield script if block_given?
400     Rubinius::VM.save_encloser_path
402     # Setup the scoping.
403     ss = StaticScope.new(Object)
404     ss.script = script
405     @staticscope = ss
407     activate_as_script
408     Rubinius::VM.restore_encloser_path
409   end
411   def line_from_ip(i)
412     @lines.each do |t|
413       start = t.at(0)
414       nd = t.at(1)
415       op = t.at(2)
416       if i >= start and i <= nd
417         return op
418       end
419     end
420     return 0
421   end
423   # Returns the address (IP) of the first instruction in this CompiledMethod
424   # that is on the specified line, or the address of the first instruction on
425   # the next code line after the specified line if there are no instructions
426   # on the requested line.
427   # This method only looks at instructions within the current CompiledMethod;
428   # see #locate_line for an alternate method that also searches inside the child
429   # CompiledMethods.
431   def first_ip_on_line(line)
432     @lines.each do |t|
433       if t.at(2) >= line
434         return t.at(0)
435       end
436     end
438     return -1
439   end
441   def bytecodes=(other)
442     @bytecodes = other
443   end
445   def first_line
446     @lines.each do |ent|
447       return ent[2] if ent[2] > 0
448     end
450     return -1
451   end
453   def is_block?
454     @name =~ /__(?:(?:\w|_)+)?block__/
455   end
457   # Convenience method to return an array of the child CompiledMethods from
458   # this CompiledMethod's literals.
460   def child_methods
461     literals.select {|lit| lit.kind_of? CompiledMethod}
462   end
464   # Convenience method to return an array of the SendSites from
465   # this CompiledMethod's literals.
467   def send_sites
468     literals.select {|lit| lit.kind_of? SendSite}
469   end
471   # Locates the CompiledMethod and instruction address (IP) of the first
472   # instruction on the specified line. This method recursively examines child
473   # compiled methods until an exact match for the searched line is found.
474   # It returns both the matching CompiledMethod and the IP of the first 
475   # instruction on the requested line, or nil if no match for the specified line
476   # is found.
478   def locate_line(line, cm=self)
479     cm.lines.each do |t|
480       if (l = t.at(2)) == line
481         # Found target line - return first IP
482         return cm, t.at(0)
483       elsif l > line
484         break
485       end
486     end
487     # Didn't find line in this CM, so check if a contained
488     # CM encompasses the line searched for
489     cm.child_methods.each do |child|
490       if res = locate_line(line, child)
491         return res
492       end
493     end
495     # No child method is a match - fail
496     return nil
497   end
499   ##
500   # Decodes the instruction sequence that is represented by this compileed
501   # method. Delegates to InstructionSequence to do the instruction decoding,
502   # but then converts opcode literal arguments to their actual values by looking
503   # them up in the literals tuple.
504   # Takes an optional bytecodes argument representing the bytecode that is to
505   # be decoded using this CompiledMethod's locals and literals. This is provided
506   # for use by the debugger, where the bytecode sequence to be decoded may not
507   # exactly match the bytecode currently held by the CompiledMethod, typically
508   # as a result of substituting yield_debugger instructions into the bytecode.
509   def decode(bytecodes = @bytecodes)
510     stream = bytecodes.decode(false)
511     ip = 0
512     args_reg = 0
513     stream.map! do |inst|
514       instruct = Instruction.new(inst, self, ip, args_reg)
515       ip += instruct.size
516       if instruct.opcode == :set_args
517         args_reg = 0
518       elsif instruct.opcode == :cast_array_for_args
519         args_reg = instruct.args.first
520       end
521       instruct
522     end
524     # Add a convenience method to the array containing the decoded instructions
525     # to convert an IP address to the index of the corresponding instruction
526     def stream.ip_to_index(ip)
527       if ip < 0 or ip > last.ip
528         raise ArgumentError, "IP address is outside valid range of 0 to #{last.ip} (got #{ip})"
529       end
530       each_with_index do |inst, i|
531         return i if ip <= inst.ip
532       end
533     end
534     stream
535   end
537   ##
538   # Calculates the minimum stack size required for this method.
539   #
540   # Returns two values:
541   # * The minimum size stack required
542   # * A flag indicating whether this is an exact size, or a minimum
543   def min_stack_size
544     dc = decode
545     high_mark = 0
546     exact = true
547     dc.inject(0) do |sz,op|
548       i,flg = op.stack_produced
549       sz += i
550       exact &&= flg
551       i,flg = op.stack_consumed
552       sz -= i
553       exact &&= flg
554       high_mark = sz if sz > high_mark
555       sz
556     end
557     return high_mark, exact
558   end
560   # Represents virtual machine's CPU instruction.
561   # Instructions are organized into instruction
562   # sequences known as iSeq, forming body
563   # of CompiledMethods.
564   #
565   # To generate VM optcodes documentation
566   # use rake doc:vm task.
567   class Instruction
568     def initialize(inst, cm, ip, args_reg)
569       @op = inst[0]
570       @args = inst[1..-1]
571       @args.each_index do |i|
572         case @op.args[i]
573         when :literal
574           @args[i] = cm.literals[@args[i]]
575         when :local
576           # TODO: Blocks should be able to retrieve local names as well,
577           # but need access to method corresponding to home context
578           @args[i] = cm.local_names[args[i]] if cm.local_names and cm.name != :__block__
579         when :block_local
580           # TODO: Blocks should be able to retrieve enclosing block local names as well,
581           # but need access to static scope
582           @args[i] = cm.local_names[args[i]] if cm.local_names and args[0] == 0
583         end
584       end
585       @ip = ip
586       @line = cm.line_from_ip(ip)
588       @stack_consumed = calculate_stack_usage(@op.stack_consumed, args_reg)
589       @stack_produced = calculate_stack_usage(@op.stack_produced)
590     end
592     # Instruction pointer
593     attr_reader :ip
594     attr_reader :line
596     ##
597     # Returns the OpCode object
599     # Associated OptCode instance.
600     def instruction
601       @op
602     end
604     ##
605     # Returns the symbol representing the opcode for this instruction.
607     def opcode
608       @op.opcode
609     end
611     ##
612     # Returns an array of 0 to 2 arguments, depending on the opcode.
614     def args
615       @args
616     end
618     def size
619       @args.size + 1
620     end
622     ##
623     # Returns the stack operands consumed by this instruction, as well as a flag
624     # indicating whether this is an exact value (true) or a minimum (false).
626     def stack_consumed
627       @stack_consumed
628     end
630     ##
631     # Returns the stack operands produced by this instruction, as well as a flag
632     # indicating whether this is an exact value (true) or a minimum (false).
634     def stack_produced
635       @stack_produced
636     end
638     ##
639     # Calculate the stack usage (pushes or pops) of this instruction.
641     def calculate_stack_usage(code, args_reg=0)
642       usage = code
643       exact = true
644       if code < 0
645         usage = 0
646         if code == -999
647           exact = false
648         else
649           # Stack usage depends on opcode args
650           code *= -1
651           mult, code = code.divmod(100)
652           arg, code = code.divmod(10)
653           if arg >= 1 and arg <= 2
654             # Opcode consumes/produces a multiple of the value in the specified
655             # opcode arg
656             usage += mult * args[arg-1]
657           elsif arg == 3
658             # Opcode consumes number of args specified in args register
659             usage += mult * args_reg
660             exact = false
661           end
662           usage += code
663         end
664       end
665       return usage, exact
666     end
668     def to_s
669       str = "%04d:  %-27s" % [@ip, opcode]
670       str << @args.map{|a| a.inspect}.join(', ')
671     end
672   end