renames Typed to Vm
This commit is contained in:
57
lib/vm/method_compiler/README.md
Normal file
57
lib/vm/method_compiler/README.md
Normal file
@ -0,0 +1,57 @@
|
||||
### Compiling
|
||||
|
||||
The typed syntax tree is created by the ruby compiler.
|
||||
|
||||
The code in this directory compiles the typed tree to the register machine code, and
|
||||
Parfait object structure.
|
||||
|
||||
If this were an interpreter, we would just walk the tree and do what it says.
|
||||
Since it's not things are a little more difficult, especially in time.
|
||||
|
||||
When compiling we deal with two times, compile-time and run-time.
|
||||
All the headache comes from mixing those two up.*
|
||||
|
||||
Similarly, the result of compiling is two-fold: a static and a dynamic part.
|
||||
|
||||
- the static part are objects like the constants, but also defined classes and their methods
|
||||
- the dynamic part is the code, which is stored as streams of instructions in the MethodSource
|
||||
|
||||
Too make things a little simpler, we create a very high level instruction stream at first and then
|
||||
run transformation and optimization passes on the stream to improve it.
|
||||
|
||||
The compiler has a method for each class of typed tree, named along on_xxx with xxx as the type
|
||||
|
||||
#### Compiler holds scope
|
||||
|
||||
The Compiler instance can hold arbitrary scope needed during the compilation.
|
||||
A class statement sets the current @type scope , a method definition the @method.
|
||||
If either are not set when needed compile errors will follow. So easy, so nice.
|
||||
|
||||
All code is encoded as a stream of Instructions in the MethodSource.
|
||||
Instructions are stored as a list of Blocks, and Blocks are the smallest unit of code,
|
||||
which is always linear.
|
||||
|
||||
Code is added to the method (using add_code), rather than working with the actual instructions.
|
||||
This is so each compiling method can just do it's bit and be unaware of the larger structure
|
||||
that is being created.
|
||||
The general structure of the instructions is a graph
|
||||
(with if's and whiles and breaks and what), but we build it to have one start and *one* end (return).
|
||||
|
||||
|
||||
#### Messages and frames
|
||||
|
||||
Since the machine is oo we define it in objects.
|
||||
|
||||
Also it is important to define how instructions operate, which is is in a physical machine would
|
||||
be by changing the contents of registers or some stack.
|
||||
|
||||
Our machine is not a register machine, but an object machine: it operates directly on objects and
|
||||
also has no separate stack, only objects. There is only one object which is accessible,
|
||||
basically meaning pinned to a register, the Message.
|
||||
|
||||
One can think of the Message as an oo replacement of the stack.
|
||||
|
||||
When a TypedMethod needs to make a call, it creates a NewMessage object.
|
||||
Messages contain return addresses (yes, plural) and arguments.
|
||||
|
||||
The important thing here is that Messages and Frames are normal objects.
|
32
lib/vm/method_compiler/assignment.rb
Normal file
32
lib/vm/method_compiler/assignment.rb
Normal file
@ -0,0 +1,32 @@
|
||||
module Vm
|
||||
module Assignment
|
||||
|
||||
def on_Assignment( statement )
|
||||
reset_regs # statements reset registers, ie have all at their disposal
|
||||
value = process(statement.value)
|
||||
raise "Not register #{v}" unless value.is_a?(Register::RegisterValue)
|
||||
name = check_name(statement.name.name)
|
||||
named_list = use_reg(:NamedList)
|
||||
if( index = @method.has_arg(name))
|
||||
type = :arguments
|
||||
value_type = @method.argument_type( index )
|
||||
else
|
||||
index = @method.has_local( name )
|
||||
type = :locals
|
||||
raise "must define variable #{statement.name.name} before using it in #{@method.inspect}" unless index
|
||||
value_type = @method.locals_type( index )
|
||||
end
|
||||
raise "Type mismatch for #{type} access #{value.type}!=#{value_type}" unless value.type == value_type
|
||||
add_slot_to_reg(statement , :message , type , named_list )
|
||||
add_reg_to_slot(statement , value , named_list , index + 1 ) # one for type
|
||||
end
|
||||
|
||||
# ensure the name given is not space and raise exception otherwise
|
||||
# return the name
|
||||
def check_name( name )
|
||||
raise "space is a reserved name" if name == :space
|
||||
name
|
||||
end
|
||||
|
||||
end
|
||||
end
|
52
lib/vm/method_compiler/basic_values.rb
Normal file
52
lib/vm/method_compiler/basic_values.rb
Normal file
@ -0,0 +1,52 @@
|
||||
module Vm
|
||||
# collection of the simple ones, int and strings and such
|
||||
|
||||
module BasicValues
|
||||
|
||||
# Constant expressions can by definition be evaluated at compile time.
|
||||
# But that does not solve their storage, ie they need to be accessible at runtime from _somewhere_
|
||||
# So expressions move the data into a Register.
|
||||
# All expressions return registers
|
||||
|
||||
# But in the future (in the one that holds great things) we optimize those unneccesay moves away
|
||||
|
||||
def on_IntegerExpression expression
|
||||
int = expression.value
|
||||
reg = use_reg :Integer , int
|
||||
add_load_constant( expression, int , reg )
|
||||
return reg
|
||||
end
|
||||
|
||||
def on_TrueExpression expression
|
||||
reg = use_reg :Boolean
|
||||
add_load_constant( expression, true , reg )
|
||||
return reg
|
||||
end
|
||||
|
||||
def on_FalseExpression expression
|
||||
reg = use_reg :Boolean
|
||||
add_load_constant( expression, false , reg )
|
||||
return reg
|
||||
end
|
||||
|
||||
def on_NilExpression expression
|
||||
reg = use_reg :NilClass
|
||||
add_load_constant( expression, nil , reg )
|
||||
return reg
|
||||
end
|
||||
|
||||
def on_StringExpression expression
|
||||
value = Parfait.new_word expression.value.to_sym
|
||||
reg = use_reg :Word
|
||||
Register.machine.constants << value
|
||||
add_load_constant( expression, value , reg )
|
||||
return reg
|
||||
end
|
||||
|
||||
def on_ClassExpression expression
|
||||
name = expression.value
|
||||
raise "No meta class #{name}"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
94
lib/vm/method_compiler/call_site.rb
Normal file
94
lib/vm/method_compiler/call_site.rb
Normal file
@ -0,0 +1,94 @@
|
||||
module Vm
|
||||
module CallSite
|
||||
|
||||
def on_CallSite( statement )
|
||||
# name_s , arguments , receiver = *statement
|
||||
raise "not inside method " unless @method
|
||||
reset_regs
|
||||
#move the new message (that we need to populate to make a call) to std register
|
||||
new_message = Register.resolve_to_register(:new_message)
|
||||
add_slot_to_reg(statement, :message , :next_message , new_message )
|
||||
me = get_me( statement )
|
||||
type = get_my_type(me)
|
||||
|
||||
method = type.get_method(statement.name)
|
||||
raise "Method not implemented #{type.inspect}.#{statement.name}" unless method
|
||||
|
||||
# move our receiver there
|
||||
add_reg_to_slot( statement , me , :new_message , :receiver)
|
||||
|
||||
set_message_details(method , statement , statement.arguments)
|
||||
set_arguments(method , statement.arguments)
|
||||
ret = use_reg( :Integer ) #FIXME real return type
|
||||
|
||||
Register.issue_call( self , method )
|
||||
|
||||
# the effect of the method is that the NewMessage Return slot will be filled, return it
|
||||
# but move it into a register too
|
||||
add_slot_to_reg(statement, :new_message , :return_value , ret )
|
||||
ret
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_me( statement )
|
||||
if statement.receiver
|
||||
me = process( statement.receiver )
|
||||
else
|
||||
me = use_reg @method.for_type
|
||||
add_slot_to_reg(statement, :message , :receiver , me )
|
||||
end
|
||||
me
|
||||
end
|
||||
def get_my_type( me )
|
||||
# now we have to resolve the method name (+ receiver) into a callable method
|
||||
case me.type
|
||||
when Parfait::Type
|
||||
type = me.type
|
||||
when Symbol
|
||||
type = Parfait.object_space.get_class_by_name(me.type).instance_type
|
||||
else
|
||||
raise me.inspect
|
||||
end
|
||||
raise "Not type #{type}" unless type.is_a? Parfait::Type
|
||||
type
|
||||
end
|
||||
|
||||
# load method name and set to new message (for exceptions/debug)
|
||||
def set_message_details( method , name_s , arguments )
|
||||
name = name_s.name
|
||||
name_tmp = use_reg(:Word)
|
||||
add_load_constant("#{name} load method name", name , name_tmp)
|
||||
add_reg_to_slot( "#{name} store method name" , name_tmp , :new_message , :name)
|
||||
# next arg type
|
||||
args_reg = use_reg(:Type , method.arguments )
|
||||
list_reg = use_reg(:NamedList , arguments )
|
||||
add_load_constant("#{name} load arguments type", method.arguments , args_reg)
|
||||
add_slot_to_reg( "#{name} get args from method" , :new_message , :arguments , list_reg )
|
||||
add_reg_to_slot( "#{name} store args type in args" , args_reg , list_reg , 1 )
|
||||
end
|
||||
|
||||
def set_arguments( method , arguments )
|
||||
# reset tmp regs for each and load result into new_message
|
||||
arg_type = method.arguments
|
||||
message = "Arg number mismatch, method=#{arg_type.instance_length - 1} , call=#{arguments.length}"
|
||||
raise message if (arg_type.instance_length - 1 ) != arguments.length
|
||||
arguments.each_with_index do |arg , i |
|
||||
store_arg_no(arguments , arg_type , arg , i + 1) #+1 for ruby(0 based)
|
||||
end
|
||||
end
|
||||
|
||||
def store_arg_no(arguments , arg_type , arg , i )
|
||||
reset_regs
|
||||
i = i + 1 # disregarding type field
|
||||
val = process( arg) # processing should return the register with the value
|
||||
raise "Not register #{val}" unless val.is_a?(Register::RegisterValue)
|
||||
#FIXME definately needs some tests
|
||||
raise "TypeMismatch calling with #{val.type} , instead of #{arg_type.type_at(i)}" if val.type != arg_type.type_at(i)
|
||||
list_reg = use_reg(:NamedList , arguments )
|
||||
add_slot_to_reg( "Set arg #{i}:#{arg}" , :new_message , :arguments , list_reg )
|
||||
# which we load int the new_message at the argument's index
|
||||
add_reg_to_slot( arg , val , list_reg , i ) #one for type and one for ruby
|
||||
end
|
||||
end
|
||||
end
|
14
lib/vm/method_compiler/collections.rb
Normal file
14
lib/vm/method_compiler/collections.rb
Normal file
@ -0,0 +1,14 @@
|
||||
module Vm
|
||||
|
||||
module Collections
|
||||
|
||||
# attr_reader :values
|
||||
def on_array statement, context
|
||||
end
|
||||
# attr_reader :key , :value
|
||||
def on_association context
|
||||
end
|
||||
def on_hash context
|
||||
end
|
||||
end
|
||||
end
|
27
lib/vm/method_compiler/field_access.rb
Normal file
27
lib/vm/method_compiler/field_access.rb
Normal file
@ -0,0 +1,27 @@
|
||||
module Vm
|
||||
module FieldAccess
|
||||
|
||||
def on_FieldAccess statement
|
||||
# receiver_ast , field_ast = *statement
|
||||
receiver = process(statement.receiver)
|
||||
|
||||
type = receiver.type
|
||||
if(type.is_a?(Symbol))
|
||||
type = Parfait.object_space.get_class_by_name(type).instance_type
|
||||
end
|
||||
field_name = statement.field.name
|
||||
|
||||
index = type.variable_index(field_name)
|
||||
raise "no such field:#{field_name} for class #{type.inspect}" unless index
|
||||
value = use_reg(type.type_at(index))
|
||||
|
||||
add_slot_to_reg(statement , receiver , index, value)
|
||||
|
||||
value
|
||||
end
|
||||
|
||||
def on_receiver expression
|
||||
process expression.first
|
||||
end
|
||||
end
|
||||
end
|
41
lib/vm/method_compiler/if_statement.rb
Normal file
41
lib/vm/method_compiler/if_statement.rb
Normal file
@ -0,0 +1,41 @@
|
||||
module Vm
|
||||
module IfStatement
|
||||
|
||||
# an if evaluates the condition and jumps to the true block if true
|
||||
# so the else block is automatically after that.
|
||||
# But then the else needs to jump over the true block unconditionally.
|
||||
def on_IfStatement( statement )
|
||||
# branch_type , condition , if_true , if_false = *statement
|
||||
|
||||
true_block = compile_if_condition( statement )
|
||||
merge = compile_if_false( statement )
|
||||
add_code true_block
|
||||
compile_if_true(statement)
|
||||
add_code merge
|
||||
nil # statements don't return anything
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def compile_if_condition( statement )
|
||||
reset_regs
|
||||
process(statement.condition)
|
||||
branch_class = Object.const_get "Register::Is#{statement.branch_type.capitalize}"
|
||||
true_block = Register.label(statement, "if_true")
|
||||
add_code branch_class.new( statement.condition , true_block )
|
||||
return true_block
|
||||
end
|
||||
def compile_if_true( statement )
|
||||
reset_regs
|
||||
process(statement.if_true)
|
||||
end
|
||||
|
||||
def compile_if_false( statement )
|
||||
reset_regs
|
||||
process(statement.if_false) if statement.if_false.statements
|
||||
merge = Register.label(statement , "if_merge")
|
||||
add_code Register::Branch.new(statement.if_false, merge )
|
||||
merge
|
||||
end
|
||||
end
|
||||
end
|
60
lib/vm/method_compiler/name_expression.rb
Normal file
60
lib/vm/method_compiler/name_expression.rb
Normal file
@ -0,0 +1,60 @@
|
||||
module Vm
|
||||
module NameExpression
|
||||
|
||||
# attr_reader :name
|
||||
# compiling name needs to check if it's a local variable
|
||||
# or an argument
|
||||
# whichever way this goes the result is stored in the return slot (as all compiles)
|
||||
def on_NameExpression statement
|
||||
name = statement.name
|
||||
[:self , :space , :message].each do |special|
|
||||
return send(:"load_special_#{special}" , statement ) if name == special
|
||||
end
|
||||
return load_argument(statement) if( @method.has_arg(name))
|
||||
load_local(statement)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_argument(statement)
|
||||
name = statement.name
|
||||
index = @method.has_arg(name)
|
||||
named_list = use_reg :NamedList
|
||||
ret = use_reg @method.argument_type(index)
|
||||
#puts "For #{name} at #{index} got #{@method.arguments.inspect}"
|
||||
add_slot_to_reg("#{statement} load args" , :message , :arguments, named_list )
|
||||
add_slot_to_reg("#{statement} load #{name}" , named_list , index + 1, ret )
|
||||
return ret
|
||||
end
|
||||
|
||||
def load_local( statement )
|
||||
name = statement.name
|
||||
index = @method.has_local( name )
|
||||
raise "must define variable '#{name}' before using it" unless index
|
||||
named_list = use_reg :NamedList
|
||||
add_slot_to_reg("#{name} load locals" , :message , :locals , named_list )
|
||||
ret = use_reg @method.locals_type( index )
|
||||
add_slot_to_reg("#{name} load from locals" , named_list , index + 1, ret )
|
||||
return ret
|
||||
end
|
||||
|
||||
def load_special_self(statement)
|
||||
ret = use_reg @type
|
||||
add_slot_to_reg("#{statement} load self" , :message , :receiver , ret )
|
||||
return ret
|
||||
end
|
||||
|
||||
def load_special_space(statement)
|
||||
space = Parfait.object_space
|
||||
reg = use_reg :Space , space
|
||||
add_load_constant( "#{statement} load space", space , reg )
|
||||
return reg
|
||||
end
|
||||
|
||||
def load_special_message(statement)
|
||||
reg = use_reg :Message
|
||||
add_transfer( "#{statement} load message", Register.message_reg , reg )
|
||||
return reg
|
||||
end
|
||||
end #module
|
||||
end
|
15
lib/vm/method_compiler/operator_expression.rb
Normal file
15
lib/vm/method_compiler/operator_expression.rb
Normal file
@ -0,0 +1,15 @@
|
||||
module Vm
|
||||
module OperatorExpression
|
||||
|
||||
def on_OperatorExpression statement
|
||||
# operator , left_e , right_e = *statement
|
||||
# left and right must be expressions. Expressions return a register when compiled
|
||||
left_reg = process(statement.left_expression)
|
||||
right_reg = process(statement.right_expression)
|
||||
raise "Not register #{left_reg}" unless left_reg.is_a?(Register::RegisterValue)
|
||||
raise "Not register #{right_reg}" unless right_reg.is_a?(Register::RegisterValue)
|
||||
add_code Register::OperatorInstruction.new(statement,statement.operator,left_reg,right_reg)
|
||||
return left_reg # though this has wrong value attached
|
||||
end
|
||||
end
|
||||
end
|
10
lib/vm/method_compiler/return_statement.rb
Normal file
10
lib/vm/method_compiler/return_statement.rb
Normal file
@ -0,0 +1,10 @@
|
||||
module Vm
|
||||
module ReturnStatement
|
||||
|
||||
def on_ReturnStatement statement
|
||||
reg = process(statement.return_value)
|
||||
add_reg_to_slot( statement, reg , :message , :return_value)
|
||||
nil # statements don't return
|
||||
end
|
||||
end
|
||||
end
|
7
lib/vm/method_compiler/statement_list.rb
Normal file
7
lib/vm/method_compiler/statement_list.rb
Normal file
@ -0,0 +1,7 @@
|
||||
module Vm
|
||||
module StatementList
|
||||
def on_Statements statement
|
||||
process_all( statement.statements )
|
||||
end
|
||||
end
|
||||
end
|
42
lib/vm/method_compiler/while_statement.rb
Normal file
42
lib/vm/method_compiler/while_statement.rb
Normal file
@ -0,0 +1,42 @@
|
||||
module Vm
|
||||
module WhileStatement
|
||||
|
||||
def on_WhileStatement statement
|
||||
#branch_type , condition , statements = *statement
|
||||
|
||||
condition_label = compile_while_preamble( statement ) #jump there
|
||||
|
||||
start = compile_while_body( statement )
|
||||
|
||||
# This is where the loop starts, though in subsequent iterations it's in the middle
|
||||
add_code condition_label
|
||||
|
||||
compile_while_condition( statement )
|
||||
|
||||
branch_class = Object.const_get "Register::Is#{statement.branch_type.capitalize}"
|
||||
# this is where the while ends and both branches meet
|
||||
add_code branch_class.new( statement.condition , start )
|
||||
|
||||
nil # statements don't return anything
|
||||
end
|
||||
private
|
||||
|
||||
def compile_while_preamble( statement )
|
||||
condition_label = Register.label(statement.condition , "condition_label")
|
||||
# unconditionally branch to the condition upon entering the loop
|
||||
add_code Register::Branch.new(statement.condition , condition_label)
|
||||
condition_label
|
||||
end
|
||||
def compile_while_body( statement )
|
||||
start = Register.label(statement , "while_start" )
|
||||
add_code start
|
||||
reset_regs
|
||||
process(statement.statements)
|
||||
start
|
||||
end
|
||||
def compile_while_condition( statement )
|
||||
reset_regs
|
||||
process(statement.condition)
|
||||
end
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user