Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

974 lines (798 sloc) 23.662 kb
# A simple parser to transform a description of the VM instructions into
# various implementation files.
#
# The parser recognizes three forms presently:
#
# 1. define NAME <value>
# 2. section "Some description"
# 3. instruction opcode_name(arg1, arg2) [ consumed -- produced ]
# body
# end
#
# Once the forms are parsed, each type (a subclass of Definition) implements
# methods to emit its form in the context of a particular generator. In other
# words, the generated files are produced by a set of methods, each of which
# simply iterate the array of definitions in order calling a method for that
# generator.
#
# For example, to generate the opcode definitions file, the #opcode_definition
# method is called on each definition parsed from the 'instructions.def' file.
#
# The format of the instruction definition needs addition explanation. The
# instructions can take 0, 1, or 2 arguments. The arguments are listed in
# parenthesis after the instruction name.
#
# Each instruction has a stack effect, or the stack items consumed and the
# stack items produced. These are described in the square brackets after the
# argument list a la Forth. The '--' symbol separates the consumed from the
# produced.
#
# The names of the stack items are purely for documentation except in the
# special case of an item named like '+count'. This special form refers to one
# of the instruction's arguments and means that the total number of stack
# items consumed is the sum of the explicit stack items listed and the value
# of the instruction's argument.
#
# For example, the 'make_array' instruction consumes 'count' items on the
# stack, but no additional items. While the 'send_stack' instruction consumes
# the 'receiver' and 'count' additional items.
#
# Finally, for instructions that alter the normal sequential flow of execution,
# the type of control flow change will be specified in curly braces, e.g.
# {branch}. The control flow behavior defaults to next, but for those
# instructions where control is (or may) change, one of the following flow
# types must be specified:
# branch - for instructions that branch within the same method
# send - for instructions that invoke another method
# yield - for instructions that yield to a block
# return - for instructions that return to the calling method
# raise - for instructions that invoke exception handling
class InstructionParser
# Processes an opcode definition from the instruction parser into a method
# called by the bytecode compiler's Generator to emit the instruction
# stream.
class GeneratorMethod
def initialize(parser, file, opcode)
@parser = parser
@file = file
@opcode = opcode
@method_name = nil
@before_stream = nil
@after_stream = nil
@before_stack = nil
@after_stack = nil
end
def method_name
@method_name || @opcode.name
end
def process
__send__ :"process_#{@opcode.name}"
end
# Define the method signature.
def method_signature
case @opcode.arguments.size
when 0
@file.puts " def #{method_name}"
when 1
@file.puts " def #{method_name}(arg1)"
when 2
@file.puts " def #{method_name}(arg1, arg2)"
end
end
# Append to the opcode stream.
def method_append_stream
case @opcode.arguments.size
when 0
@file.puts " @stream << #{@opcode.bytecode}"
when 1
@file.puts " @stream << #{@opcode.bytecode} << arg1"
when 2
@file.puts " @stream << #{@opcode.bytecode} << arg1 << arg2"
end
@file.puts " @ip += #{@opcode.arguments.size + 1}"
end
# Calculate the full stack affect of this opcode.
def method_stack_effect
read = @opcode.static_read_effect
write = @opcode.static_write_effect
if @opcode.extra
if read > 0
read = "arg#{@opcode.extra+1}+#{read}"
else
read = "arg#{@opcode.extra+1}"
end
end
if @opcode.produced_extra
if write > 0
write = "(arg#{@opcode.produced_extra+1} * #{@opcode.produced_times})+#{write}"
else
write = "(arg#{@opcode.produced_extra+1} * #{@opcode.produced_times})"
end
end
@file.puts " @current_block.add_stack(#{read}, #{write})"
end
def method_close
@file.puts " @instruction = #{@opcode.bytecode}"
@file.puts " end"
@file.puts ""
end
def method_definition
method_signature
@before_stream.call if @before_stream
method_append_stream
@after_stream.call if @after_stream
@before_stack.call if @before_stack
method_stack_effect
@after_stack.call if @after_stack
method_close
end
def literal_method
@before_stream = lambda { @file.puts " arg1 = find_literal arg1" }
method_definition
end
def literal_count_method
@before_stream = lambda do
@file.puts " arg1 = find_literal arg1"
@file.puts " arg2 = Integer(arg2)"
end
method_definition
end
def method_missing(sym, *args)
method_definition
end
# Basic block creation, closing, transitions
def bb_unconditional_branch
@file.puts " @current_block.left = arg1.basic_block"
@file.puts " @current_block.close"
@file.puts " @current_block = new_basic_block"
end
def bb_conditional_branch
@file.puts " @current_block.left = arg1.basic_block"
@file.puts " @current_block.close"
@file.puts " block = new_basic_block"
@file.puts " @current_block.right = block"
@file.puts " @current_block = block"
end
def bb_exit(check_stack)
@file.puts " @current_block.close #{check_stack}"
@file.puts " @current_block = new_basic_block"
end
# Categories of instructions. These could be done based on the control
# flow attributes added to the instructions for the debugger, but that
# would require more complicated parsing and contortions to work around
# potential special cases. Instead, the simple #process_<opcode> method
# is used and redundancy is factored out as needed.
def unconditional_branch
@before_stream = lambda { @file.puts " location = @ip + 1" }
@after_stream = lambda { @file.puts " arg1.used_at location" }
@after_stack = lambda { bb_unconditional_branch }
method_definition
end
def conditional_branch
@before_stream = lambda { @file.puts " location = @ip + 1" }
@after_stream = lambda { @file.puts " arg1.used_at location" }
@after_stack = lambda { bb_conditional_branch }
method_definition
end
def unconditional_exit(check_stack=false)
@after_stack = lambda { bb_exit check_stack }
method_definition
end
# Specific instruction methods
def process_goto
unconditional_branch
end
def process_goto_if_false
conditional_branch
end
def process_goto_if_true
conditional_branch
end
def process_setup_unwind
conditional_branch
end
def process_ret
unconditional_exit true
end
def process_raise_exc
unconditional_exit
end
def process_raise_return
unconditional_exit true
end
def process_ensure_return
unconditional_exit true
end
def process_raise_break
unconditional_exit
end
def process_reraise
unconditional_exit
end
def process_push_int
method_signature
# Integers greater than 256 are stored in the literals tuple.
@file.puts <<EOM
if arg1 > 2 and arg1 < 256
@stream << #{@opcode.bytecode} << arg1
@current_block.add_stack(0, 1)
@ip += 2
@instruction = #{@opcode.bytecode}
else
case arg1
when -1
meta_push_neg_1
when 0
meta_push_0
when 1
meta_push_1
when 2
meta_push_2
else
push_literal arg1
end
end
end
EOM
end
def process_push_literal
@method_name = :emit_push_literal
method_definition
end
def process_dup_top
method_definition
end
def process_swap_stack
method_definition
end
def process_push_const
# unused right now
end
def process_find_const
literal_method
end
def process_push_ivar
literal_method
end
def process_set_ivar
literal_method
end
def process_check_serial
literal_count_method
end
def process_check_serial_private
literal_count_method
end
def process_create_block
@before_stream = lambda do
@file.puts " arg1 = add_literal arg1"
@file.puts " @generators << arg1"
end
method_definition
end
def process_invoke_primitive
literal_count_method
end
def process_call_custom
literal_count_method
end
def process_zsuper
literal_method
end
def process_cast_array
method_signature
make_array = @parser.find_opcode "make_array"
@file.puts " unless @instruction == #{@opcode.bytecode} or @instruction == #{make_array.bytecode}"
@file.puts " @stream << #{@opcode.bytecode}"
@file.puts " @ip += 1"
@file.puts " end"
method_close
end
end
class ParseError < Exception; end
class Definition
def opcode_definition(file)
end
def opcode_method(parser, file)
end
def opcode_name
end
def opcode_width
end
def opcode_prototype
end
def opcode_location
end
def opcode_implementation(file)
end
def opcode_enum
end
def opcode_define
end
def opcode_visitor
end
def opcode_stack_effect(file)
end
def opcode_documentation(file)
end
end
class Section < Definition
attr_reader :heading
def initialize(line, doc)
@heading = line.strip[1..-2]
end
def opcode_definition(file)
file.print "\n # "
file.puts @heading
end
end
class Define < Definition
attr_reader :name, :value
def initialize(line, doc)
@name, @value = line.strip.split
end
def opcode_definition(file)
file.puts
file.puts " #{@name} = #{@value}"
file.puts
end
def opcode_define
"#define #{@name} #{@value}"
end
end
class InstructionDocumentation
def initialize(instruction)
@instruction = instruction
@description = []
@consumed = nil
@produced = nil
@see_also = []
@notes = []
@example = []
end
def parse(text)
section = []
text.each do |line|
case line
when /\[Description\]/
section = @description
when /\[Stack Before\]/
section = @consumed = []
when /\[Stack After\]/
section = @produced = []
when /\[See Also\]/
section = @see_also
when /\[Notes\]/
section = @notes
when /\[Example\]/
section = @example
else
section << line[1..-1]
end
end
self
end
def consumed
# TODO: account for extra consumed
@consumed || @instruction.consumed + ["..."]
end
def produced
# TODO: account for extra produced
@produced || @instruction.produced + ["..."]
end
def format(file)
file.puts %[<h3><a class="instruction" name="#{name}">#{name}(#{arguments})</a></h3>]
end
def stack_effect(file)
c = consumed
p = produced
n = c.size > p.size ? c.size : p.size
file.puts %[\n<table class="stack_effect">]
file.puts "<thead>"
file.puts "<tr><th>Before</th><th>After</th></tr>"
file.puts "</thead>"
file.puts "<tbody>"
n.times do |i|
file.puts "<tr><td>#{c[i]}</td><td>#{p[i]}</td></tr>"
end
file.puts "</tbody>"
file.puts "</table>"
end
def description(file)
file.puts ""
file.puts @description
file.puts ""
end
def example(file)
return if @example.empty?
file.puts ""
file.puts '#### Example'
file.puts @example
file.puts ""
end
def see_also(file)
return if @see_also.empty?
file.puts "\n<h4>See Also</h4>"
file.puts %[<ul class="insn_cross_ref">]
@see_also.each do |x|
x = x.strip
file.puts %[<li><a href="\##{x}">#{x}</a></li>]
end
file.puts "</ul>"
end
def notes(file)
return if @notes.empty?
file.puts ""
file.puts '#### Notes'
file.puts @notes
file.puts ""
end
def name
@instruction.name
end
def arguments
@instruction.arguments.join ", "
end
def render(file)
format file
description file
stack_effect file
example file
notes file
see_also file
end
end
class Instruction < Definition
attr_reader :name, :bytecode, :arguments, :consumed, :extra,
:produced, :produced_extra, :effect, :body, :control_flow,
:produced_times
def self.bytecodes
@bytecodes
end
def self.bytecode
@bytecodes ||= -1
@bytecodes += 1
end
def initialize(parser, header, doc)
@parser = parser
@file = parser.file
@header = header
@body = []
@doc = InstructionDocumentation.new(self).parse(doc)
@extra = nil
@produced_extra = nil
@bytecode = self.class.bytecode
@control_flow = :next
end
def parse
parse_header
parse_body
end
def parse_header
m = @header.match(/(\w+)\(([^)]*)\) \[ ([\w+ ]*)-- ([\w+ ]*)\]( =>\s*(\w+))?/)
unless m
raise ParseError, "invalid instruction header '#{@header}' at #{@file.lineno}"
end
@name = m[1]
@arguments = m[2].strip.split
consumed = m[3].strip.split
last = consumed.last
if last and last[0] == ?+
argument = last[1..-1]
unless @extra = @arguments.index(argument)
raise ParseError, "no argument named '#{arg}' at #{@file.lineno}"
end
consumed.pop
end
@consumed = consumed
@produced = m[4].strip.split
last = @produced.last
if last and last[0] == ?+
@produced_times = 0
while last[0] == ?+
@produced_times += 1
last = last[1..-1]
end
unless @produced_extra = @arguments.index(argument)
raise ParseError, "no argument named '#{arg}' at #{@file.lineno}"
end
@produced.pop
end
@effect = @produced.size - @consumed.size
@control_flow = m[6].to_sym if m[6]
end
def static_read_effect
@consumed.size
end
def static_write_effect
@produced.size
end
def parse_body
while line = @file.gets
return if line =~ /^end\b/
@body << line
end
end
def opcode_definition(file)
if @extra
consumed = "[#{@consumed.size},#{@extra+1}]"
else
consumed = "#{@consumed.size}"
end
if @produced_extra
produced = "[#{@produced.size}, #{@produced_extra+1}, #{@produced_times}]"
else
produced = "#{@produced.size}"
end
file.print " opcode %2d, :%-28s " % [@bytecode, @name + ","]
stack = "[#{consumed}, #{produced}],"
file.print ":stack => %-12s" % stack
syms = @arguments.map { |x| ":#{x}" }.join(", ")
args = "[#{syms}],"
file.print ":args => %-20s" % args
file.puts " :control_flow => :#{@control_flow}"
end
def opcode_method(parser, file)
GeneratorMethod.new(parser, file, self).process
end
def opcode_name
"op_#{@name}"
end
def opcode_width
@arguments.size + 1
end
def opcode_prototype
name = opcode_name
indent = " " * (name.size + 9)
prototype = "Object* #{name}(rubinius::VM* state, rubinius::MachineCode* mcode,
#{indent}rubinius::InterpreterCallFrame* const call_frame,
#{indent}rubinius::MachineCode::InterpreterState& is"
unless @arguments.empty?
prototype << ",\n#{indent}"
prototype << @arguments.map { |a| "int #{a}" }.join(", ")
end
prototype << ");\n"
end
def opcode_enum
"insn_#{@name}"
end
def opcode_location
"op_impl_#{@name}"
end
def opcode_implementation(file)
file.puts " #{opcode_location}: {"
@arguments.each do |arg|
file.puts " intptr_t #{arg} = next_int;"
end
@body.each do |line|
file.puts " #{line}".rstrip
end
end
def opcode_visitor
"HANDLE_INST#{@arguments.size}(#{@bytecode}, #{@name});"
end
def opcode_stack_effect(file)
file.puts "case InstructionSequence::insn_#{@name}:"
read = static_read_effect
write = static_write_effect
if @extra
if read > 0
read = "operand#{@extra+1}+#{read}"
else
read = "operand#{@extra+1}"
end
end
if @produced_extra
if write > 0
write = "(operand#{@produced_extra+1} * #{@produced_times})+#{write}"
else
write = "(operand#{@produced_extra+1} * #{@produced_times})"
end
end
file.puts "if(read_effect) { *read_effect = (#{read}); }"
file.puts "if(write_effect) { *write_effect = (#{write}); }"
file.puts "return (#{write}) - (#{read});"
end
def opcode_documentation(file)
@doc.render file
end
end
# InstructionParser methods
attr_reader :file, :objects
def initialize(filename)
@filename = filename
@objects = []
@parsed = false
@blank_line = false
@doc = []
end
def process_define(parser, line, doc)
parser.objects << Define.new(line, doc)
end
def process_section(parser, line, doc)
parser.objects << Section.new(line, doc)
end
def process_instruction(parser, line, doc)
insn = Instruction.new parser, line, doc
insn.parse
parser.objects << insn
end
def parse
return if @parsed
File.open @filename, "rb" do |file|
@file = file
while line = @file.gets
if index = line.index("#")
if index == 0
if @blank_line
@doc.clear
@blank_line = false
end
@doc << line
end
line = line[0, index]
elsif line.strip.empty?
@blank_line = true
next
end
if m = line.match(/^(instruction|section|define) +(.*)$/)
send :"process_#{m[1]}", self, m[2], @doc
@doc.clear
end
end
end
@parsed = true
end
def find_opcode(name)
@objects.find { |x| x.kind_of? Instruction and x.name == name }
end
# Generated output methods
def generate_opcodes(filename)
File.open filename, "wb" do |file|
file.puts "# *** This file is generated by InstructionParser ***"
file.puts
file.puts "module Rubinius"
file.puts " class InstructionSet"
objects.each { |obj| obj.opcode_definition file }
file.puts " end"
file.puts "end"
end
end
def generate_generator_methods(filename)
File.open filename, "wb" do |file|
file.puts "# *** This file is generated by InstructionParser ***"
file.puts
file.puts "module Rubinius"
file.puts " module GeneratorMethods"
objects.each { |obj| obj.opcode_method self, file }
file.puts " end"
file.puts "end"
end
end
def generate_names(filename)
File.open filename, "wb" do |file|
file.puts "const char *rubinius::InstructionSequence::get_instruction_name(int op) {"
file.puts " static const char instruction_names[] = {"
offset = 0
offsets = []
objects.each do |obj|
if name = obj.opcode_name
file.puts " \"#{name}\\0\""
offsets << offset
offset += name.size + 1
end
end
file.puts " };"
file.puts
file.puts " static const unsigned int instruction_name_offsets[] = {"
file.print " "
file.puts offsets.join(",\n ")
file.puts " };"
file.puts
file.puts " return instruction_names + instruction_name_offsets[op];"
file.puts "}"
end
end
def generate_names_header(filename)
File.open filename, "wb" do |file|
file.puts "static const char *get_instruction_name(int op);"
file.puts "typedef enum {"
objects.each do |obj|
if enum = obj.opcode_enum
file.puts " #{enum} = #{obj.bytecode},"
end
end
file.puts "} instruction_names;"
file.puts "const static unsigned int cTotal = #{Instruction.bytecodes};"
end
end
def generate_prototypes(filename)
File.open filename, "wb" do |file|
file.puts "extern \"C\" {"
objects.each do |obj|
if prototype = obj.opcode_prototype
file.puts prototype
end
end
file.puts "} // extern \"C\""
end
end
def generate_sizes(filename)
File.open filename, "wb" do |file|
file.puts "size_t width = 0;"
file.puts "switch(op) {"
objects.each do |obj|
if width = obj.opcode_width
file.puts " case #{obj.bytecode}:"
file.puts " width = #{width}; break;"
end
end
file.puts "}"
end
end
def generate_locations(filename)
File.open filename, "wb" do |file|
file.puts " static const void* insn_locations[] = {"
objects.each do |obj|
if location = obj.opcode_location
file.puts " &&#{location},"
end
end
file.puts " NULL"
file.puts " };"
end
end
def generate_implementations(filename)
File.open filename, "wb" do |file|
file.puts " DISPATCH;"
objects.each do |obj|
if obj.opcode_implementation(file)
file.puts " DISPATCH;"
file.puts " }"
end
end
end
end
def generate_defines(filename)
File.open filename, "wb" do |file|
objects.each do |obj|
if define = obj.opcode_define
file.puts define
end
end
end
end
def generate_visitors(filename)
File.open filename, "wb" do |file|
objects.each do |obj|
if visitor = obj.opcode_visitor
file.puts visitor
end
end
end
end
def generate_stack_effects(filename)
File.open filename, "wb" do |file|
file.puts "static inline int stack_difference(opcode op,"
file.puts " opcode operand1 = 0,"
file.puts " opcode operand2 = 0,"
file.puts " int* read_effect = 0, int* write_effect = 0)"
file.puts "{"
file.puts " switch(op) {"
objects.each do |obj|
obj.opcode_stack_effect file
end
file.puts " }"
file.puts " abort();"
file.puts " return 0;"
file.puts "}"
end
end
def generate_documentation(filename)
File.open filename, "wb" do |file|
objects.each do |obj|
obj.opcode_documentation file
end
end
end
end
Jump to Line
Something went wrong with that request. Please try again.