renames Typed to Vm

This commit is contained in:
Torsten Ruger
2017-01-14 19:28:44 +02:00
parent 75c7ca950e
commit bd78a2d555
95 changed files with 61 additions and 61 deletions

View 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.

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@ -0,0 +1,7 @@
module Vm
module StatementList
def on_Statements statement
process_all( statement.statements )
end
end
end

View 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