mirror of
https://github.com/mistergibson/gxg-web-client.git
synced 2026-08-15 11:34:29 -07:00
726 lines
19 KiB
Ruby
726 lines
19 KiB
Ruby
module Rubinius
|
|
class CompiledCode < Executable
|
|
def self.allocate
|
|
Rubinius.primitive :compiledcode_allocate
|
|
raise PrimitiveFailure, "CompiledCode.allocate primitive failed"
|
|
end
|
|
|
|
def dup
|
|
Rubinius.primitive :compiledcode_dup
|
|
raise PrimitiveFailure, "CompiledCode#dup primitive failed"
|
|
end
|
|
|
|
def call_sites
|
|
Rubinius.primitive :compiledcode_call_sites
|
|
raise PrimitiveFailure, "CompiledCode#call_sites primitive failed"
|
|
end
|
|
|
|
def code_id
|
|
@code_id || stamp_id
|
|
end
|
|
|
|
def constant_caches
|
|
Rubinius.primitive :compiledcode_constant_caches
|
|
raise PrimitiveFailure, "CompiledCode#constant_caches primitive failed"
|
|
end
|
|
|
|
# Return the CompiledCode for caller of the method that called
|
|
# .of_sender.
|
|
#
|
|
# For example:
|
|
#
|
|
# def g
|
|
# f_code = Rubinius::CompiledCode.of_sender
|
|
# end
|
|
#
|
|
# def f
|
|
# g
|
|
# end
|
|
#
|
|
# f_code is the CompiledCode of f as requested by g.
|
|
#
|
|
def self.of_sender
|
|
Rubinius.primitive :compiledcode_of_sender
|
|
raise PrimitiveFailure, "CompiledCode.of_sender primitive failed"
|
|
end
|
|
|
|
def jitted?
|
|
Rubinius.primitive :compiledcode_jitted_p
|
|
raise PrimitiveFailure, "CompiledCode#jitted? primitive failed"
|
|
end
|
|
|
|
def sample_count
|
|
Rubinius.primitive :compiledcode_sample_count
|
|
raise PrimitiveFailure, "CompiledCode#sample_count primitive failed"
|
|
end
|
|
|
|
# Returns the CompiledCode object for the currently executing Ruby
|
|
# method. For example:
|
|
#
|
|
# def m
|
|
# p Rubinius::CompiledCode.current.name
|
|
# end
|
|
#
|
|
def self.current
|
|
Rubinius.primitive :compiledcode_current
|
|
raise PrimitiveFailure, "CompiledCode.current primitive failed"
|
|
end
|
|
|
|
##
|
|
# Any CompiledCode with this value in it's serial slot is expected to be
|
|
# the default, core library version.
|
|
KernelMethodSerial = 47
|
|
|
|
##
|
|
# Ivars: instance_variables, primitive, serial, name, iseq, stack_size,
|
|
# local_count, required_args, total_args, splat, literals, exceptions,
|
|
# lines, file, compiled, scope
|
|
##
|
|
# This is runtime hints, added to the method by the VM to indicate how it's
|
|
# being used.
|
|
|
|
attr_accessor :name
|
|
attr_accessor :hints # added by the VM to indicate how it's being used.
|
|
attr_accessor :metadata # [Tuple] extra data
|
|
attr_accessor :name # [Symbol] name of the method
|
|
attr_accessor :iseq # [Tuple] instructions to execute
|
|
attr_accessor :stack_size # [Integer] size of stack at compile time
|
|
attr_accessor :local_count # [Integer] number of local vars
|
|
attr_accessor :required_args # [Integer] number of required args
|
|
attr_accessor :post_args # [Integer] number of args after splat
|
|
attr_accessor :total_args # [Integer] number of total args
|
|
attr_accessor :splat # [Integer] POSITION of the splat arg
|
|
attr_accessor :literals # [Tuple] tuple of the literals
|
|
attr_accessor :lines # [Tuple] tuple of the lines where its found
|
|
attr_accessor :file # [Symbol] the file where this comes from
|
|
attr_accessor :local_names # [Array<Symbol>] names of the local vars
|
|
attr_accessor :scope # [LexicalScope] scope for looking up constants
|
|
attr_accessor :keywords # [Tuple] pairs of Symbol name, required flag
|
|
attr_accessor :arity # [Integer] number of arguments, negative if variadic.
|
|
attr_accessor :registers # [Integer] number of registers used.
|
|
|
|
##
|
|
# Compare this method with +other+. Instead of bugging out if +other+
|
|
# isn't a {CompiledCode}, this returns +false+ immediately unless
|
|
# we're comparing two apples, AKA, {CompiledCode}s. The methods have
|
|
# to be the exact same in implementation, but their scoping (location)
|
|
# can differ.
|
|
#
|
|
# For instance:
|
|
#
|
|
# (module A; def m; 5; end; end) == (def m; 5; end)
|
|
#
|
|
# and
|
|
#
|
|
# def monkey; 5; end
|
|
#
|
|
# and
|
|
#
|
|
# module B
|
|
# def monkey; 5; end
|
|
# end
|
|
#
|
|
# would all be the same, despite their access to different ivars and scopes
|
|
# (which {CompiledCode}s DO keep track of)
|
|
#
|
|
# @todo Make example (in method documentation) match reality
|
|
# @param [Rubinius::CompiledCode] other the other part to compare
|
|
# @param [Boolean]
|
|
#
|
|
def ==(other)
|
|
return false unless other.kind_of? CompiledCode
|
|
equivalent_body?(other) and
|
|
@lines == other.lines and
|
|
@name == other.name
|
|
end
|
|
|
|
def equivalent_body?(other)
|
|
@tags == other.tags and
|
|
@primitive == other.primitive and
|
|
@iseq == other.iseq and
|
|
@stack_size == other.stack_size and
|
|
@local_count == other.local_count and
|
|
@required_args == other.required_args and
|
|
@total_args == other.total_args and
|
|
@splat == other.splat and
|
|
block_index == other.block_index and
|
|
@literals == other.literals and
|
|
@file == other.file and
|
|
@local_names == other.local_names
|
|
end
|
|
|
|
##
|
|
# Returns the index of local +name+ or nil if there is no local.
|
|
#
|
|
def local_slot(name)
|
|
slot = 0
|
|
while slot < @local_count
|
|
return slot if @local_names[slot] == name
|
|
slot += 1
|
|
end
|
|
end
|
|
|
|
##
|
|
# Stores the index of the block argument in the metadata storage.
|
|
#
|
|
# @param [Fixnum] position
|
|
#
|
|
def block_index=(position)
|
|
if position
|
|
add_metadata(:block_index, position)
|
|
end
|
|
end
|
|
|
|
##
|
|
# @return [Fixnum|NilClass]
|
|
#
|
|
def block_index
|
|
return get_metadata(:block_index)
|
|
end
|
|
|
|
##
|
|
# Return a human readable interpretation of this method.
|
|
#
|
|
# @return [String]
|
|
def inspect
|
|
"#<#{self.class.name}:0x#{object_id.to_s(16)} name=#{@name} file=#{@file} line=#{defined_line}>"
|
|
end
|
|
|
|
alias_method :to_s, :inspect
|
|
|
|
##
|
|
# Make the method change its scope so that it can act as though
|
|
# it's from somewhere else. You can pass in a method and +self+
|
|
# will borrow its scope.
|
|
#
|
|
# @param [#scope] other the other method that has a scope we can borrow
|
|
def inherit_scope(other)
|
|
@scope = other.scope
|
|
end
|
|
|
|
##
|
|
# Set a breakpoint on +ip+. +obj+ can be any object. When the breakpoint
|
|
# is hit +obj+ is sent to the debugger so it can distinguish which breakpoint
|
|
# this is.
|
|
#
|
|
def set_breakpoint(ip, obj)
|
|
Rubinius.primitive :compiledcode_set_breakpoint
|
|
raise ArgumentError, "Unable to set breakpoint on #{inspect} at invalid bytecode address #{ip}"
|
|
end
|
|
|
|
##
|
|
# Erase a breakpoint at +ip+
|
|
#
|
|
def clear_breakpoint(ip)
|
|
Rubinius.primitive :compiledcode_clear_breakpoint
|
|
raise ArgumentError, "Unable to clear breakpoint on #{inspect} at invalid bytecode address #{ip}"
|
|
end
|
|
|
|
##
|
|
# Indicate if there is a breakpoint set at +ip+
|
|
#
|
|
def breakpoint?(ip)
|
|
Rubinius.primitive :compiledcode_is_breakpoint
|
|
raise ArgumentError, "Unable to retrieve breakpoint status on #{inspect} at bytecode address #{ip}"
|
|
end
|
|
|
|
def stamp_id
|
|
Rubinius.primitive :compiledcode_stamp_id
|
|
raise ArgumentError, "CompiledCode#stamp_id primitive failed"
|
|
end
|
|
|
|
class Script
|
|
attr_accessor :compiled_code
|
|
attr_accessor :file_path
|
|
attr_accessor :data_path
|
|
attr_accessor :eval_source
|
|
|
|
def initialize(method, path=nil, for_eval=false)
|
|
@compiled_code = method
|
|
@file_path = path
|
|
@for_eval = for_eval
|
|
@eval_source = nil
|
|
@main = false
|
|
end
|
|
|
|
private :initialize
|
|
|
|
def eval?
|
|
@for_eval
|
|
end
|
|
|
|
def make_main!
|
|
@main = true
|
|
end
|
|
|
|
def main?
|
|
@main
|
|
end
|
|
end
|
|
|
|
# Creates the Script instance for a toplevel compiled method.
|
|
def create_script(wrap=false)
|
|
script = CompiledCode::Script.new(self)
|
|
|
|
# Setup the scoping.
|
|
cs = LexicalScope.new(Object)
|
|
cs.script = script
|
|
|
|
if wrap
|
|
@scope = LexicalScope.new(Module.new, cs)
|
|
else
|
|
@scope = cs
|
|
end
|
|
|
|
sc = Rubinius::Type.object_singleton_class(MAIN)
|
|
VM.reset_method_cache sc, :__script__
|
|
sc.method_table.store :__script__, nil, self, @scope, 0, :public
|
|
|
|
script
|
|
end
|
|
|
|
def active_path
|
|
if @scope and script = @scope.current_script
|
|
if fp = script.file_path
|
|
return fp.dup
|
|
end
|
|
end
|
|
|
|
@file.to_s
|
|
end
|
|
|
|
def eval_source
|
|
if @scope and script = @scope.current_script
|
|
return script.eval_source
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
##
|
|
# Return the line of source code at +ip+.
|
|
#
|
|
# @param [Fixnum] ip
|
|
# @return [Fixnum] line
|
|
def line_from_ip(ip)
|
|
return -1 unless @lines
|
|
return 0 if @lines.size < 2
|
|
|
|
low = 0
|
|
high = @lines.size / 2 - 1
|
|
|
|
while low <= high
|
|
# the chance that we're going from a fixnum to a bignum
|
|
# here is low, but we still try to prevent that.
|
|
mid = low + ((high - low) / 2)
|
|
|
|
line_index = mid * 2 + 1
|
|
|
|
if ip < @lines.at(line_index - 1)
|
|
high = mid - 1
|
|
elsif ip >= @lines.at(line_index + 1)
|
|
low = mid + 1
|
|
else
|
|
return @lines.at(line_index)
|
|
end
|
|
end
|
|
|
|
@lines.at(@lines.size - 2)
|
|
end
|
|
|
|
##
|
|
# Returns the address (IP) of the first instruction in this CompiledCode
|
|
# that is on the specified line, or the address of the first instruction on
|
|
# the next code line after the specified line if there are no instructions
|
|
# on the requested line.
|
|
# This method only looks at instructions within the current CompiledCode;
|
|
# see #locate_line for an alternate method that also searches inside the child
|
|
# CompiledCodes.
|
|
#
|
|
# Optionally only consider ip's greater than +start+
|
|
#
|
|
# @return [Fixnum] the address of the first instruction
|
|
# OR nil if there is no ip for the given line
|
|
def first_ip_on_line(line, start=nil)
|
|
i = 1
|
|
total = @lines.size
|
|
|
|
while i < total
|
|
cur_line = @lines.at(i)
|
|
if cur_line >= line
|
|
ip = @lines.at(i-1)
|
|
|
|
if !start or ip > start
|
|
# matched the definition line, return 0
|
|
return 0 if ip == -1
|
|
return ip
|
|
end
|
|
end
|
|
|
|
i += 2
|
|
end
|
|
|
|
nil
|
|
end
|
|
|
|
##
|
|
# The first line where instructions are located.
|
|
#
|
|
# @return [Fixnum]
|
|
def first_line
|
|
line_from_ip(0)
|
|
end
|
|
|
|
##
|
|
# Indicate the line in the source code that this
|
|
# was defined on.
|
|
#
|
|
def defined_line
|
|
# Detect a -1 ip, which indicates a definition entry.
|
|
return @lines[1] if @lines[0] == -1
|
|
first_line
|
|
end
|
|
|
|
##
|
|
#
|
|
# Given all CompiledCodes in the system, find one that
|
|
# was defined in file +file+ and encompasses +line+
|
|
#
|
|
def self.locate(file, line=nil)
|
|
file = StringValue file
|
|
|
|
if line
|
|
line = Integer line
|
|
elsif m = /\A(.*):(\d+)\Z/.match(file)
|
|
file = m[1]
|
|
line = m[2].to_i
|
|
end
|
|
|
|
ary = []
|
|
ObjectSpace.find_object([:kind_of, Rubinius::CompiledCode], ary)
|
|
|
|
methods = ary.find_all do |x|
|
|
x.scope and path = x.scope.absolute_active_path and \
|
|
path.suffix?(file)
|
|
end
|
|
|
|
return methods unless line
|
|
|
|
methods.find_all { |x| x.first_ip_on_line(line) }
|
|
end
|
|
|
|
##
|
|
# Is this actually a block of code?
|
|
#
|
|
# @return [Boolean]
|
|
def is_block?
|
|
get_metadata(:for_block)
|
|
end
|
|
|
|
def for_eval?
|
|
get_metadata(:for_eval)
|
|
end
|
|
|
|
def for_module_body?
|
|
get_metadata(:for_module_body)
|
|
end
|
|
|
|
def describe
|
|
str = "method #{@name}: #{@total_args} arg(s), #{@required_args} required"
|
|
if @splat
|
|
str << ", splatted."
|
|
end
|
|
str
|
|
end
|
|
|
|
##
|
|
# Convenience method to return an array of the child CompiledCodes from
|
|
# this CompiledCode's literals.
|
|
#
|
|
# @return [Tuple]
|
|
def child_methods
|
|
literals.select { |lit| lit.kind_of? CompiledCode }
|
|
end
|
|
|
|
def change_name(name)
|
|
code = dup
|
|
code.name = name
|
|
|
|
lits = Tuple.new(code.literals.size)
|
|
code.literals.each_with_index do |lit, idx|
|
|
if lit.kind_of? CompiledCode and lit.is_block?
|
|
lit = lit.change_name name
|
|
end
|
|
|
|
lits[idx] = lit
|
|
end
|
|
|
|
code.literals = lits
|
|
return code
|
|
end
|
|
|
|
##
|
|
# Locates the CompiledCode and instruction address (IP) of the first
|
|
# instruction on the specified line. This method recursively examines child
|
|
# compiled methods until an exact match for the searched line is found.
|
|
# It returns both the matching CompiledCode and the IP of the first
|
|
# instruction on the requested line, or nil if no match for the specified line
|
|
# is found.
|
|
#
|
|
# @return [(Rubinius::CompiledCode, Fixnum), NilClass] returns
|
|
# nil if nothing is found, else an array of size 2 containing the method
|
|
# the line was found in and the IP pointing there.
|
|
def locate_line(line)
|
|
i = 1
|
|
total = @lines.size
|
|
while i < total
|
|
cur_line = @lines.at(i)
|
|
if cur_line == line
|
|
ip = @lines.at(i-1)
|
|
return nil if ip < 0
|
|
return [self, ip]
|
|
elsif cur_line > line
|
|
break
|
|
end
|
|
|
|
i += 2
|
|
end
|
|
|
|
# Didn't find line in this CM, so check if a contained
|
|
# CM encompasses the line searched for
|
|
child_methods.each do |child|
|
|
if res = child.locate_line(line)
|
|
return res
|
|
end
|
|
end
|
|
|
|
# No child method is a match - fail
|
|
return nil
|
|
end
|
|
|
|
##
|
|
# Decodes the instruction sequence that is represented by this compileed
|
|
# method. Delegates to InstructionSequence to do the instruction decoding,
|
|
# but then converts opcode literal arguments to their actual values by looking
|
|
# them up in the literals tuple.
|
|
# Takes an optional bytecodes argument representing the bytecode that is to
|
|
# be decoded using this CompiledCode's locals and literals. This is provided
|
|
# for use by the debugger, where the bytecode sequence to be decoded may not
|
|
# exactly match the bytecode currently held by the CompiledCode, typically
|
|
# as a result of substituting yield_debugger instructions into the bytecode.
|
|
def decode(bytecodes = @iseq)
|
|
decoder = Rubinius::InstructionDecoder.new(bytecodes)
|
|
stream = decoder.decode(false)
|
|
ip = 0
|
|
|
|
stream.map do |inst|
|
|
instruct = Instruction.new(inst, self, ip)
|
|
ip += instruct.size
|
|
instruct
|
|
end
|
|
end
|
|
|
|
def add_metadata(key, val)
|
|
raise TypeError, "key must be a symbol" unless key.kind_of? Symbol
|
|
|
|
case val
|
|
when true, false, Symbol, Fixnum, String
|
|
# ok
|
|
else
|
|
raise TypeError, "invalid type of value"
|
|
end
|
|
|
|
@metadata ||= nil # to deal with MRI seeing @metadata as not set
|
|
|
|
unless @metadata
|
|
@metadata = Tuple.new(2)
|
|
@metadata[0] = key
|
|
@metadata[1] = val
|
|
return val
|
|
end
|
|
|
|
i = 0
|
|
fin = @metadata.size
|
|
|
|
while i < fin
|
|
if @metadata[i] == key
|
|
@metadata[i + 1] = val
|
|
return val
|
|
end
|
|
|
|
i += 2
|
|
end
|
|
|
|
tup = Tuple.new(fin + 2)
|
|
tup.copy_from @metadata, 0, fin, 0
|
|
tup[fin] = key
|
|
tup[fin + 1] = val
|
|
|
|
@metadata = tup
|
|
|
|
return val
|
|
end
|
|
|
|
def get_metadata(key)
|
|
return nil unless @metadata.kind_of? Tuple
|
|
|
|
i = 0
|
|
while i < @metadata.size
|
|
if @metadata[i] == key
|
|
return @metadata[i + 1]
|
|
end
|
|
|
|
i += 2
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
##
|
|
# For Method#parameters
|
|
def parameters
|
|
params = []
|
|
|
|
return params unless respond_to?(:local_names)
|
|
|
|
m = required_args - post_args
|
|
o = m + total_args - required_args
|
|
p = o + post_args
|
|
p += 1 if splat
|
|
|
|
local_names.each_with_index do |name, i|
|
|
if i < m
|
|
params << parameter(:req, name)
|
|
elsif i < o
|
|
params << [:opt, name]
|
|
elsif splat == i
|
|
params << parameter(:rest, name)
|
|
elsif i < p
|
|
params << parameter(:req, name)
|
|
elsif block_index == i
|
|
params << [:block, name]
|
|
end
|
|
end
|
|
|
|
params
|
|
end
|
|
|
|
def parameter(kind, param)
|
|
array = [kind]
|
|
array << param unless param == :* or param[0, 2] == "_:"
|
|
array
|
|
end
|
|
private :parameter
|
|
|
|
##
|
|
# Represents virtual machine's CPU instruction.
|
|
# Instructions are organized into instruction
|
|
# sequences known as iSeq, forming body
|
|
# of CompiledCodes.
|
|
#
|
|
# To generate VM opcodes documentation
|
|
# use rake doc:vm task.
|
|
class Instruction
|
|
class Association
|
|
def initialize(index)
|
|
@index = index
|
|
end
|
|
|
|
private :initialize
|
|
|
|
def inspect
|
|
"literals[#{@index}]"
|
|
end
|
|
end
|
|
|
|
class Location
|
|
FORMAT = "%04d:"
|
|
|
|
def initialize(location)
|
|
@location = location
|
|
end
|
|
|
|
private :initialize
|
|
|
|
def inspect
|
|
FORMAT % @location
|
|
end
|
|
end
|
|
|
|
def initialize(inst, code, ip)
|
|
@instruction = inst[0]
|
|
@args = inst[1..-1]
|
|
@comment = nil
|
|
|
|
@args.each_index do |i|
|
|
case @instruction.args[i]
|
|
when :literal
|
|
@args[i] = code.literals[@args[i]]
|
|
when :local
|
|
# TODO: Blocks should be able to retrieve local names as well,
|
|
# but need access to method corresponding to home context
|
|
if code.local_names and !code.is_block?
|
|
@comment = code.local_names[args[i]].to_s
|
|
end
|
|
when :association
|
|
@args[i] = Association.new(args[i])
|
|
when :location
|
|
@args[i] = Location.new(args[i])
|
|
end
|
|
end
|
|
|
|
@compiled_code = code
|
|
@ip = ip
|
|
end
|
|
|
|
private :initialize
|
|
|
|
# Instruction pointer
|
|
attr_reader :ip
|
|
|
|
##
|
|
# Return the line that this instruction is on in the method
|
|
#
|
|
def line
|
|
@compiled_code.line_from_ip(ip)
|
|
end
|
|
|
|
##
|
|
# Returns the OpCode object
|
|
#
|
|
attr_reader :instruction
|
|
|
|
##
|
|
# Returns the symbol representing the opcode for this instruction.
|
|
#
|
|
def opcode
|
|
@instruction.opcode
|
|
end
|
|
|
|
##
|
|
# Returns an array of 0 to 2 arguments, depending on the opcode.
|
|
#
|
|
attr_reader :args
|
|
|
|
##
|
|
# Returns a Fixnum indicating how wide the instruction takes up
|
|
# in the instruction stream
|
|
#
|
|
def size
|
|
@args.size + 1
|
|
end
|
|
|
|
##
|
|
# A nice human readable interpretation of this set of instructions
|
|
def to_s
|
|
str = "#{Location::FORMAT} %-27s" % [@ip, opcode]
|
|
str << @args.map{ |a| a.inspect }.join(', ')
|
|
if @comment
|
|
str << " # #{@comment}"
|
|
end
|
|
|
|
return str
|
|
end
|
|
end
|
|
end
|
|
end
|