move the code insertion functionality up to function. makes more sense. block still carries code though
This commit is contained in:
parent
e9fc8ac6aa
commit
e9519d4f05
@ -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
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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?
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user