clean up intruction instantiation and fix tests
This commit is contained in:
parent
46ea1df51e
commit
2230a4f25e
@ -5,31 +5,10 @@ require_relative "move_instruction"
|
|||||||
require_relative "compare_instruction"
|
require_relative "compare_instruction"
|
||||||
require_relative "memory_instruction"
|
require_relative "memory_instruction"
|
||||||
require_relative "call_instruction"
|
require_relative "call_instruction"
|
||||||
|
require_relative "constants"
|
||||||
|
|
||||||
module Arm
|
module Arm
|
||||||
class ArmMachine < Vm::CMachine
|
class ArmMachine < Vm::CMachine
|
||||||
|
|
||||||
# defines a method in the current class, with the name inst (first erg)
|
|
||||||
# the method instantiates an instruction of the given class which gets passed a single hash as arg
|
|
||||||
|
|
||||||
# gets called for every "standard" instruction.
|
|
||||||
# may be used for machine specific ones too
|
|
||||||
def define_instruction inst , clazz
|
|
||||||
super
|
|
||||||
return
|
|
||||||
# need to use create_method and move to attributes hash
|
|
||||||
define_method("#{inst}s") do |*args|
|
|
||||||
instruction clazz , inst , :al , 1 , *args
|
|
||||||
end
|
|
||||||
ArmMachine::COND_CODES.keys.each do |suffix|
|
|
||||||
define_method("#{inst}#{suffix}") do |attributes|
|
|
||||||
instruction clazz , inst , suffix , 0 , *args
|
|
||||||
end
|
|
||||||
define_method("#{inst}s#{suffix}") do |attributes|
|
|
||||||
instruction clazz , inst , suffix , 1 , *args
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def integer_less_or_equal block , left , right
|
def integer_less_or_equal block , left , right
|
||||||
block.add_code cmp(:left => left , :right => right )
|
block.add_code cmp(:left => left , :right => right )
|
||||||
|
@ -5,7 +5,6 @@ module Arm
|
|||||||
|
|
||||||
# A branch could be called a jump as it has no notion of returning
|
# A branch could be called a jump as it has no notion of returning
|
||||||
|
|
||||||
# A call has the bl code as someone thought "branch with link" is a useful name.
|
|
||||||
# The pc is put into the link register to make a return possible
|
# The pc is put into the link register to make a return possible
|
||||||
# a return is affected by moving the stored link register into the pc, effectively a branch
|
# a return is affected by moving the stored link register into the pc, effectively a branch
|
||||||
|
|
||||||
@ -24,12 +23,12 @@ module Arm
|
|||||||
def initialize(attributes)
|
def initialize(attributes)
|
||||||
super(attributes)
|
super(attributes)
|
||||||
@attributes[:update_status_flag] = 0
|
@attributes[:update_status_flag] = 0
|
||||||
@attributes[:condition_code] = :al
|
@attributes[:condition_code] = :al if @attributes[:condition_code] == nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def assemble(io)
|
def assemble(io)
|
||||||
case @attributes[:opcode]
|
case @attributes[:opcode]
|
||||||
when :b, :bl
|
when :b, :call
|
||||||
arg = @attributes[:left]
|
arg = @attributes[:left]
|
||||||
#puts "BLAB #{arg.inspect}"
|
#puts "BLAB #{arg.inspect}"
|
||||||
if( arg.is_a? Fixnum ) #HACK to not have to change the code just now
|
if( arg.is_a? Fixnum ) #HACK to not have to change the code just now
|
||||||
@ -46,9 +45,9 @@ module Arm
|
|||||||
# TODO add check that the value fits into 24 bits
|
# TODO add check that the value fits into 24 bits
|
||||||
io << packed[0,3]
|
io << packed[0,3]
|
||||||
else
|
else
|
||||||
raise "else not coded #{inspect}"
|
raise "else not coded arg =#{arg}: #{inspect}"
|
||||||
end
|
end
|
||||||
io.write_uint8 OPCODES[opcode] | (COND_CODES[@attributes[:condition_code]] << 4)
|
io.write_uint8 op_bit_code | (COND_CODES[@attributes[:condition_code]] << 4)
|
||||||
when :swi
|
when :swi
|
||||||
arg = @attributes[:left]
|
arg = @attributes[:left]
|
||||||
if( arg.is_a? Fixnum ) #HACK to not have to change the code just now
|
if( arg.is_a? Fixnum ) #HACK to not have to change the code just now
|
||||||
@ -61,6 +60,8 @@ module Arm
|
|||||||
else
|
else
|
||||||
raise "invalid operand argument expected literal not #{arg} #{inspect}"
|
raise "invalid operand argument expected literal not #{arg} #{inspect}"
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
raise "Should not be the case #{inspect}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ module Arm
|
|||||||
|
|
||||||
def initialize(attributes)
|
def initialize(attributes)
|
||||||
super(attributes)
|
super(attributes)
|
||||||
@attributes[:condition_code] = :al
|
@attributes[:condition_code] = :al if @attributes[:condition_code] == nil
|
||||||
@operand = 0
|
@operand = 0
|
||||||
@i = 0
|
@i = 0
|
||||||
@attributes[:update_status_flag] = 1
|
@attributes[:update_status_flag] = 1
|
||||||
|
@ -18,12 +18,12 @@ module Arm
|
|||||||
:tst => 0b1000,
|
:tst => 0b1000,
|
||||||
|
|
||||||
:b => 0b1010,
|
:b => 0b1010,
|
||||||
:bl => 0b1011,
|
:call=> 0b1011
|
||||||
:bx => 0b00010010
|
|
||||||
}
|
}
|
||||||
#return the bit patter that the cpu uses for the current instruction @attributes[:opcode]
|
#return the bit patter that the cpu uses for the current instruction @attributes[:opcode]
|
||||||
def op_bit_code
|
def op_bit_code
|
||||||
OPCODES[@attributes[:opcode]] or throw "no code found for #{@attributes[:opcode].inspect}"
|
bit_code = OPCODES[@attributes[:opcode]]
|
||||||
|
bit_code or raise "no code found for #{@attributes[:opcode].inspect}"
|
||||||
end
|
end
|
||||||
|
|
||||||
#codition codes can be applied to many instructions and thus save branches
|
#codition codes can be applied to many instructions and thus save branches
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
module Arm
|
module Arm
|
||||||
# Many arm instructions may be conditional, where the default condition is always (al)
|
# Many arm instructions may be conditional, where the default condition is always (al)
|
||||||
# ArmMachine::COND_CODES names them, and this attribute reflects it
|
# Constants::COND_CODES names them, and this attribute reflects it
|
||||||
#attr_reader :condition_code
|
#attr_reader :condition_code
|
||||||
#attr_reader :operand
|
#attr_reader :operand
|
||||||
|
|
||||||
|
@ -8,8 +8,8 @@ module Arm
|
|||||||
|
|
||||||
def initialize(attributes)
|
def initialize(attributes)
|
||||||
super(attributes)
|
super(attributes)
|
||||||
@attributes[:update_status_flag] = 0
|
@attributes[:update_status_flag] = 0 if @attributes[:update_status_flag] == nil
|
||||||
@attributes[:condition_code] = :al
|
@attributes[:condition_code] = :al if @attributes[:condition_code] == nil
|
||||||
@operand = 0
|
@operand = 0
|
||||||
|
|
||||||
@rn = nil
|
@rn = nil
|
||||||
|
@ -8,8 +8,8 @@ module Arm
|
|||||||
|
|
||||||
def initialize(attributes)
|
def initialize(attributes)
|
||||||
super(attributes)
|
super(attributes)
|
||||||
@attributes[:update_status_flag] = 0
|
@attributes[:update_status_flag] = 0 if @attributes[:update_status_flag] == nil
|
||||||
@attributes[:condition_code] = :al
|
@attributes[:condition_code] = :al if @attributes[:condition_code] == nil
|
||||||
@operand = 0
|
@operand = 0
|
||||||
|
|
||||||
@pre_post_index = 0 #P flag
|
@pre_post_index = 0 #P flag
|
||||||
|
@ -8,8 +8,8 @@ module Arm
|
|||||||
|
|
||||||
def initialize(attributes)
|
def initialize(attributes)
|
||||||
super(attributes)
|
super(attributes)
|
||||||
@attributes[:update_status_flag] = 0
|
@attributes[:update_status_flag] = 0 if @attributes[:update_status_flag] == nil
|
||||||
@attributes[:condition_code] = :al
|
@attributes[:condition_code] = :al if @attributes[:condition_code] == nil
|
||||||
@attributes[:opcode] = attributes[:opcode]
|
@attributes[:opcode] = attributes[:opcode]
|
||||||
@operand = 0
|
@operand = 0
|
||||||
|
|
||||||
|
@ -13,8 +13,8 @@ module Arm
|
|||||||
|
|
||||||
def initialize(attributes)
|
def initialize(attributes)
|
||||||
super(attributes)
|
super(attributes)
|
||||||
@attributes[:update_status_flag] = 0
|
@attributes[:update_status_flag] = 0 if @attributes[:update_status_flag] == nil
|
||||||
@attributes[:condition_code] = :al
|
@attributes[:condition_code] = :al if @attributes[:condition_code] == nil
|
||||||
@attributes[:opcode] = attributes[:opcode]
|
@attributes[:opcode] = attributes[:opcode]
|
||||||
@operand = 0
|
@operand = 0
|
||||||
|
|
||||||
|
@ -34,15 +34,11 @@ module Ast
|
|||||||
context.locals = locals
|
context.locals = locals
|
||||||
context.function = function
|
context.function = function
|
||||||
|
|
||||||
into = function.entry
|
into = function.body
|
||||||
block.each do |b|
|
block.each do |b|
|
||||||
compiled = b.compile(context , into)
|
compiled = b.compile(context , into)
|
||||||
if compiled.is_a? Vm::Block
|
function.return_type = compiled
|
||||||
into = compiled
|
raise "alarm " unless compiled.is_a? Vm::Word
|
||||||
he.breaks.loose
|
|
||||||
else
|
|
||||||
function.body.add_code compiled
|
|
||||||
end
|
|
||||||
puts compiled.inspect
|
puts compiled.inspect
|
||||||
end
|
end
|
||||||
context.locals = parent_locals
|
context.locals = parent_locals
|
||||||
|
@ -7,10 +7,12 @@ module Core
|
|||||||
def main_start block
|
def main_start block
|
||||||
#TODO extract args into array of strings
|
#TODO extract args into array of strings
|
||||||
Vm::CMachine.instance.main_start block
|
Vm::CMachine.instance.main_start block
|
||||||
|
block
|
||||||
end
|
end
|
||||||
def main_exit block
|
def main_exit block
|
||||||
# Machine.exit mov r7 , 0 + swi 0
|
# Machine.exit mov r7 , 0 + swi 0
|
||||||
Vm::CMachine.instance.main_exit block
|
Vm::CMachine.instance.main_exit block
|
||||||
|
block
|
||||||
end
|
end
|
||||||
def function_entry block , f_name
|
def function_entry block , f_name
|
||||||
Vm::CMachine.instance.function_entry block , f_name
|
Vm::CMachine.instance.function_entry block , f_name
|
||||||
|
@ -34,6 +34,7 @@ module Vm
|
|||||||
end
|
end
|
||||||
|
|
||||||
def add_code(kode)
|
def add_code(kode)
|
||||||
|
raise "alarm #{kode}" if kode.is_a? Word
|
||||||
@codes << kode
|
@codes << kode
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
@ -36,46 +36,53 @@ module Vm
|
|||||||
# consistency in this code, but also because that is what is actually done
|
# consistency in this code, but also because that is what is actually done
|
||||||
attr_reader :status
|
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
|
# 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
|
# Derived machines may use own instructions and define functions for them if so desired
|
||||||
def initialize
|
def initialize
|
||||||
[:push, :pop].each do |inst|
|
[:push, :pop].each do |inst|
|
||||||
define_instruction(inst , StackInstruction)
|
define_instruction_for(inst , StackInstruction)
|
||||||
end
|
end
|
||||||
|
|
||||||
[:adc, :add, :and, :bic, :eor, :orr, :rsb, :rsc, :sbc, :sub].each do |inst|
|
[:adc, :add, :and, :bic, :eor, :orr, :rsb, :rsc, :sbc, :sub].each do |inst|
|
||||||
define_instruction(inst , LogicInstruction)
|
define_instruction_for(inst , LogicInstruction)
|
||||||
end
|
end
|
||||||
[:mov, :mvn].each do |inst|
|
[:mov, :mvn].each do |inst|
|
||||||
define_instruction(inst , MoveInstruction)
|
define_instruction_for(inst , MoveInstruction)
|
||||||
end
|
end
|
||||||
[:cmn, :cmp, :teq, :tst].each do |inst|
|
[:cmn, :cmp, :teq, :tst].each do |inst|
|
||||||
define_instruction(inst , CompareInstruction)
|
define_instruction_for(inst , CompareInstruction)
|
||||||
end
|
end
|
||||||
[:strb, :str , :ldrb, :ldr].each do |inst|
|
[:strb, :str , :ldrb, :ldr].each do |inst|
|
||||||
define_instruction(inst , MemoryInstruction)
|
define_instruction_for(inst , MemoryInstruction)
|
||||||
end
|
end
|
||||||
[:b, :bl , :swi].each do |inst|
|
[:b, :call , :swi].each do |inst|
|
||||||
define_instruction(inst , CallInstruction)
|
define_instruction_for(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_for("b#{suffix}".to_sym , CallInstruction)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_method(name, &block)
|
def create_method(name, &block)
|
||||||
self.class.send(:define_method, name , &block)
|
self.class.send(:define_method, name , &block)
|
||||||
end
|
end
|
||||||
|
|
||||||
def define_instruction(inst , clazz )
|
# define the instruction inst (given as a symbol) on this class as a methods
|
||||||
c_name = clazz.name
|
# As we define a standard set of instructions (or memnonics) , this turns this class into a kind of
|
||||||
my_module = self.class.name.split("::").first
|
# Assembler, in that you can write .mov() or .pop() and those functions mean the same as if they
|
||||||
clazz_name = clazz.name.split("::").last
|
# were in an assembler file (also options are the same)
|
||||||
if(my_module != Vm )
|
# defaults gets merged into the instructions options hash, ie passed on to the (machine specific)
|
||||||
module_class = eval("#{my_module}::#{clazz_name}") rescue nil
|
# Instruction constructor and as such can be used to influence that classes behaviour
|
||||||
clazz = module_class if module_class
|
def define_instruction(inst , clazz , defaults = {} )
|
||||||
end
|
|
||||||
create_method(inst) do |options|
|
create_method(inst) do |options|
|
||||||
options = {} if options == nil
|
options = {} if options == nil
|
||||||
|
options.merge defaults
|
||||||
options[:opcode] = inst
|
options[:opcode] = inst
|
||||||
clazz.new(options)
|
clazz.new(options)
|
||||||
end
|
end
|
||||||
@ -87,5 +94,25 @@ module Vm
|
|||||||
def self.instance= machine
|
def self.instance= machine
|
||||||
@@instance = machine
|
@@instance = machine
|
||||||
end
|
end
|
||||||
|
private
|
||||||
|
#defining the instruction (opcode, symbol) as an given class.
|
||||||
|
# the class is a Vm::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 Vm::MoveInstruction
|
||||||
|
# for an Arm machine, a class Arm::MoveInstruction < Vm::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_for(inst , clazz )
|
||||||
|
c_name = clazz.name
|
||||||
|
my_module = self.class.name.split("::").first
|
||||||
|
clazz_name = clazz.name.split("::").last
|
||||||
|
if(my_module != Vm )
|
||||||
|
module_class = eval("#{my_module}::#{clazz_name}") rescue nil
|
||||||
|
clazz = module_class if module_class
|
||||||
|
end
|
||||||
|
define_instruction(inst , clazz )
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -43,5 +43,13 @@ module Vm
|
|||||||
class MoveInstruction < Instruction
|
class MoveInstruction < Instruction
|
||||||
end
|
end
|
||||||
class CallInstruction < Instruction
|
class CallInstruction < Instruction
|
||||||
|
def initialize options
|
||||||
|
super
|
||||||
|
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
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -3,7 +3,7 @@ require_relative 'helper'
|
|||||||
# try to test that the generation of basic instructions works
|
# try to test that the generation of basic instructions works
|
||||||
# one instruction at a time, reverse testing from objdump --demangle -Sfghxp
|
# one instruction at a time, reverse testing from objdump --demangle -Sfghxp
|
||||||
# tests are named as per assembler code, ie test_mov testing mov instruction
|
# tests are named as per assembler code, ie test_mov testing mov instruction
|
||||||
# adc add and bic eor orr rsb rsc sbc sub mov mvn cmn cmp teq tst b bl bx swi strb
|
# adc add and bic eor orr rsb rsc sbc sub mov mvn cmn cmp teq tst b call bx swi strb
|
||||||
|
|
||||||
class TestArmAsm < MiniTest::Test
|
class TestArmAsm < MiniTest::Test
|
||||||
# need Assembler and a block (see those classes)
|
# need Assembler and a block (see those classes)
|
||||||
@ -19,7 +19,7 @@ class TestArmAsm < MiniTest::Test
|
|||||||
io = StringIO.new
|
io = StringIO.new
|
||||||
code.assemble(io)
|
code.assemble(io)
|
||||||
binary = io.string
|
binary = io.string
|
||||||
assert_equal 4 , binary.length
|
assert_equal 4 , binary.length , "code length wrong for #{code.inspect}"
|
||||||
index = 0
|
index = 0
|
||||||
binary.each_byte do |byte |
|
binary.each_byte do |byte |
|
||||||
assert_equal should[index] , byte , "byte #{index} 0x#{should[index].to_s(16)} != 0x#{byte.to_s(16)}"
|
assert_equal should[index] , byte , "byte #{index} 0x#{should[index].to_s(16)} != 0x#{byte.to_s(16)}"
|
||||||
@ -45,9 +45,9 @@ class TestArmAsm < MiniTest::Test
|
|||||||
code = @machine.b :left => -4 #this jumps to the next instruction
|
code = @machine.b :left => -4 #this jumps to the next instruction
|
||||||
assert_code code , :b , [0xff,0xff,0xff,0xea] #ea ff ff fe
|
assert_code code , :b , [0xff,0xff,0xff,0xea] #ea ff ff fe
|
||||||
end
|
end
|
||||||
def test_bl #see comment above. bx not implemented (as it means into thumb, and no thumb here)
|
def test_call #see comment above. bx not implemented (as it means into thumb, and no thumb here)
|
||||||
code = @machine.bl :left => -4 #this jumps to the next instruction
|
code = @machine.call :left => -4 #this jumps to the next instruction
|
||||||
assert_code code , :bl , [0xff,0xff,0xff,0xeb] #ea ff ff fe
|
assert_code code , :call, [0xff,0xff,0xff,0xeb] #ea ff ff fe
|
||||||
end
|
end
|
||||||
def test_bic
|
def test_bic
|
||||||
code = @machine.bic :left => :r2 , :right => :r2 , :extra => :r3
|
code = @machine.bic :left => :r2 , :right => :r2 , :extra => :r3
|
||||||
|
@ -18,10 +18,8 @@ class TestSmallProg < MiniTest::Test
|
|||||||
start = Vm::Block.new("start")
|
start = Vm::Block.new("start")
|
||||||
add_code start
|
add_code start
|
||||||
start.instance_eval do
|
start.instance_eval do
|
||||||
#subs :left => :r0, :right => :r0, :offset => 1 #2
|
sub :left => :r0, :right => :r0, :extra => 1 , :update_status_flag => 1 #2
|
||||||
#bne :left => :start #3
|
bne :left => start #3
|
||||||
mov :left => :r7, :right => 1 #4
|
|
||||||
swi :left => 0 #5 5 instructions
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
write( 6 , "loop" )
|
write( 6 , "loop" )
|
||||||
@ -36,10 +34,8 @@ class TestSmallProg < MiniTest::Test
|
|||||||
add :left =>:r1 , :extra => hello # address of "hello Raisa"
|
add :left =>:r1 , :extra => hello # address of "hello Raisa"
|
||||||
mov :left =>:r2 , :right => hello.length
|
mov :left =>:r2 , :right => hello.length
|
||||||
swi :left => 0 #software interupt, ie kernel syscall
|
swi :left => 0 #software interupt, ie kernel syscall
|
||||||
mov :left => :r7, :right => 1 # 1 == exit
|
|
||||||
swi :left => 0
|
|
||||||
end
|
end
|
||||||
write(9 + hello.length/4 + 1 , 'hello')
|
write(7 + hello.length/4 + 1 , 'hello')
|
||||||
end
|
end
|
||||||
|
|
||||||
#helper to write the file
|
#helper to write the file
|
||||||
|
Loading…
x
Reference in New Issue
Block a user