move the code insertion functionality up to function. makes more sense. block still carries code though

This commit is contained in:
Torsten Ruger 2014-06-10 23:57:56 +03:00
parent e9fc8ac6aa
commit e9519d4f05
15 changed files with 130 additions and 115 deletions

View File

@ -25,74 +25,72 @@ module Arm
end
def integer_equals block , left , right
block << cmp( left , right )
block.add_code cmp( left , right )
Vm::BranchCondition.new :eq
end
def integer_less_or_equal block , left , right
block << cmp( left , right )
block.add_code cmp( left , right )
Vm::BranchCondition.new :le
end
def integer_greater_or_equal block , left , right
block << cmp( left , right )
block.add_code cmp( left , right )
Vm::BranchCondition.new :ge
end
def integer_less_than block , left , right
block << cmp( left , right )
block.add_code cmp( left , right )
Vm::BranchCondition.new :lt
end
def integer_greater_than block , left , right
block << cmp( left , right )
block.add_code cmp( left , right )
Vm::BranchCondition.new :gt
end
# TODO wrong type, should be object_reference. But that needs the actual typing to work
def integer_at_index block , result ,left , right
block << ldr( result , left , right )
block.add_code ldr( result , left , right )
result
end
def integer_plus block , result , left , right
block << add( result , left , right )
block.add_code add( result , left , right )
result
end
def integer_minus block , result , left , right
block << sub( result , left , right )
block.add_code sub( result , left , right )
result
end
def integer_left_shift block , result , left , right
block << mov( result , left , shift_lsr: right )
block.add_code mov( result , left , shift_lsr: right )
result
end
def function_call into , call
raise "Not CallSite #{call.inspect}" unless call.is_a? Vm::CallSite
raise "Not linked #{call.inspect}" unless call.function
into << call( call.function )
into.add_code call( call.function )
raise "No return type for #{call.function.name}" unless call.function.return_type
call.function.return_type
end
def main_start entry
entry << mov( :fp , 0 )
entry.do_add mov( :fp , 0 )
end
def main_exit exit
syscall(exit , 1)
exit
end
def function_entry block, f_name
block << push( [:lr] )
block.do_add push( [:lr] )
end
def function_exit entry , f_name
entry << pop( [:pc] )
entry.do_add pop( [:pc] )
end
# assumes string in r0 and r1 and moves them along for the syscall
def write_stdout block
block.instance_eval do
# TODO save and restore r0
mov( :r0 , 1 ) # 1 == stdout
end
# TODO save and restore r0
block.do_add mov( :r0 , 1 ) # 1 == stdout
syscall( block , 4 )
end
@ -122,9 +120,9 @@ module Arm
def syscall block , num
#small todo, is this actually correct for all (that they return int)
sys_and_ret = Vm::Integer.new( Vm::RegisterMachine.instance.return_register )
block << mov( sys_and_ret , num )
block << swi( 0 )
block << mov( sys_and_ret , return_register ) # syscall returns in r0, more to our return
block.do_add mov( sys_and_ret , num )
block.do_add swi( 0 )
block.do_add mov( sys_and_ret , return_register ) # syscall returns in r0, more to our return
#todo should write type into r0 according to syscall
sys_and_ret
end

View File

@ -4,7 +4,7 @@ module Ast
class IntegerExpression < Expression
# attr_reader :value
def compile context , into
def compile context
Vm::IntegerConstant.new value
end
end
@ -13,7 +13,7 @@ module Ast
# attr_reader :name
# compiling a variable resolves it. if it wasn't defined, raise an exception
def compile context , into
def compile context
raise "Undefined variable #{name}, defined locals #{context.locals.keys.join('-')}" unless context.locals.has_key?(name)
context.locals[name]
end
@ -21,7 +21,7 @@ module Ast
class ModuleName < NameExpression
def compile context , into
def compile context
clazz = context.object_space.get_or_create_class name
raise "uups #{clazz}.#{name}" unless clazz
#class qualifier, means call from metaclass
@ -34,7 +34,7 @@ module Ast
class StringExpression < Expression
# attr_reader :string
def compile context , into
def compile context
value = Vm::StringConstant.new(string)
context.object_space.add_object value
value

View File

@ -4,14 +4,15 @@ module Ast
class CallSiteExpression < Expression
# attr_reader :name, :args , :receiver
@@counter = 0
def compile context , into
params = args.collect{ |a| a.compile(context, into) }
def compile context
into = context.function
params = args.collect{ |a| a.compile(context) }
if receiver.is_a?(NameExpression) and (receiver.name == :self)
function = context.current_class.get_or_create_function(name)
value_receiver = Vm::Integer.new(Vm::RegisterMachine.instance.receiver_register)
else
value_receiver = receiver.compile(context , into)
value_receiver = receiver.compile(context)
function = context.current_class.get_or_create_function(name)
end
# this lot below should go, since the compile should handle all
@ -25,9 +26,10 @@ module Ast
into.push([]) unless current_function.nil?
call.load_args into
call.do_call into
after = into.new_block("#{into.name}_call#{@@counter+=1}")
after = into.new_block("call#{@@counter+=1}")
into.insert_at after
after.pop([]) unless current_function.nil?
into.pop([]) unless current_function.nil?
puts "compile call #{function.return_type}"
function.return_type
end

View File

@ -1,8 +1,7 @@
module Ast
class FunctionExpression < Expression
# attr_reader :name, :params, :body , :receiver
def compile context , into
raise "function does not compile into anything #{self}" if into
def compile context
args = []
locals = {}
params.each_with_index do |param , index|
@ -33,7 +32,7 @@ module Ast
last_compiled = nil
body.each do |b|
puts "compiling in function #{b}"
last_compiled = b.compile(context , into)
last_compiled = b.compile(context)
raise "alarm #{last_compiled} \n #{b}" unless last_compiled.is_a? Vm::Word
end

View File

@ -1,32 +1,36 @@
module Ast
class IfExpression < Expression
# attr_reader :cond, :if_true, :if_false
def compile context , into
def compile context
f = context.function
# to execute the logic as the if states it, the blocks are the other way around
# so we can the jump over the else if true ,and the else joins unconditionally after the true_block
false_block = into.new_block "#{into.name}_if_false"
true_block = false_block.new_block "#{into.name}_if_true"
merge_block = true_block.new_block "#{into.name}_if_merge"
false_block = f.new_block "if_false"
true_block = f.new_block "if_true"
merge_block = f.new_block "if_merge"
puts "compiling if condition #{cond}"
cond_val = cond.compile(context , into)
into.b true_block , condition_code: cond_val.operator
into.branch = true_block
cond_val = cond.compile(context)
f.b true_block , condition_code: cond_val.operator
f.insertion_point.branch = true_block
f.insert_at false_block
if_false.each do |part|
puts "compiling in if false #{part}"
last = part.compile(context , false_block )
last = part.compile(context )
end
false_block.b merge_block
f.b merge_block
f.insertion_point.branch = false_block
f.insert_at true_block
last = nil
if_true.each do |part|
puts "compiling in if true #{part}"
last = part.compile(context , true_block )
last = part.compile(context )
end
puts "compiled if: end"
into.insert_at merge_block
f.insert_at merge_block
return last
end

View File

@ -1,7 +1,7 @@
module Ast
class ModuleExpression < Expression
# attr_reader :name ,:expressions
def compile context , into
def compile context
clazz = context.object_space.get_or_create_class name
puts "Created class #{clazz.name.inspect}"
context.current_class = clazz
@ -10,7 +10,7 @@ module Ast
# if not, execute it, but that does means we should be in crystal (executable), not ruby. ie throw an error for now
raise "only functions for now #{expression.inspect}" unless expression.is_a? Ast::FunctionExpression
puts "compiling expression #{expression}"
expression_value = expression.compile(context , nil )
expression_value = expression.compile(context )
#puts "compiled expression #{expression_value.inspect}"
end

View File

@ -1,9 +1,10 @@
module Ast
class OperatorExpression < Expression
# attr_reader :operator, :left, :right
def compile context , into
def compile context
into = context.function
puts "compiling operator #{to_s}"
r_val = right.compile(context , into)
r_val = right.compile(context)
#puts "compiled right #{r_val.inspect}"
if operator == "=" # assignment, value based
raise "Can only assign variables, not #{left}" unless left.is_a?(NameExpression)
@ -18,7 +19,7 @@ module Ast
return l_val
end
l_val = left.compile(context , into)
l_val = left.compile(context)
case operator
when ">"
code = l_val.greater_than into , r_val

View File

@ -1,9 +1,9 @@
module Ast
class ReturnExpression < Expression
# attr_reader :expression
def compile context , into
def compile context
puts "compiling return expression #{expression}, now return in return_regsiter"
expression_value = expression.compile(context , into)
expression_value = expression.compile(context)
# copied from function expression: TODO make function
return_reg = Vm::Integer.new(Vm::RegisterMachine.instance.return_register)

View File

@ -1,18 +1,21 @@
module Ast
class WhileExpression < Expression
# attr_reader :condition, :body
def compile context , into
while_block = into.new_block "#{into.name}_while"
ret = while_block.new_block "#{into.name}_return"
def compile context
into = context.function
while_block = into.new_block "while"
ret = while_block.new_block "return"
into.insert_at while_block
puts "compiling while condition #{condition}"
cond_val = condition.compile(context , while_block)
cond_val = condition.compile(context)
while_block.b ret , condition_code: cond_val.not_operator
while_block.branch = ret
last = nil
body.each do |part|
puts "compiling in while #{part}"
last = part.compile(context , while_block )
last = part.compile(context)
end
while_block.b while_block
puts "compile while end"

View File

@ -23,13 +23,13 @@ module Boot
var_name = get_function.args.first
return_to = get_function.return_type
index_function = context.object_space.get_or_create_class(:Object).get_or_create_function(:index_of)
body = get_function.body
body.push( [me] )
body.call( index_function )
after_body = body.new_block("#{body.name}_a")
body.insert_at after_body
after_body.pop([me])
return_to.at_index( after_body , me , return_to )
get_function.push( [me] )
get_function.call( index_function )
after_body = get_function.new_block("#{get_function.insertion_point.name}_a")
get_function.insert_at after_body
get_function.pop([me])
return_to.at_index( get_function , me , return_to )
get_function.set_return return_to
return get_function
end

View File

@ -27,7 +27,6 @@ module Vm
@next = next_block
@branch = nil
@codes = []
@insert_at = self
# keeping track of register usage, left (assigns) or right (uses)
@assigns = []
@uses = []
@ -43,60 +42,16 @@ module Vm
ret
end
def add_code(kode)
raise "alarm #{kode}" if kode.is_a? Word
raise "alarm #{kode.class} #{kode}" unless kode.is_a? Code
@insert_at.do_add kode
self
end
def do_add kode
kode.assigns.each { |a| (@assigns << a) unless @assigns.include?(a) }
kode.uses.each { |use| (@uses << use) unless (@assigns.include?(use) or @uses.include?(use)) }
#puts "IN ADD #{name}#{uses}"
@codes << kode
end
alias :<< :add_code
# create a new linear block after this block. Linear means there is no brach needed from this one
# to the new one. Usually the new one just serves as jump address for a control statement
# In code generation (assembly) , new new_block is written after this one, ie zero runtime cost
def new_block new_name
new_b = Block.new( new_name , @function , @insert_at.next )
@insert_at.set_next new_b
return new_b
end
def set_next next_b
@next = next_b
end
# when control structures create new blocks (with new_block) control continues at some new block the
# the control structure creates.
# Example: while, needs 2 extra blocks
# 1 condition code, must be its own blockas we jump back to it
# - the body, can actually be after the condition as we don't need to jump there
# 2 after while block. Condition jumps here
# After block 2, the function is linear again and the calling code does not need to know what happened
# But subsequent statements are still using the original block (self) to add code to
# So the while expression creates the extra blocks, adds them and the code and then "moves" the insertion point along
def insert_at block
@insert_at = block
self
end
# sugar to create instructions easily.
# any method will be passed on to the RegisterMachine and the result added to the block
# With this trick we can write what looks like assembler,
# Example b.instance_eval
# mov( r1 , r2 )
# add( r1 , r2 , 4)
# end
# mov and add will be called on Machine and generate Inststuction that are then added
# to the block
# also symbols are supported and wrapped as register usages (for bare metal programming)
def method_missing(meth, *args, &block)
add_code RegisterMachine.instance.send(meth , *args)
end
# returns if this is a block that ends in a call (and thus needs local variable handling)
def call_block?

View File

@ -51,6 +51,7 @@ module Vm
@exit = Core::Kernel::function_exit( Vm::Block.new("exit" , self , nil) , name )
@return = Block.new("return", self , @exit)
@body = Block.new("body", self , @return)
@insert_at = @body
@entry = Core::Kernel::function_entry( Vm::Block.new("entry" , self , @body) ,name )
@locals = []
@linked = false # incase link is called twice, we only calculate locals once
@ -58,6 +59,9 @@ module Vm
attr_reader :args , :entry , :exit , :body , :name , :return_type , :receiver
def insertion_point
@insert_at
end
def set_return type_or_value
@return_type = type_or_value || Vm::Integer
if @return_type.is_a?(Value)
@ -104,6 +108,55 @@ module Vm
ret
end
# when control structures create new blocks (with new_block) control continues at some new block the
# the control structure creates.
# Example: while, needs 2 extra blocks
# 1 condition code, must be its own blockas we jump back to it
# - the body, can actually be after the condition as we don't need to jump there
# 2 after while block. Condition jumps here
# After block 2, the function is linear again and the calling code does not need to know what happened
# But subsequent statements are still using the original block (self) to add code to
# So the while expression creates the extra blocks, adds them and the code and then "moves" the insertion point along
def insert_at block
@insert_at = block
self
end
# create a new linear block after the current insertion block.
# Linear means there is no brach needed from that one to the new one.
# Usually the new one just serves as jump address for a control statement
# In code generation (assembly) , new new_block is written after this one, ie zero runtime cost
# This does _not_ change the insertion point, that has do be done with insert_at(block)
def new_block new_name
block_name = "#{@insert_at.name}_#{new_name}"
new_b = Block.new( block_name , @function , @insert_at.next )
@insert_at.set_next new_b
return new_b
end
def add_code(kode)
raise "alarm #{kode}" if kode.is_a? Word
raise "alarm #{kode.class} #{kode}" unless kode.is_a? Code
@insert_at.do_add kode
self
end
# sugar to create instructions easily.
# any method will be passed on to the RegisterMachine and the result added to the insertion block
# With this trick we can write what looks like assembler,
# Example func.instance_eval
# mov( r1 , r2 )
# add( r1 , r2 , 4)
# end
# mov and add will be called on Machine and generate Inststuction that are then added
# to the current block
# also symbols are supported and wrapped as register usages (for bare metal programming)
def method_missing(meth, *args, &block)
puts "passing #{meth} , #{args.length} #{args}"
add_code RegisterMachine.instance.send(meth , *args)
end
# following id the Code interface
# to link we link the entry and then any blocks. The entry links the straight line

View File

@ -24,9 +24,9 @@ module Fragments
# and the last is wrapped as a main
parts.each_with_index do |part,index|
if part.is_a? Ast::FunctionExpression
expr = part.compile( @object_space.context , nil )
expr = part.compile( @object_space.context )
else
expr = part.compile( @object_space.context , @object_space.main )
expr = part.compile( @object_space.context )
end
end
end

View File

@ -65,9 +65,9 @@ HERE
# and the last is wrapped as a main
parts.each_with_index do |part,index|
if index == (parts.length - 1)
expr = part.compile( @object_space.context , @object_space.main )
expr = part.compile( @object_space.context )
else
expr = part.compile( @object_space.context , nil )
expr = part.compile( @object_space.context )
raise "should be function definition for now, not #{part.inspect}#{expr.inspect}" unless expr.is_a? Vm::BootClass
end
end

View File

@ -28,9 +28,9 @@ class TestRunner < MiniTest::Test
# and the last is wrapped as a main
parts.each_with_index do |part,index|
if index == (parts.length - 1)
expr = part.compile( program.context , program.main )
expr = part.compile( program.context )
else
expr = part.compile( program.context , nil )
expr = part.compile( program.context )
raise "should be function definition for now" unless expr.is_a? Vm::Function
end
end