From 5a5b016a7ec54179a36f43326210a117c90058ed Mon Sep 17 00:00:00 2001 From: Torsten Ruger Date: Sun, 25 May 2014 10:57:56 +0300 Subject: [PATCH] use crystal calling convention, documented in readme --- lib/arm/arm_machine.rb | 11 +++++++---- lib/ast/function_expression.rb | 4 ++-- lib/vm/README.markdown | 27 +++++++++++++++++++++++++++ lib/vm/call_site.rb | 2 +- lib/vm/function.rb | 17 ++++++++--------- 5 files changed, 45 insertions(+), 16 deletions(-) diff --git a/lib/arm/arm_machine.rb b/lib/arm/arm_machine.rb index de718372..5a30b8c9 100644 --- a/lib/arm/arm_machine.rb +++ b/lib/arm/arm_machine.rb @@ -62,8 +62,7 @@ module Arm # assumes string in r0 and r1 and moves them along for the syscall def write_stdout block block.instance_eval do - mov( :r2 , :r1 ) - mov( :r1 , :r0 ) + # TODO save and restore r0 mov( :r0 , 1 ) # 1 == stdout end syscall( block , 4 ) @@ -93,9 +92,13 @@ module Arm end def syscall block , num - block << mov( :r7 , num ) + sys_and_ret = Vm::Integer.new(7) + block << mov( sys_and_ret , num ) block << swi( 0 ) - Vm::Integer.new(0) #small todo, is this actually correct for all (that they return int) + #small todo, is this actually correct for all (that they return int) + block << mov( sys_and_ret , :r0 ) # syscall returns in r0, more to our return + #todo should write type into r0 according to syscall + sys_and_ret end end diff --git a/lib/ast/function_expression.rb b/lib/ast/function_expression.rb index 3c469f21..b3836682 100644 --- a/lib/ast/function_expression.rb +++ b/lib/ast/function_expression.rb @@ -22,7 +22,7 @@ module Ast locals = {} params.each_with_index do |param , index| arg = param.name - arg_value = Vm::Integer.new(index) + arg_value = Vm::Integer.new(index+1) locals[arg] = arg_value args << arg_value end @@ -42,7 +42,7 @@ module Ast raise "alarm #{last_compiled} \n #{b}" unless last_compiled.is_a? Vm::Word end - return_reg = Vm::Integer.new(0) + return_reg = Vm::Integer.new(7) if last_compiled.is_a?(Vm::IntegerConstant) or last_compiled.is_a?(Vm::StringConstant) return_reg.load into , last_compiled if last_compiled.register != return_reg.register else diff --git a/lib/vm/README.markdown b/lib/vm/README.markdown index eabe386d..3ef77453 100644 --- a/lib/vm/README.markdown +++ b/lib/vm/README.markdown @@ -7,3 +7,30 @@ Apart from shuffeling things around from one layer to the other, it keeps track provides the stack glue. All the stuff a compiler would usually do. Also all syscalls are abstracted as functions. + +The Crystal Convention +---------------------- + +Since we're not in c, we use the regsters more suitably for our job: + +- return register is _not_ the same as passing registers +- we pin one more register (ala stack/fp) for type information (this is used for returns too) +- one line (8 registers) can be used by a function (caller saved?) +- rest are scratch and may not hold values during call + +For Arm this works out as: +- 0 type word (for the line) +- 1-6 argument passing + workspace +- 7 return value + +This means syscalls (using 7 for call number and 0 for return) must shuffle a little, but there's space to do it. +Some more detail: + +1 - returning in the same register as passing makes that one register a special case, which i want to avoid. shuffling it gets tricky and involves 2 moves for what? +As i see it the benefitd of reusing the same register are one more argument register (not needed) and easy chaining of calls, which doen't really happen so much. +On the plus side, not using the same register makes saving and restoring registers easy (to implement and understand!). +An easy to understand policy is worth gold, as register mistakes are HARD to debug and not what i want to spend my time with just now. So that's settled. + +2 - Tagging integers like MRI/BB is a hack which does not extend to other types, such as floats. So we don't use that and instead carry type information externally to the value. This is a burden off course, but then so is tagging. +The convention (to make it easier) is to handle data in lines (8 words) and have one of them carry the type info for the other 7. This is also the object layout and so we reuse that code on the stack. + diff --git a/lib/vm/call_site.rb b/lib/vm/call_site.rb index e3ee25f7..115f1c56 100644 --- a/lib/vm/call_site.rb +++ b/lib/vm/call_site.rb @@ -16,7 +16,7 @@ module Vm if arg.is_a?(IntegerConstant) or arg.is_a?(StringConstant) function.args[index].load into , arg else - function.args[index].move( into, arg ) if arg.register != index + function.args[index].move( into, arg ) if arg.register != args[index].register end end end diff --git a/lib/vm/function.rb b/lib/vm/function.rb index 1af6e967..a62a38d2 100644 --- a/lib/vm/function.rb +++ b/lib/vm/function.rb @@ -34,9 +34,9 @@ module Vm args.each_with_index do |arg , i| if arg.is_a?(Value) @args[i] = arg - raise "arg in non std register #{arg.inspect}" unless i == arg.register + raise "arg in non std register #{arg.inspect}" unless (i+1) == arg.register else - @args[i] = arg.new(i) + @args[i] = arg.new(i+1) end end set_return return_type @@ -53,9 +53,9 @@ module Vm def set_return type_or_value @return_type = type_or_value || Vm::Integer if @return_type.is_a?(Value) - raise "return in non std register #{@return_type.inspect}" unless 0 == @return_type.register + raise "return in non std register #{@return_type.inspect}" unless 7 == @return_type.register else - @return_type = @return_type.new(0) + @return_type = @return_type.new(7) end end def arity @@ -64,22 +64,21 @@ module Vm def new_local type = Vm::Integer register = args.length + @locals.length - l = type.new(register) + l = type.new(register + 1) # one for the type register 0, TODO add type as arg0 implicitly + raise "the red flag #{inspect}" if l.register > 6 @locals << l l end def save_locals context , into save = args.collect{|a| a.register } + @locals.collect{|l| l.register} - save.delete_at(0) - into.push save + into.push(save) unless save.empty? end def restore_locals context , into #TODO assumes allocation in order, as the pop must be get regs in ascending order (also push) restore = args.collect{|a| a.register } + @locals.collect{|l| l.register} - restore.delete_at(0) - into.pop restore + into.pop(restore) unless restore.empty? end def new_block name