rubyx/lib/risc/linker.rb
Torsten Ruger 8a81d41d5e Move booting to RubyXCompiler init
Also pass the source into the compile method.
This way compiler can be reused for subsequent compile.
Does remove some double boots, but no major time save
2018-09-02 13:57:19 +03:00

130 lines
4.2 KiB
Ruby

require_relative "collector"
require_relative "binary_writer"
module Risc
# 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 Linker
include Util::Logging
log_level :info
def initialize(platform , assemblers , constants)
if(platform.is_a?(Symbol))
platform = platform.to_s.capitalize
platform = Risc::Platform.for(platform)
end
raise "Platform must be platform, not #{platform.class}" unless platform.is_a?(Platform)
@platform = platform
@assemblers = assemblers
@constants = constants
@cpu_init = cpu_init_init
end
attr_reader :constants , :cpu_init
attr_reader :platform , :assemblers
# machine keeps a list of all objects and their positions.
# this is lazily created with a collector
def object_positions
Collector.collect_space(self) if Position.positions.length < 2 #one is the label
Position.positions
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
#need the initial jump at 0 and then functions
Position.new(@cpu_init).set(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 = object_positions.keys.sort do |left,right|
left.class.name <=> right.class.name
end
previous = nil
sorted.each do |objekt|
next unless Position.is_object(objekt)
before = at
raise objekt.class unless( Position.set?(objekt)) #debug check
position = Position.get(objekt).set(at)
previous.position_listener(objekt) if previous
previous = position
at += objekt.padded_length
log.debug "Object #{objekt.class}:0x#{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)
assemblers.each do |asm|
Position.log.debug "Method start #{code_start.to_s(16)} #{asm.callable.name}"
code_pos = CodeListener.init(asm.callable.binary, platform)
instructions = asm.instructions
InstructionListener.init( instructions, asm.callable.binary)
code_pos.position_listener( LabelListener.new(instructions))
code_pos.set(code_start)
code_start = Position.get(asm.callable.binary.last_code).next_slot
end
end
# Create Binary code for all methods and the initial jump
# BinaryWriter handles the writing from instructions into BinaryCode objects
#
def create_binary
prerun
assemble
log.debug "BinaryInit #{@cpu_init.object_id.to_s(16)}"
end
def prerun
assemblers.each do |asm|
asm.instructions.each do |ins|
ins.precheck
end
end
end
def assemble
assemblers.each do |asm|
writer = BinaryWriter.new(asm.callable.binary)
writer.assemble(asm.instructions)
end
end
private
# cpu_init come from translating the risc_init
# risc_init is a branch to the __init__ method
#
def cpu_init_init
init = assemblers.find {|asm| asm.callable.name == :__init__}
risc_init = Branch.new( "__initial_branch__" , init.callable.binary )
@platform.translator.translate(risc_init)
end
end
end