use crystal calling convention, documented in readme

This commit is contained in:
Torsten Ruger 2014-05-25 10:57:56 +03:00
parent 5fb1d9825a
commit 5a5b016a7e
5 changed files with 45 additions and 16 deletions

View File

@ -62,8 +62,7 @@ module Arm
# assumes string in r0 and r1 and moves them along for the syscall # assumes string in r0 and r1 and moves them along for the syscall
def write_stdout block def write_stdout block
block.instance_eval do block.instance_eval do
mov( :r2 , :r1 ) # TODO save and restore r0
mov( :r1 , :r0 )
mov( :r0 , 1 ) # 1 == stdout mov( :r0 , 1 ) # 1 == stdout
end end
syscall( block , 4 ) syscall( block , 4 )
@ -93,9 +92,13 @@ module Arm
end end
def syscall block , num def syscall block , num
block << mov( :r7 , num ) sys_and_ret = Vm::Integer.new(7)
block << mov( sys_and_ret , num )
block << swi( 0 ) 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
end end

View File

@ -22,7 +22,7 @@ module Ast
locals = {} locals = {}
params.each_with_index do |param , index| params.each_with_index do |param , index|
arg = param.name arg = param.name
arg_value = Vm::Integer.new(index) arg_value = Vm::Integer.new(index+1)
locals[arg] = arg_value locals[arg] = arg_value
args << arg_value args << arg_value
end end
@ -42,7 +42,7 @@ module Ast
raise "alarm #{last_compiled} \n #{b}" unless last_compiled.is_a? Vm::Word raise "alarm #{last_compiled} \n #{b}" unless last_compiled.is_a? Vm::Word
end 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) 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 return_reg.load into , last_compiled if last_compiled.register != return_reg.register
else else

View File

@ -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. provides the stack glue. All the stuff a compiler would usually do.
Also all syscalls are abstracted as functions. 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.

View File

@ -16,7 +16,7 @@ module Vm
if arg.is_a?(IntegerConstant) or arg.is_a?(StringConstant) if arg.is_a?(IntegerConstant) or arg.is_a?(StringConstant)
function.args[index].load into , arg function.args[index].load into , arg
else 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 end
end end

View File

@ -34,9 +34,9 @@ module Vm
args.each_with_index do |arg , i| args.each_with_index do |arg , i|
if arg.is_a?(Value) if arg.is_a?(Value)
@args[i] = arg @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 else
@args[i] = arg.new(i) @args[i] = arg.new(i+1)
end end
end end
set_return return_type set_return return_type
@ -53,9 +53,9 @@ module Vm
def set_return type_or_value def set_return type_or_value
@return_type = type_or_value || Vm::Integer @return_type = type_or_value || Vm::Integer
if @return_type.is_a?(Value) 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 else
@return_type = @return_type.new(0) @return_type = @return_type.new(7)
end end
end end
def arity def arity
@ -64,22 +64,21 @@ module Vm
def new_local type = Vm::Integer def new_local type = Vm::Integer
register = args.length + @locals.length 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 @locals << l
l l
end end
def save_locals context , into def save_locals context , into
save = args.collect{|a| a.register } + @locals.collect{|l| l.register} save = args.collect{|a| a.register } + @locals.collect{|l| l.register}
save.delete_at(0) into.push(save) unless save.empty?
into.push save
end end
def restore_locals context , into def restore_locals context , into
#TODO assumes allocation in order, as the pop must be get regs in ascending order (also push) #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 = args.collect{|a| a.register } + @locals.collect{|l| l.register}
restore.delete_at(0) into.pop(restore) unless restore.empty?
into.pop restore
end end
def new_block name def new_block name