Unify instruction namings also dirs
Was getting confused myself, where it was instruction or instructions, when if the base class was inside or out of dir. Now dirs are plural, and base class is inside.
This commit is contained in:
51
lib/slot_machine/instructions/argument_transfer.rb
Normal file
51
lib/slot_machine/instructions/argument_transfer.rb
Normal file
@ -0,0 +1,51 @@
|
||||
module SlotMachine
|
||||
|
||||
# Transering the arguments from the current frame into the next frame
|
||||
#
|
||||
# This could be _done_ at this level, and in fact used to be.
|
||||
# The instruction was introduced to
|
||||
# 1. make optimisations easier
|
||||
# 2. localise the inevitable change
|
||||
#
|
||||
# 1. The optimal risc implementation for this loads old and new frames into registers
|
||||
# and does a whole bunch of transfers
|
||||
# But if we do individual SlotMoves here, each one has to load the frames,
|
||||
# thus making advanced analysis/optimisation neccessary to achieve the same effect.
|
||||
#
|
||||
# 2. Closures will have to have access to variables after the frame goes out of scope
|
||||
# and in fact be able to change the parents variables. The current design does not allow
|
||||
# for this, and so will have to be change in the not so distant future.
|
||||
#
|
||||
class ArgumentTransfer < Instruction
|
||||
|
||||
attr_reader :receiver , :arguments
|
||||
|
||||
# receiver is a slot_definition
|
||||
# arguments is an array of SlotLoads
|
||||
def initialize( source , receiver,arguments )
|
||||
super(source)
|
||||
@receiver , @arguments = receiver , arguments
|
||||
raise "Receiver not Slot #{@receiver}" unless @receiver.is_a?(Slotted)
|
||||
@arguments.each{|a| raise "args not Slotted #{a}" unless a.is_a?(Slotted)}
|
||||
end
|
||||
|
||||
def to_s
|
||||
"ArgumentTransfer " + ([@receiver] + @arguments).join(",")
|
||||
end
|
||||
|
||||
# load receiver and then each arg into the new message
|
||||
# delegates to SlotLoad for receiver and to the actual args.to_risc
|
||||
def to_risc(compiler)
|
||||
transfer = SlotLoad.new(self.source ,[:message , :next_message , :receiver] , @receiver, self).to_risc(compiler)
|
||||
#TODO transfer the Number of arguments to :arguments_given (to be checked on entry)
|
||||
arg_target = [:message , :next_message ]
|
||||
@arguments.each_with_index do |arg , index| # +1 because of type
|
||||
load = SlotMachine::SlotLoad.new(self.source, arg_target + ["arg#{index+1}".to_sym] , arg)
|
||||
load.to_risc(compiler)
|
||||
end
|
||||
transfer
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
38
lib/slot_machine/instructions/block_yield.rb
Normal file
38
lib/slot_machine/instructions/block_yield.rb
Normal file
@ -0,0 +1,38 @@
|
||||
module SlotMachine
|
||||
|
||||
# A BlockYield calls an argument block. All we need to know is the index
|
||||
# of the argument, and the rest is almost as simple as a SimpleCall
|
||||
|
||||
class BlockYield < Instruction
|
||||
attr :arg_index
|
||||
|
||||
# pass in the source (sol statement) and the index.
|
||||
# The index is the argument index of the block that we call
|
||||
def initialize(source , index)
|
||||
super(source)
|
||||
@arg_index = index
|
||||
end
|
||||
|
||||
def to_s
|
||||
"BlockYield[#{arg_index}] "
|
||||
end
|
||||
|
||||
# almost as simple as a SimpleCall, use a dynamic_jump to get there
|
||||
def to_risc(compiler)
|
||||
return_label = Risc.label("block_yield", "continue_#{object_id}")
|
||||
index = arg_index
|
||||
compiler.build("BlockYield") do
|
||||
next_message! << message[:next_message]
|
||||
return_address! << return_label
|
||||
next_message[:return_address] << return_address
|
||||
|
||||
block_reg! << message["arg#{index}".to_sym]
|
||||
|
||||
message << message[:next_message]
|
||||
add_code Risc::DynamicJump.new("block_yield", block_reg )
|
||||
add_code return_label
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
25
lib/slot_machine/instructions/check.rb
Normal file
25
lib/slot_machine/instructions/check.rb
Normal file
@ -0,0 +1,25 @@
|
||||
module SlotMachine
|
||||
|
||||
# A base class for conditions in SlotMachine
|
||||
# Checks (if in code, compare in assm) jump or not, depending
|
||||
# The logic we choose is closer to the code logic (the asm is reversed)
|
||||
# When we write an if, the true is the next code, so the Check logic is
|
||||
# that if the check passes, no jump happens
|
||||
# This means you need to pass the false label, where to jump to if the
|
||||
# check does not pass
|
||||
# Note: In assembler a branch on 0 does just that, it branches if the condition
|
||||
# is met. This means that the asm implementation is somewhat the reverse
|
||||
# of the SlotMachine names. But it's easier to understand (imho)
|
||||
class Check < Instruction
|
||||
attr_reader :false_label
|
||||
|
||||
def initialize(false_label)
|
||||
set_label(false_label)
|
||||
end
|
||||
|
||||
def set_label(false_label)
|
||||
@false_label = false_label
|
||||
raise "Jump target must be a label #{false_label}" unless false_label.is_a?(Label) end
|
||||
end
|
||||
|
||||
end
|
50
lib/slot_machine/instructions/dynamic_call.rb
Normal file
50
lib/slot_machine/instructions/dynamic_call.rb
Normal file
@ -0,0 +1,50 @@
|
||||
module SlotMachine
|
||||
|
||||
# A dynamic call calls a method at runtime. This off course implies that we don't know the
|
||||
# method at compile time and so must "find" it. Resolving, or finding the method, is a
|
||||
# a seperate instruction though, and here we assume that we know this Method instance.
|
||||
#
|
||||
# Both (to be called) Method instance and the type of receiver are stored as
|
||||
# variables here. The type is used to check before calling.
|
||||
#
|
||||
# Setting up the method is not part of this instructions scope. That setup
|
||||
# includes the type check and any necccessay method resolution.
|
||||
# See sol send statement
|
||||
#
|
||||
class DynamicCall < Instruction
|
||||
attr :cache_entry
|
||||
|
||||
def initialize(type = nil, method = nil)
|
||||
@cache_entry = Parfait::CacheEntry.new(type, method)
|
||||
end
|
||||
|
||||
def to_s
|
||||
str = "DynamicCall "
|
||||
str += cache_entry.cached_method&.name if cache_entry
|
||||
str
|
||||
end
|
||||
|
||||
# One could almost think that one can resolve this to a Risc::FunctionCall
|
||||
# (which btw resolves to a simple jump), alas, the FunctionCall, like all other
|
||||
# jumping, resolves the address at compile time.
|
||||
#
|
||||
# Instead we need a DynamicJump instruction that explicitly takes a register as
|
||||
# a target (not a label)
|
||||
def to_risc(compiler)
|
||||
entry = @cache_entry
|
||||
compiler.add_constant( entry )
|
||||
return_label = Risc.label(self, "continue_#{object_id}")
|
||||
compiler.build("DynamicCall") do
|
||||
return_address! << return_label
|
||||
next_message! << message[:next_message]
|
||||
next_message[:return_address] << return_address
|
||||
message << message[:next_message]
|
||||
cache_entry! << entry
|
||||
cache_entry << cache_entry[:cached_method]
|
||||
add_code Risc::DynamicJump.new("DynamicCall", cache_entry )
|
||||
add_code return_label
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
55
lib/slot_machine/instructions/instruction.rb
Normal file
55
lib/slot_machine/instructions/instruction.rb
Normal file
@ -0,0 +1,55 @@
|
||||
module SlotMachine
|
||||
|
||||
# Base class for SlotMachine instructions
|
||||
# At the base class level instructions are a linked list.
|
||||
#
|
||||
# SlotMachine::Instructions are created by the Sol level as an intermediate step
|
||||
# towards the next level down, the Risc level.
|
||||
# SlotMachine and Risc are both abstract machines (ie have instructions), so both
|
||||
# share the linked list functionality (In Util::List)
|
||||
#
|
||||
# To convert a SlotMachine instruction to it's Risc equivalent to_risc is called
|
||||
#
|
||||
class Instruction
|
||||
include Util::List
|
||||
|
||||
def initialize( source , nekst = nil )
|
||||
@source = source
|
||||
@next = nekst
|
||||
return unless source
|
||||
unless source.is_a?(String) or
|
||||
source.is_a?(Sol::Statement)
|
||||
raise "Source must be string or Instruction, not #{source.class}"
|
||||
end
|
||||
end
|
||||
attr_reader :source
|
||||
|
||||
# to_risc, like the name says, converts the instruction to it's Risc equivalent.
|
||||
# The Risc machine is basically a simple register machine (kind of arm).
|
||||
# In other words SlotMachine is the higher abstraction and so slot instructions convert
|
||||
# to many (1-10) risc instructions
|
||||
#
|
||||
# The argument that is passed is a MethodCompiler, which has the method and some
|
||||
# state about registers used. (also provides helpers to generate risc instructions)
|
||||
def to_risc(compiler)
|
||||
raise self.class.name + "_todo"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
require_relative "label"
|
||||
require_relative "check"
|
||||
require_relative "simple_call"
|
||||
require_relative "dynamic_call"
|
||||
require_relative "block_yield"
|
||||
require_relative "resolve_method"
|
||||
require_relative "truth_check"
|
||||
require_relative "not_same_check"
|
||||
require_relative "same_check"
|
||||
require_relative "jump"
|
||||
require_relative "return_jump"
|
||||
require_relative "slot_load"
|
||||
require_relative "return_sequence"
|
||||
require_relative "message_setup"
|
||||
require_relative "argument_transfer"
|
20
lib/slot_machine/instructions/jump.rb
Normal file
20
lib/slot_machine/instructions/jump.rb
Normal file
@ -0,0 +1,20 @@
|
||||
module SlotMachine
|
||||
|
||||
# Branch jump to the Label given
|
||||
# Eg used at the end of while or end of if_true branch
|
||||
#
|
||||
# Risc equivalent is the same really, called Branch there.
|
||||
#
|
||||
class Jump < Instruction
|
||||
attr_reader :label
|
||||
|
||||
def initialize(label)
|
||||
@label = label
|
||||
end
|
||||
def to_risc(compiler)
|
||||
compiler.add_code Risc::Branch.new(self , @label.risc_label(compiler))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
48
lib/slot_machine/instructions/label.rb
Normal file
48
lib/slot_machine/instructions/label.rb
Normal file
@ -0,0 +1,48 @@
|
||||
module SlotMachine
|
||||
|
||||
# A Label is the only legal target for a branch (in SlotMachine, in Risc a BinaryCode is ok too)
|
||||
#
|
||||
# In the dynamic view (runtime) where the instructions form a graph,
|
||||
# branches fan out, Labels collect. In other words a branch is the place where
|
||||
# several roads lead off, and a Label where several roads arrive.
|
||||
#
|
||||
# A Label has a name which is mainly used for debugging.
|
||||
#
|
||||
# A SlotMachine::Label converts one2one to a Risc::Label. So in a way it could not be more
|
||||
# simple.
|
||||
# Alas, since almost by definition several roads lead to this label, all those
|
||||
# several converted instructions must also point to the identical label on the
|
||||
# risc level.
|
||||
#
|
||||
# This is achieved by caching the created Risc::Label in an instance variable.
|
||||
# All branches that lead to this label can thus safely call the to_risc and
|
||||
# whoever calls first triggers the labels creation, but all get the same label.
|
||||
#
|
||||
# Off course some specific place still has to be responsible for actually
|
||||
# adding the label to the instruction list (usually an if/while)
|
||||
|
||||
class Label < Instruction
|
||||
attr_reader :name
|
||||
def initialize(source , name)
|
||||
super(source)
|
||||
@name = name
|
||||
end
|
||||
|
||||
def to_s
|
||||
"Label: #{name}"
|
||||
end
|
||||
|
||||
# generate the risc label lazily
|
||||
def risc_label(compiler)
|
||||
@risc_label ||= Risc.label(self,name)
|
||||
compiler.add_constant(@risc_label.address)
|
||||
@risc_label
|
||||
end
|
||||
|
||||
# add the risc_label to the compiler (instruction flow)
|
||||
# should only be called once
|
||||
def to_risc(compiler)
|
||||
compiler.add_code( risc_label(compiler) )
|
||||
end
|
||||
end
|
||||
end
|
62
lib/slot_machine/instructions/message_setup.rb
Normal file
62
lib/slot_machine/instructions/message_setup.rb
Normal file
@ -0,0 +1,62 @@
|
||||
module SlotMachine
|
||||
|
||||
# As reminder: a statically resolved call (the simplest one) becomes three SlotMachine Instructions.
|
||||
# Ie: MessageSetup,ArgumentTransfer,SimpleCall
|
||||
#
|
||||
# MessageSetup does Setup before a call can be made, acquiring and filling the message
|
||||
# basically. Only after MessageSetup is the next_message safe to use.
|
||||
#
|
||||
# The Factory (instane kept by Space) keeps a linked list of Messages,
|
||||
# from which we take and currenty also return.
|
||||
#
|
||||
# Message setup set the name to the called method's name, and also set the arg and local
|
||||
# types on the new message, currently for debugging but later for dynamic checking
|
||||
#
|
||||
# The only difference between the setup of a static call and a dynamic one is where
|
||||
# the method comes from. A static, or simple call, passes the method, but a dynamic
|
||||
# call passes the cache_entry that holds the resolved method.
|
||||
#
|
||||
# In either case, the method is loaded and name,frame and args set
|
||||
#
|
||||
class MessageSetup < Instruction
|
||||
attr_reader :method_source
|
||||
|
||||
def initialize(method_source)
|
||||
raise "no nil source" unless method_source
|
||||
@method_source = method_source
|
||||
end
|
||||
|
||||
# Move method name, frame and arguemnt types from the method to the next_message
|
||||
# Get the message from Space and link it.
|
||||
def to_risc(compiler)
|
||||
build_with(compiler.builder(self))
|
||||
end
|
||||
|
||||
# directly called by to_risc
|
||||
# but also used directly in __init
|
||||
def build_with(builder)
|
||||
case from = method_source
|
||||
when Parfait::CallableMethod
|
||||
callable = builder.load_object(from)
|
||||
when Parfait::CacheEntry
|
||||
callable = builder.load_object(from)[:cached_method].to_reg
|
||||
when Integer
|
||||
callable = builder.message[ "arg#{from}".to_sym ].to_reg
|
||||
else
|
||||
raise "unknown source #{method_source.class}:#{method_source}"
|
||||
end
|
||||
build_message_data(builder , callable)
|
||||
return builder.built
|
||||
end
|
||||
|
||||
private
|
||||
def source
|
||||
"method setup "
|
||||
end
|
||||
|
||||
# set the method into the message
|
||||
def build_message_data( builder , callable)
|
||||
builder.message[:next_message][:method] << callable
|
||||
end
|
||||
end
|
||||
end
|
32
lib/slot_machine/instructions/not_same_check.rb
Normal file
32
lib/slot_machine/instructions/not_same_check.rb
Normal file
@ -0,0 +1,32 @@
|
||||
module SlotMachine
|
||||
|
||||
# SlotMachine internal check, as the name says to see if two values are not the same
|
||||
# In other words, we this checks identity, bit-values, pointers
|
||||
#
|
||||
# The values that are compared are defined as Slots, ie can be anything
|
||||
# available to the machine through frame message or self
|
||||
#
|
||||
# Acording to SlotMachine::Check logic, we jump to the given label is the values are the same
|
||||
#
|
||||
class NotSameCheck < Check
|
||||
attr_reader :left , :right
|
||||
|
||||
def initialize(left, right , label)
|
||||
super(label)
|
||||
@left , @right = left , right
|
||||
end
|
||||
|
||||
def to_s
|
||||
"NotSameCheck: #{left}:#{right}"
|
||||
end
|
||||
|
||||
# basically move both left and right values into register
|
||||
# subtract them and see if IsZero comparison
|
||||
def to_risc(compiler)
|
||||
l_reg = left.to_register(compiler, self)
|
||||
r_reg = right.to_register(compiler, self)
|
||||
compiler.add_code Risc.op( self , :- , l_reg , r_reg)
|
||||
compiler.add_code Risc::IsZero.new( self, false_label.risc_label(compiler))
|
||||
end
|
||||
end
|
||||
end
|
73
lib/slot_machine/instructions/resolve_method.rb
Normal file
73
lib/slot_machine/instructions/resolve_method.rb
Normal file
@ -0,0 +1,73 @@
|
||||
module SlotMachine
|
||||
|
||||
# Dynamic method resolution is at the heart of a dynamic language, and here
|
||||
# is the SlotMachine level instruction to do it.
|
||||
#
|
||||
# When the static type can not be determined a CacheEntry is used to store
|
||||
# type and method of the resolved method. The CacheEntry is shared with
|
||||
# DynamicCall instruction who is responsible for calling the method in the entry.
|
||||
#
|
||||
# This instruction resolves the method, in case the types don't match (and
|
||||
# at least on first encouter)
|
||||
#
|
||||
# This used to be a method, but we don't really need the method setup etc
|
||||
#
|
||||
class ResolveMethod < Instruction
|
||||
attr :cache_entry , :name
|
||||
|
||||
# pass in source (SolStatement)
|
||||
# name of the method (don't knwow the actaual method)
|
||||
# and the cache_entry
|
||||
def initialize(source , name , cache_entry)
|
||||
super(source)
|
||||
@name = name
|
||||
@cache_entry = cache_entry
|
||||
end
|
||||
|
||||
def to_s
|
||||
"ResolveMethod #{name}"
|
||||
end
|
||||
|
||||
# When the method is resolved, a cache_entry is used to hold the result.
|
||||
# That cache_entry (holding type and method) is checked before, and
|
||||
# needs to be updated by this instruction.
|
||||
#
|
||||
# We use the type stored in the cache_entry to check the methods if any of it's
|
||||
# names are the same as the given @name
|
||||
#
|
||||
# currently a fail results in sys exit
|
||||
def to_risc( compiler )
|
||||
name_ = @name
|
||||
cache_entry_ = @cache_entry
|
||||
builder = compiler.builder(self)
|
||||
builder.build do
|
||||
word! << name_
|
||||
cache_entry! << cache_entry_
|
||||
|
||||
type! << cache_entry[:cached_type]
|
||||
callable_method! << type[:methods]
|
||||
|
||||
add_code while_start_label
|
||||
|
||||
object! << Parfait.object_space.nil_object
|
||||
object - callable_method
|
||||
if_zero exit_label
|
||||
|
||||
name! << callable_method[:name]
|
||||
name - word
|
||||
|
||||
if_zero ok_label
|
||||
|
||||
callable_method << callable_method[:next_callable]
|
||||
branch while_start_label
|
||||
|
||||
add_code exit_label
|
||||
MethodMissing.new(compiler.source_name , word.symbol).to_risc(compiler)
|
||||
|
||||
add_code ok_label
|
||||
cache_entry[:cached_method] << callable_method
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
32
lib/slot_machine/instructions/return_jump.rb
Normal file
32
lib/slot_machine/instructions/return_jump.rb
Normal file
@ -0,0 +1,32 @@
|
||||
module SlotMachine
|
||||
|
||||
# the return jump jumps to the return label
|
||||
# the method setup is such that there is exactly one return_label in a method
|
||||
# This is so the actual code that executes the return can be quite complicated
|
||||
# and big, and won't be repeated
|
||||
#
|
||||
class ReturnJump < Instruction
|
||||
|
||||
attr_reader :return_label
|
||||
|
||||
# pass in the source_name (string/sol_instruction) for accounting purposes
|
||||
# and the return_label, where we actually jump to. This is set up by the
|
||||
# method_compiler, so it is easy to find (see return_label in compiler)
|
||||
def initialize( source , label )
|
||||
super(source)
|
||||
@return_label = label
|
||||
end
|
||||
|
||||
# the jump quite simple resolves to an uncondition risc Branch
|
||||
# we use the label that is passed in at creation
|
||||
def to_risc(compiler)
|
||||
compiler.add_code Risc::Branch.new(self , return_label.risc_label(compiler))
|
||||
end
|
||||
|
||||
def to_s
|
||||
"ReturnJump: #{return_label}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
43
lib/slot_machine/instructions/return_sequence.rb
Normal file
43
lib/slot_machine/instructions/return_sequence.rb
Normal file
@ -0,0 +1,43 @@
|
||||
module SlotMachine
|
||||
|
||||
# The ReturnSequence models the return from a method.
|
||||
#
|
||||
# This involves the jump to the return address stored in the message, and
|
||||
# the reinstantiation of the previous message.
|
||||
#
|
||||
# The (slot) machine only ever "knows" one message, the current message.
|
||||
# Messages are a double linked list, calling involves going forward,
|
||||
# returning means going back.
|
||||
#
|
||||
# The return value of the current message is transferred into the return value of the
|
||||
# callers return value during the swap of messages, and just before the jump.
|
||||
#
|
||||
# The callers perspective of a call is the magical apperance of a return_value
|
||||
# in it's message at the instruction after the call.
|
||||
#
|
||||
# The instruction is not parameterized as it translates to a constant
|
||||
# set of lower level instructions.
|
||||
#
|
||||
class ReturnSequence < Instruction
|
||||
def to_risc(compiler)
|
||||
compiler.reset_regs
|
||||
builder = compiler.builder(self)
|
||||
builder.build do
|
||||
object! << message[:return_value]
|
||||
caller_reg! << message[:caller]
|
||||
caller_reg[:return_value] << object
|
||||
end
|
||||
builder.build do
|
||||
return_address! << message[:return_address]
|
||||
return_address << return_address[ Parfait::Integer.integer_index]
|
||||
message << message[:caller]
|
||||
add_code Risc.function_return("return #{compiler.callable.name}", return_address)
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
"ReturnSequence"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
32
lib/slot_machine/instructions/same_check.rb
Normal file
32
lib/slot_machine/instructions/same_check.rb
Normal file
@ -0,0 +1,32 @@
|
||||
module SlotMachine
|
||||
|
||||
# SlotMachine internal check, as the name says to see if two values are the same
|
||||
# In other words, we this checks identity, bit-values, pointers
|
||||
#
|
||||
# The values that are compared are defined as Slots, ie can be anything
|
||||
# available to the machine through frame message or self
|
||||
#
|
||||
# Acording to SlotMachine::Check logic, we jump to the given label is the values are not the same
|
||||
#
|
||||
class SameCheck < Check
|
||||
attr_reader :left , :right
|
||||
|
||||
def initialize(left, right , label)
|
||||
super(label)
|
||||
@left , @right = left , right
|
||||
end
|
||||
|
||||
def to_s
|
||||
"SameCheck: #{left}:#{right}"
|
||||
end
|
||||
|
||||
# basically move both left and right values into register
|
||||
# subtract them and see if IsZero comparison
|
||||
def to_risc(compiler)
|
||||
l_reg = left.to_register(compiler, self)
|
||||
r_reg = right.to_register(compiler, self)
|
||||
compiler.add_code Risc.op( self , :- , l_reg , r_reg)
|
||||
compiler.add_code Risc::IsNotZero.new( self, false_label.risc_label(compiler))
|
||||
end
|
||||
end
|
||||
end
|
40
lib/slot_machine/instructions/simple_call.rb
Normal file
40
lib/slot_machine/instructions/simple_call.rb
Normal file
@ -0,0 +1,40 @@
|
||||
module SlotMachine
|
||||
|
||||
# A SimpleCall is just that, a simple call. This could be called a function call too,
|
||||
# meaning we managed to resolve the function at compile time and all we have to do is
|
||||
# actually call it.
|
||||
#
|
||||
# As the call setup is done beforehand (for both simple and cached call), the
|
||||
# calling really means mostly jumping to the address. Simple.
|
||||
#
|
||||
class SimpleCall < Instruction
|
||||
attr_reader :method
|
||||
|
||||
def initialize(method)
|
||||
@method = method
|
||||
end
|
||||
|
||||
def to_s
|
||||
"SimpleCall #{@method.name}"
|
||||
end
|
||||
|
||||
# Calling a Method is basically jumping to the Binary (+ offset).
|
||||
# We just swap in the new message and go.
|
||||
#
|
||||
# For returning, we add a label after the call, and load it's address into the
|
||||
# return_address of the next_message, for the ReturnSequence to pick it up.
|
||||
def to_risc(compiler)
|
||||
method = @method
|
||||
return_label = Risc.label(self,"continue_#{object_id}")
|
||||
return_address = compiler.load_object( return_label )
|
||||
compiler.build(self.to_s) do
|
||||
message[:next_message][:return_address] << return_address
|
||||
message << message[:next_message]
|
||||
add_code Risc.function_call(self.to_s, method )
|
||||
add_code return_label
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
55
lib/slot_machine/instructions/slot_load.rb
Normal file
55
lib/slot_machine/instructions/slot_load.rb
Normal file
@ -0,0 +1,55 @@
|
||||
module SlotMachine
|
||||
|
||||
# SlotLoad is for moving data into a slot, either from another slot, or constant
|
||||
# A Slot is basically an instance variable, but it must be of known type
|
||||
#
|
||||
# The value loaded (the right hand side) can be a constant (SlotMachine::Constant) or come from
|
||||
# another Slot (Slot)
|
||||
#
|
||||
# The Slot on the left hand side is always a Slot.
|
||||
# The only known object (*) for the left side is the current message, which is a bit like
|
||||
# the oo version of a Stack (Stack Register, Frame Pointer, ..)
|
||||
# (* off course all class objects are global, and so they are allowed too)
|
||||
#
|
||||
# A maybe not immediately obvious corrolar of this design is the total absence of
|
||||
# general external instance variable accessors. Ie only inside an object's functions
|
||||
# can a method access instance variables, because only inside the method is the type
|
||||
# guaranteed.
|
||||
# From the outside a send is neccessary, both for get and set, (which goes through the method
|
||||
# resolution and guarantees the correct method for a type), in other words perfect data hiding.
|
||||
#
|
||||
# @left: A Slot, or an array that can be passed to the constructor of the
|
||||
# Slot (see there)
|
||||
#
|
||||
# @right: A Slot with slots or a SlotMachine::Constant
|
||||
# original_source: optinally another slot_machine instruction that will be passed down
|
||||
# to created risc instructions. (Because SlotLoad is often used internally)
|
||||
class SlotLoad < Instruction
|
||||
|
||||
attr_reader :left , :right , :original_source
|
||||
|
||||
def initialize(source , left , right, original_source = nil)
|
||||
super(source)
|
||||
@left , @right = left , right
|
||||
@left = Slotted.for(@left.shift , @left) if @left.is_a? Array
|
||||
@right = Slotted.for(@right.shift , @right) if @right.is_a? Array
|
||||
raise "right not SlotMachine, #{@right.to_s}" unless @right.is_a?( Slotted )
|
||||
raise "left not SlotMachine, #{@left.to_s}" unless @left.is_a?( Slotted )
|
||||
@original_source = original_source || self
|
||||
end
|
||||
|
||||
def to_s
|
||||
"SlotLoad #{right} -> #{left}"
|
||||
end
|
||||
|
||||
# resolve the SlotLoad to the respective risc Instructions.
|
||||
# calls sym_to_risc for most (symbols), and ConstantLoad for CacheEntry
|
||||
# after loading the right into register
|
||||
def to_risc(compiler)
|
||||
const_reg = @right.to_register(compiler , original_source)
|
||||
@left.reduce_and_load(const_reg , compiler , original_source )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
36
lib/slot_machine/instructions/truth_check.rb
Normal file
36
lib/slot_machine/instructions/truth_check.rb
Normal file
@ -0,0 +1,36 @@
|
||||
module SlotMachine
|
||||
|
||||
# The funny thing about the ruby truth is that it is anything but false or nil
|
||||
#
|
||||
# To implement the normal ruby logic, we check for false or nil and jump
|
||||
# to the false branch. true_block follows implicitly
|
||||
#
|
||||
class TruthCheck < Check
|
||||
attr_reader :condition
|
||||
|
||||
def initialize(condition , false_label)
|
||||
super(false_label)
|
||||
@condition = condition
|
||||
raise "condition must be slot_definition #{condition}" unless condition.is_a?(Slotted)
|
||||
end
|
||||
|
||||
def to_s
|
||||
"TruthCheck #{@condition} -> #{false_label}"
|
||||
end
|
||||
|
||||
def to_risc(compiler)
|
||||
false_label = @false_label.risc_label(compiler)
|
||||
condition_reg = @condition.to_register(compiler,self)
|
||||
|
||||
compiler.build(self.to_s) do
|
||||
object = load_object Parfait.object_space.false_object
|
||||
object.op :- , condition_reg
|
||||
if_zero false_label
|
||||
object = load_object Parfait.object_space.nil_object
|
||||
object.op :- , condition_reg
|
||||
if_zero false_label
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user