rubyx/lib/risc/machine.rb
Torsten Ruger ad3040a846 add position_chaning to event interface
by reacting to the change _before it happens, we can move any BinaryCode out of the way

So when Instruction are inserted and code gets inserted, we don't need to set up the correct listener explicitly (which is tricky across mathods and changing chains), but instead just move anything that is in the way along
2018-06-09 08:10:41 +03:00

174 lines
5.9 KiB
Ruby

require_relative "collector"
require_relative "binary_writer"
module Risc
# The Risc Machine is an abstraction of the register level. This is seperate from the
# actual assembler level to allow for several cpu architectures.
# The Instructions (see class Instruction) define what the machine can do (ie load/store/maths)
# From code, the next step down is Vool, then Mom (in two steps)
#
# The next step transforms to the register machine layer, which is quite close to what actually
# executes. The step after transforms to Arm, which creates executables.
#
class Machine
include Util::Logging
log_level :info
def initialize
@booted = false
@risc_init = nil
@constants = []
@next_address = nil
end
attr_reader :constants , :cpu_init
attr_reader :booted , :translated
attr_reader :platform
# Translate code to whatever cpu is specified.
# Currently only :arm and :interpret
#
# Translating means translating the initial jump
# and then translating all methods
def translate( platform )
platform = platform.to_s.capitalize
@platform = Platform.for(platform)
@translated = true
translate_methods( @platform.translator )
@cpu_init = risc_init.to_cpu(@platform.translator)
end
# go through all methods and translate them to cpu, given the translator
def translate_methods(translator)
Parfait.object_space.get_all_methods.each do |method|
log.debug "Translate method #{method.name}"
method.translate_cpu(translator)
end
end
# machine keeps a list of all objects. this is lazily created with a collector
def objects
@objects ||= Collector.collect_space
end
# lazy init risc_init
def risc_init
@risc_init ||= Branch.new( "__initial_branch__" , Parfait.object_space.get_init.risc_instructions )
end
# add a constant (which get created during compilation and need to be linked)
def add_constant(const)
raise "Must be Parfait #{const}" unless const.is_a?(Parfait::Object)
@constants << const
end
# hand out a return address for use as constant the address is added
def get_address
10.times do # 10 for whole pages
@next_address = Parfait::ReturnAddress.new(0,@next_address)
add_constant( @next_address )
end unless @next_address
addr = @next_address
@next_address = @next_address.next_integer
addr
end
# To create binaries, objects (and labels) need to have a position
# (so objects can be loaded and branches know where to jump)
#
# Position in the order
# - initial jump
# - all objects
# - all code (BinaryCode objects)
# As code length may change during assembly, this way at least the objects stay
# in place and we don't have to deal with changing loading code
def position_all
raise "Not translated " unless @translated
#need the initial jump at 0 and then functions
Position.new(cpu_init , 0)
code_start = position_objects( @platform.padding )
# and then everything code
position_code(code_start)
end
# go through everything that is not code (BinaryCode) and set position
# padded_length is what determines an objects (byte) length
# return final position that is stored in code_start
def position_objects(at)
# want to have the objects first in the executable
sorted = objects.values.sort{|left,right| left.class.name <=> right.class.name}
previous = nil
sorted.each do | objekt|
next if objekt.is_a?( Parfait::BinaryCode) or objekt.is_a?( Risc::Label )
before = at
position = Position.new(objekt , at)
previous.position_listener(objekt) if previous
previous = position
at += objekt.padded_length
log.debug "Object #{objekt.class}:#{before.to_s(16)} len: #{(at - before).to_s(16)}"
end
at
end
# Position all BinaryCode.
#
# So that all code from one method is layed out linearly (for debugging)
# we go through methods, and then through all codes from the method
#
# start at code_start.
def position_code(code_start)
Parfait.object_space.types.values.each do |type|
next unless type.methods
type.methods.each_method do |method|
Position.log.debug "Position starts for method #{method.name}"
last_code = CodeListener.init(method.binary)
last_code.set(code_start)
first_position = InstructionListener.init(method.cpu_instructions, method.binary)
first_position.set( code_start + Parfait::BinaryCode.byte_offset)
code_start = last_code.next_slot
end
end
#Position.set( first_method.cpu_instructions, code_start + Parfait::BinaryCode.byte_offset , first_method.binary)
#log.debug "Method #{first_method.name}:#{before.to_s(16)} len: #{(code_start - before).to_s(16)}"
#log.debug "Instructions #{first_method.cpu_instructions.object_id.to_s(16)}:#{(before+Parfait::BinaryCode.byte_offset).to_s(16)}"
end
# Create Binary code for all methods and the initial jump
# BinaryWriter handles the writing from instructions into BinaryCode objects
#
# current (poor) design throws an exception when the assembly can't fit
# constant loads into one instruction.
#
def create_binary
objects.each do |id , method|
next unless method.is_a? Parfait::TypedMethod
writer = BinaryWriter.new(method.binary)
writer.assemble(method.cpu_instructions)
end
log.debug "BinaryInit #{cpu_init.object_id.to_s(16)}"
end
def boot
initialize
Position.clear_positions
@objects = nil
@translated = false
boot_parfait!
@booted = true
self
end
end
# Module function to retrieve singleton
def self.machine
unless defined?(@machine)
@machine = Machine.new
end
@machine
end
end
require_relative "boot"