moving instrctions into own folders and arm machine out of the way

This commit is contained in:
Torsten Ruger 2014-10-03 10:25:10 +03:00
parent ad73e320b0
commit 220d9f6213
9 changed files with 238 additions and 418 deletions

148
lib/arm/arm_machine.rb Normal file
View File

@ -0,0 +1,148 @@
require_relative "instruction"
module Arm
# Our virtual c-machine has a number of registers of a given size and uses a stack
# So much so standard
# But our machine is oo, meaning that the register contents is typed.
# Off course current hardware does not have that (a perceived issue), but for our machine we pretend.
# So internally we have at least 8 word registers, one of which is used to keep track of types*
# and any number of scratch registers
# but externally it's all Values (see there)
# * Note that register content is typed externally. Not as in mri, where int's are tagged. Floats can's
# be tagged and lambda should be it's own type, so tagging does not work
# A Machines main responsibility in the framework is to instantiate Instruction
# Value functions are mapped to machines by concatenating the values class name + the methd name
# Example: IntegerValue.plus( value ) -> Machine.signed_plus (value )
# Also, shortcuts are created to easily instantiate Instruction objects. The "standard" set of instructions
# (arm-influenced) provides for normal operations on a register machine,
# Example: pop -> StackInstruction.new( {:opcode => :pop}.merge(options) )
# Instructions work with options, so you can pass anything in, and the only thing the functions does
# is save you typing the clazz.new. It passes the function name as the :opcode
class ArmMachine
# hmm, not pretty but for now
@@instance = nil
attr_reader :registers
attr_reader :scratch
attr_reader :pc
attr_reader :stack
# is often a pseudo register (ie doesn't support move or other operations).
# Still, using if to express tests makes sense, not just for
# consistency in this code, but also because that is what is actually done
attr_reader :status
# conditions specify all the possibilities for branches. Branches are b + condition
# Example: beq means brach if equal.
# :al means always, so bal is an unconditional branch (but b() also works)
CONDITIONS = [ :al , :eq , :ne , :lt , :le, :ge, :gt , :cs , :mi , :hi , :cc , :pl, :ls , :vc , :vs ]
# here we create the shortcuts for the "standard" instructions, see above
# Derived machines may use own instructions and define functions for them if so desired
def initialize
[:push, :pop].each do |inst|
define_instruction_one(inst , StackInstruction)
end
[:adc, :add, :and, :bic, :eor, :orr, :rsb, :rsc, :sbc, :sub].each do |inst|
define_instruction_three(inst , LogicInstruction)
end
[:mov, :mvn].each do |inst|
define_instruction_two(inst , MoveInstruction)
end
[:cmn, :cmp, :teq, :tst].each do |inst|
define_instruction_two(inst , CompareInstruction)
end
[:strb, :str , :ldrb, :ldr].each do |inst|
define_instruction_three(inst , MemoryInstruction)
end
[:b, :call , :swi].each do |inst|
define_instruction_one(inst , CallInstruction)
end
# create all possible brach instructions, but the CallInstruction demangles the
# code, and has opcode set to :b and :condition_code set to the condition
CONDITIONS.each do |suffix|
define_instruction_one("b#{suffix}".to_sym , CallInstruction)
define_instruction_one("call#{suffix}".to_sym , CallInstruction)
end
end
def create_method(name, &block)
self.class.send(:define_method, name , &block)
end
def self.instance
if(@@instance.nil?)
@@instance = Arm::ArmMachine.new
end
@@instance
end
def self.instance= machine
@@instance = machine
end
def class_for clazz
c_name = clazz.name
my_module = self.class.name.split("::").first
clazz_name = clazz.name.split("::").last
if(my_module != Register )
module_class = eval("#{my_module}::#{clazz_name}") rescue nil
clazz = module_class if module_class
end
clazz
end
private
#defining the instruction (opcode, symbol) as an given class.
# the class is a Register::Instruction derived base class and to create machine specific function
# an actual machine must create derived classes (from this base class)
# These instruction classes must follow a naming pattern and take a hash in the contructor
# Example, a mov() opcode instantiates a Register::MoveInstruction
# for an Arm machine, a class Arm::MoveInstruction < Register::MoveInstruction exists, and it will
# be used to define the mov on an arm machine.
# This methods picks up that derived class and calls a define_instruction methods that can
# be overriden in subclasses
def define_instruction_one(inst , clazz , defaults = {} )
clazz = self.class_for(clazz)
create_method(inst) do |first , options = nil|
options = {} if options == nil
options.merge defaults
options[:opcode] = inst
first = Register::RegisterReference.new(first) if first.is_a? Symbol
clazz.new(first , options)
end
end
# same for two args (left right, from to etc)
def define_instruction_two(inst , clazz , defaults = {} )
clazz = self.class_for(clazz)
create_method(inst) do |left ,right , options = nil|
options = {} if options == nil
options.merge defaults
left = Register::RegisterReference.new(left) if left.is_a? Symbol
right = Register::RegisterReference.new(right) if right.is_a? Symbol
options[:opcode] = inst
clazz.new(left , right ,options)
end
end
# same for three args (result = left right,)
def define_instruction_three(inst , clazz , defaults = {} )
clazz = self.class_for(clazz)
create_method(inst) do |result , left ,right = nil , options = nil|
options = {} if options == nil
options.merge defaults
options[:opcode] = inst
result = Register::RegisterReference.new(result) if result.is_a? Symbol
left = Register::RegisterReference.new(left) if left.is_a? Symbol
right = Register::RegisterReference.new(right) if right.is_a? Symbol
clazz.new(result, left , right ,options)
end
end
end
end

View File

@ -1,20 +1,5 @@
module Arm module Arm
class ArmMachine < Register::RegisterMachine class MachineCode
# The constants are here for readablility, the code uses access functions below
RETURN_REG = :r0
TYPE_REG = :r1
RECEIVER_REG = :r2
SYSCALL_REG = :r7
def return_register
RETURN_REG
end
def type_register
TYPE_REG
end
def receiver_register
RECEIVER_REG
end
def function_call into , call def function_call into , call
raise "Not CallSite #{call.inspect}" unless call.is_a? Virtual::CallSite raise "Not CallSite #{call.inspect}" unless call.is_a? Virtual::CallSite
@ -94,10 +79,3 @@ module Arm
end end
end end
require_relative "stack_instruction"
require_relative "logic_instruction"
require_relative "move_instruction"
require_relative "compare_instruction"
require_relative "memory_instruction"
require_relative "call_instruction"
require_relative "constants"

View File

@ -1,24 +1,7 @@
module Register module Register
# The register machine model is close to current hardware and has following instruction classes class Instruction
# - Memory
# - Stack
# - Logic
# - Math
# - Control/Compare
# - Move
# - Call
# Instruction derives from Code, for the assembly api
class Instruction < Virtual::Object
def initialize options
@attributes = options
end
attr_reader :attributes
def opcode
@attributes[:opcode]
end
# returns an array of registers (RegisterReferences) that this instruction uses. # returns an array of registers (RegisterReferences) that this instruction uses.
# ie for r1 = r2 + r3 # ie for r1 = r2 + r3
# which in assembler is add r1 , r2 , r3 # which in assembler is add r1 , r2 , r3
@ -48,136 +31,4 @@ module Register
end end
end end
class StackInstruction < Instruction
def initialize first , options = {}
@first = first
super(options)
end
# when calling we place a dummy push/pop in the stream and calculate later what registers actually need saving
def set_registers regs
@first = regs.collect{ |r| r.symbol }
end
def is_push?
opcode == :push
end
def is_pop?
!is_push?
end
def uses
is_push? ? regs : []
end
def assigns
is_pop? ? regs : []
end
def regs
@first
end
def to_s
"#{opcode} [#{@first.collect {|f| f.to_asm}.join(',') }] #{super}"
end
end
class MemoryInstruction < Instruction
def initialize result , left , right = nil , options = {}
@result = result
@left = left
@right = right
super(options)
end
def uses
ret = [@left.register ]
ret << @right.register unless @right.nil?
ret
end
def assigns
[@result.register]
end
end
class LogicInstruction < Instruction
# result = left op right
#
# Logic instruction are your basic operator implementation. But unlike the (normal) code we write
# these Instructions must have "place" to write their results. Ie when you write 4 + 5 in ruby
# the result is sort of up in the air, but with Instructions the result must be assigned
def initialize result , left , right , options = {}
@result = result
@left = left
@right = right.is_a?(Fixnum) ? Virtual::IntegerConstant.new(right) : right
super(options)
end
attr_accessor :result , :left , :right
def uses
ret = []
ret << @left.register if @left and not @left.is_a? Constant
ret << @right.register if @right and not @right.is_a?(Constant)
ret
end
def assigns
[@result.register]
end
end
class CompareInstruction < Instruction
def initialize left , right , options = {}
@left = left
@right = right.is_a?(Fixnum) ? IntegerConstant.new(right) : right
super(options)
end
def uses
ret = [@left.register ]
ret << @right.register unless @right.is_a? Constant
ret
end
def assigns
[]
end
def to_s
"#{opcode} #{@left.to_asm} , #{@right.to_asm} #{super}"
end
end
class MoveInstruction < Instruction
def initialize to , from , options = {}
@to = to
@from = from.is_a?(Fixnum) ? Virtual::IntegerConstant.new(from) : from
raise "move must have from set #{inspect}" unless from
super(options)
end
attr_accessor :to , :from
def uses
@from.is_a?(Constant) ? [] : [@from.register]
end
def assigns
[@to.register]
end
end
class CallInstruction < Instruction
def initialize first , options = {}
@first = first
super(options)
opcode = @attributes[:opcode].to_s
if opcode.length == 3 and opcode[0] == "b"
@attributes[:condition_code] = opcode[1,2].to_sym
@attributes[:opcode] = :b
end
if opcode.length == 6 and opcode[0] == "c"
@attributes[:condition_code] = opcode[4,2].to_sym
@attributes[:opcode] = :call
end
end
def uses
if opcode == :call
@first.args.collect {|arg| arg.register }
else
[]
end
end
def assigns
if opcode == :call
[RegisterReference.new(RegisterMachine.instance.return_register)]
else
[]
end
end
def to_s
"#{opcode} #{@first.to_asm} #{super}"
end
end
end end

View File

@ -79,30 +79,4 @@ module Register
end end
end end
end end
# We insert push/pops as dummies to fill them later in CallSaving
# as we can not know ahead of time which locals wil be live in the code to come
# and also we don't want to "guess" later where the push/pops should be
# Here we check which registers need saving and add them
# Or sometimes just remove the push/pops, when no locals needed saving
class SaveLocals
def run block
push = block.call_block?
return unless push
return unless block.function
locals = block.function.locals_at block
pop = block.next.codes.first
if(locals.empty?)
#puts "Empty #{block.name}"
block.codes.delete(push)
block.next.codes.delete(pop)
else
#puts "PUSH #{push}"
push.set_registers(locals)
#puts "POP #{pop}"
pop.set_registers(locals)
end
end
end
end end

View File

@ -1,149 +1,3 @@
module Register
# Our virtual c-machine has a number of registers of a given size and uses a stack
# So much so standard
# But our machine is oo, meaning that the register contents is typed.
# Off course current hardware does not have that (a perceived issue), but for our machine we pretend.
# So internally we have at least 8 word registers, one of which is used to keep track of types*
# and any number of scratch registers
# but externally it's all Values (see there)
# * Note that register content is typed externally. Not as in mri, where int's are tagged. Floats can's
# be tagged and lambda should be it's own type, so tagging does not work
# A Machines main responsibility in the framework is to instantiate Instruction
# Value functions are mapped to machines by concatenating the values class name + the methd name
# Example: IntegerValue.plus( value ) -> Machine.signed_plus (value )
# Also, shortcuts are created to easily instantiate Instruction objects. The "standard" set of instructions
# (arm-influenced) provides for normal operations on a register machine,
# Example: pop -> StackInstruction.new( {:opcode => :pop}.merge(options) )
# Instructions work with options, so you can pass anything in, and the only thing the functions does
# is save you typing the clazz.new. It passes the function name as the :opcode
class RegisterMachine
# hmm, not pretty but for now
@@instance = nil
attr_reader :registers
attr_reader :scratch
attr_reader :pc
attr_reader :stack
# is often a pseudo register (ie doesn't support move or other operations).
# Still, using if to express tests makes sense, not just for
# consistency in this code, but also because that is what is actually done
attr_reader :status
# conditions specify all the possibilities for branches. Branches are b + condition
# Example: beq means brach if equal.
# :al means always, so bal is an unconditional branch (but b() also works)
CONDITIONS = [ :al , :eq , :ne , :lt , :le, :ge, :gt , :cs , :mi , :hi , :cc , :pl, :ls , :vc , :vs ]
# here we create the shortcuts for the "standard" instructions, see above
# Derived machines may use own instructions and define functions for them if so desired
def initialize
[:push, :pop].each do |inst|
define_instruction_one(inst , StackInstruction)
end
[:adc, :add, :and, :bic, :eor, :orr, :rsb, :rsc, :sbc, :sub].each do |inst|
define_instruction_three(inst , LogicInstruction)
end
[:mov, :mvn].each do |inst|
define_instruction_two(inst , MoveInstruction)
end
[:cmn, :cmp, :teq, :tst].each do |inst|
define_instruction_two(inst , CompareInstruction)
end
[:strb, :str , :ldrb, :ldr].each do |inst|
define_instruction_three(inst , MemoryInstruction)
end
[:b, :call , :swi].each do |inst|
define_instruction_one(inst , CallInstruction)
end
# create all possible brach instructions, but the CallInstruction demangles the
# code, and has opcode set to :b and :condition_code set to the condition
CONDITIONS.each do |suffix|
define_instruction_one("b#{suffix}".to_sym , CallInstruction)
define_instruction_one("call#{suffix}".to_sym , CallInstruction)
end
end
def create_method(name, &block)
self.class.send(:define_method, name , &block)
end
def self.instance
if(@@instance.nil?)
@@instance = Arm::ArmMachine.new
end
@@instance
end
def self.instance= machine
@@instance = machine
end
def class_for clazz
c_name = clazz.name
my_module = self.class.name.split("::").first
clazz_name = clazz.name.split("::").last
if(my_module != Register )
module_class = eval("#{my_module}::#{clazz_name}") rescue nil
clazz = module_class if module_class
end
clazz
end
private
#defining the instruction (opcode, symbol) as an given class.
# the class is a Register::Instruction derived base class and to create machine specific function
# an actual machine must create derived classes (from this base class)
# These instruction classes must follow a naming pattern and take a hash in the contructor
# Example, a mov() opcode instantiates a Register::MoveInstruction
# for an Arm machine, a class Arm::MoveInstruction < Register::MoveInstruction exists, and it will
# be used to define the mov on an arm machine.
# This methods picks up that derived class and calls a define_instruction methods that can
# be overriden in subclasses
def define_instruction_one(inst , clazz , defaults = {} )
clazz = self.class_for(clazz)
create_method(inst) do |first , options = nil|
options = {} if options == nil
options.merge defaults
options[:opcode] = inst
first = Register::RegisterReference.new(first) if first.is_a? Symbol
clazz.new(first , options)
end
end
# same for two args (left right, from to etc)
def define_instruction_two(inst , clazz , defaults = {} )
clazz = self.class_for(clazz)
create_method(inst) do |left ,right , options = nil|
options = {} if options == nil
options.merge defaults
left = Register::RegisterReference.new(left) if left.is_a? Symbol
right = Register::RegisterReference.new(right) if right.is_a? Symbol
options[:opcode] = inst
clazz.new(left , right ,options)
end
end
# same for three args (result = left right,)
def define_instruction_three(inst , clazz , defaults = {} )
clazz = self.class_for(clazz)
create_method(inst) do |result , left ,right = nil , options = nil|
options = {} if options == nil
options.merge defaults
options[:opcode] = inst
result = Register::RegisterReference.new(result) if result.is_a? Symbol
left = Register::RegisterReference.new(left) if left.is_a? Symbol
right = Register::RegisterReference.new(right) if right.is_a? Symbol
clazz.new(result, left , right ,options)
end
end
end
end
require_relative "instruction" require_relative "instruction"
require_relative "register_reference" require_relative "register_reference"
require "arm/arm_machine" require "arm/arm_machine"

View File

@ -37,76 +37,8 @@ module Virtual
end end
end end
# the first instruction we need is to stop. Off course in a real machine this would be a syscall, but that is just
# an implementation (in a programm it would be a function). But in a virtual machine, not only do we need this instruction,
# it is indeed the first instruction as just this instruction is the smallest possible programm for the machine.
# As such it is the next instruction for any first instruction that we generate.
class Halt < Instruction
end
# following classes are stubs. currently in brainstorming mode, so anything may change anytime
class MethodEnter < Instruction
end
class MethodReturn < Instruction
end
# a branch must branch to a block. This is an abstract class, names indicate the actual test
class Branch < Instruction
def initialize to
@to = to
end
attr_reader :to
end
# implicit means there is no explcit test involved.
# normal ruby rules are false and nil are false, EVERYTHING else is true (and that includes 0)
class ImplicitBranch < Branch
end
class UnconditionalBranch < Branch
end
class NewMessage < Instruction
end
class NewFrame < Instruction
end
class MessageSend < Instruction
def initialize name , me , args = []
@name = name.to_sym
@me = me
@args = args
end
attr_reader :name , :me , :args
end
class FunctionCall < Instruction
def initialize method
@method = method
end
attr_reader :method
end
# class for Set instructions, A set is basically a mem move.
# to and from are indexes into the known objects(frame,message,self and new_message), these are represented as slots
# (see there)
# from may be a Constant (Object,Integer,String,Class)
class Set < Instruction
def initialize to , from
@to = to
# hard to find afterwards where it came from, so ensure it doesn't happen
raise "From must be slot or constant, not symbol #{from}" if from.is_a? Symbol
@from = from
end
attr_reader :to , :from
end
# Get a instance variable by _name_ . So we have to resolve the name to an index to trnsform into a Slot
# The slot may the be used in a set on left or right hand. The transformation is done by GetImplementation
class InstanceGet < Instruction
def initialize name
@name = name.to_sym
end
attr_reader :name
end
end end
require_relative "instructions/access.rb"
require_relative "instructions/control.rb"
require_relative "instructions/messaging.rb"

View File

@ -0,0 +1,25 @@
module Virtual
# class for Set instructions, A set is basically a mem move.
# to and from are indexes into the known objects(frame,message,self and new_message), these are represented as slots
# (see there)
# from may be a Constant (Object,Integer,String,Class)
class Set < Instruction
def initialize to , from
@to = to
# hard to find afterwards where it came from, so ensure it doesn't happen
raise "From must be slot or constant, not symbol #{from}" if from.is_a? Symbol
@from = from
end
attr_reader :to , :from
end
# Get a instance variable by _name_ . So we have to resolve the name to an index to trnsform into a Slot
# The slot may the be used in a set on left or right hand. The transformation is done by GetImplementation
class InstanceGet < Instruction
def initialize name
@name = name.to_sym
end
attr_reader :name
end
end

View File

@ -0,0 +1,30 @@
module Virtual
# the first instruction we need is to stop. Off course in a real machine this would be a syscall, but that is just
# an implementation (in a programm it would be a function). But in a virtual machine, not only do we need this instruction,
# it is indeed the first instruction as just this instruction is the smallest possible programm for the machine.
# As such it is the next instruction for any first instruction that we generate.
class Halt < Instruction
end
class MethodReturn < Instruction
end
# a branch must branch to a block. This is an abstract class, names indicate the actual test
class Branch < Instruction
def initialize to
@to = to
end
attr_reader :to
end
# implicit means there is no explcit test involved.
# normal ruby rules are false and nil are false, EVERYTHING else is true (and that includes 0)
class ImplicitBranch < Branch
end
class UnconditionalBranch < Branch
end
end

View File

@ -0,0 +1,28 @@
module Virtual
# following classes are stubs. currently in brainstorming mode, so anything may change anytime
class MethodEnter < Instruction
end
class NewMessage < Instruction
end
class NewFrame < Instruction
end
class MessageSend < Instruction
def initialize name , me , args = []
@name = name.to_sym
@me = me
@args = args
end
attr_reader :name , :me , :args
end
class FunctionCall < Instruction
def initialize method
@method = method
end
attr_reader :method
end
end