ad3040a846
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
174 lines
5.9 KiB
Ruby
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"
|