rubyx/lib/arm/translator.rb
Torsten Ruger 8322fca7b3 give labels an integer that will end up being the position at runtime
Since integers are first class objects, we need to use an integer object
as the return address. The actual address can not be stored in an
instance variable since it is not an object.
The address is unique to the label and never changes after positioning
(using the int is next up)
2018-05-29 20:26:00 +03:00

165 lines
4.8 KiB
Ruby

module Arm
# A translator is cpu specific and translates from risc instructions to a given
# cpu. This one transltes to Arm Instructions.
class Translator
# translator should translate from register instructio set to it's own (arm eg)
# for each instruction we call the translator with translate_XXX
# with XXX being the class name.
# the result is replaced in the stream
def translate( instruction )
class_name = instruction.class.name.split("::").last
self.send( "translate_#{class_name}".to_sym , instruction)
end
def translate_Label( code )
Risc.label( code.source , code.name , code.integer)
end
# arm indexes are
# in bytes, so *4
# if an instruction is passed in we get the index with index function
def arm_index( index )
index = index.index if index.is_a?(Risc::Instruction)
raise "index error #{index}" if index < 0
index * 4
end
def translate_Transfer( code )
# Risc machine convention is from => to
# But arm has the receiver/result as the first
ArmMachine.mov( code.to , code.from)
end
def translate_SlotToReg( code )
ArmMachine.ldr( *slot_args_for(code) )
end
def translate_RegToSlot( code )
ArmMachine.str( *slot_args_for(code) )
end
def slot_args_for( code )
if(code.index.is_a? Numeric)
[ code.register , code.array , arm_index(code) ]
else
[ code.register , code.array , code.index , :shift_lsl => 2]
end
end
def byte_args_for( code )
args = slot_args_for( code )
args.pop if(code.index.is_a? Numeric)
args
end
def translate_ByteToReg( code )
ArmMachine.ldrb( *byte_args_for(code) )
end
def translate_RegToByte( code )
ArmMachine.strb( *byte_args_for(code) )
end
def translate_FunctionCall( code )
ArmMachine.b( code.method.binary )
end
def translate_FunctionReturn code
ArmMachine.mov( :pc , code.register)
end
def translate_LoadConstant( code )
constant = code.constant
constant = constant.to_cpu(self) if constant.is_a?(Risc::Label)
return ArmMachine.add( code.register , constant )
end
def translate_LoadData( code )
return ArmMachine.mov( code.register , code.constant )
end
def translate_OperatorInstruction( code )
left = code.left
right = code.right
case code.operator.to_s
when "+"
c = ArmMachine.add(left , left , right)
when "-"
c = ArmMachine.sub(left , left , right)
when "&"
c = ArmMachine.and(left , left , right)
when "|"
c = ArmMachine.orr(left , left , right)
when "*"
c = ArmMachine.mul(left , right , left) #arm rule about left not being result, lukily commutative
when ">>"
c = ArmMachine.mov(left , left , :shift_asr => right) #arm rule about left not being result, lukily commutative
when "<<"
c = ArmMachine.mov(left , left , :shift_lsl => right) #arm rule about left not being result, lukily commutative
else
raise "unimplemented '#{code.operator}' #{code}"
end
c
end
# This implements branch logic, which is simply assembler branch
#
# The only target for a call is a Block, so we just need to get the address for the code
# and branch to it.
def translate_Branch( code )
target = code.label.is_a?(Risc::Label) ? code.label.to_cpu(self) : code.label
ArmMachine.b( target )
end
def translate_IsPlus( code )
ArmMachine.bpl( code.label.to_cpu(self) )
end
def translate_IsMinus( code )
ArmMachine.bmi( code.label.to_cpu(self) )
end
def translate_IsZero( code )
ArmMachine.beq( code.label.to_cpu(self) )
end
def translate_IsNotZero( code )
ArmMachine.bne( code.label.to_cpu(self) )
end
def translate_IsOverflow( code )
ArmMachine.bvs( code.label.to_cpu(self))
end
def translate_Syscall( code )
call_codes = { :putstring => 4 , :exit => 1 }
int_code = call_codes[code.name]
raise "Not implemented syscall, #{code.name}" unless int_code
send( code.name , int_code )
end
def putstring( int_code )
codes = ArmMachine.add( :r1 , :r1 , 12 ) # adjust for object header
codes.append ArmMachine.mov( :r0 , 1 ) # write to stdout == 1
syscall(int_code , codes )
end
def exit( int_code )
codes = ArmMachine.ldr( :r0 , :r0 , arm_index(Risc.resolve_to_index(:Message , :return_value)) )
syscall int_code , codes
end
private
# syscall is always triggered by swi(0)
# The actual code (ie the index of the kernel function) is in r7
def syscall int_code , codes
codes.append ArmMachine.mov( :r7 , int_code )
codes.append ArmMachine.swi( 0 )
codes
end
end
end