From 2230a4f25ed0eb938470eb8b5847a9c8af690159 Mon Sep 17 00:00:00 2001 From: Torsten Ruger Date: Wed, 14 May 2014 10:47:30 +0300 Subject: [PATCH] clean up intruction instantiation and fix tests --- lib/arm/arm_machine.rb | 23 +------------ lib/arm/call_instruction.rb | 11 +++--- lib/arm/compare_instruction.rb | 2 +- lib/arm/constants.rb | 6 ++-- lib/arm/logic_helper.rb | 2 +- lib/arm/logic_instruction.rb | 4 +-- lib/arm/memory_instruction.rb | 4 +-- lib/arm/move_instruction.rb | 4 +-- lib/arm/stack_instruction.rb | 4 +-- lib/ast/function_expression.rb | 10 ++---- lib/core/kernel.rb | 2 ++ lib/vm/block.rb | 1 + lib/vm/c_machine.rb | 61 ++++++++++++++++++++++++---------- lib/vm/instruction.rb | 8 +++++ test/test_arm.rb | 10 +++--- test/test_small_program.rb | 10 ++---- 16 files changed, 86 insertions(+), 76 deletions(-) diff --git a/lib/arm/arm_machine.rb b/lib/arm/arm_machine.rb index 6a0ab3e0..62e9b990 100644 --- a/lib/arm/arm_machine.rb +++ b/lib/arm/arm_machine.rb @@ -5,31 +5,10 @@ require_relative "move_instruction" require_relative "compare_instruction" require_relative "memory_instruction" require_relative "call_instruction" +require_relative "constants" module Arm 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 block.add_code cmp(:left => left , :right => right ) diff --git a/lib/arm/call_instruction.rb b/lib/arm/call_instruction.rb index dd8d332c..18664ee4 100644 --- a/lib/arm/call_instruction.rb +++ b/lib/arm/call_instruction.rb @@ -5,7 +5,6 @@ module Arm # 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 # 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) super(attributes) @attributes[:update_status_flag] = 0 - @attributes[:condition_code] = :al + @attributes[:condition_code] = :al if @attributes[:condition_code] == nil end def assemble(io) case @attributes[:opcode] - when :b, :bl + when :b, :call arg = @attributes[:left] #puts "BLAB #{arg.inspect}" 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 io << packed[0,3] else - raise "else not coded #{inspect}" + raise "else not coded arg =#{arg}: #{inspect}" 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 arg = @attributes[:left] if( arg.is_a? Fixnum ) #HACK to not have to change the code just now @@ -61,6 +60,8 @@ module Arm else raise "invalid operand argument expected literal not #{arg} #{inspect}" end + else + raise "Should not be the case #{inspect}" end end diff --git a/lib/arm/compare_instruction.rb b/lib/arm/compare_instruction.rb index 4706007a..16c74277 100644 --- a/lib/arm/compare_instruction.rb +++ b/lib/arm/compare_instruction.rb @@ -7,7 +7,7 @@ module Arm def initialize(attributes) super(attributes) - @attributes[:condition_code] = :al + @attributes[:condition_code] = :al if @attributes[:condition_code] == nil @operand = 0 @i = 0 @attributes[:update_status_flag] = 1 diff --git a/lib/arm/constants.rb b/lib/arm/constants.rb index 6a826e1d..6beda84a 100644 --- a/lib/arm/constants.rb +++ b/lib/arm/constants.rb @@ -18,12 +18,12 @@ module Arm :tst => 0b1000, :b => 0b1010, - :bl => 0b1011, - :bx => 0b00010010 + :call=> 0b1011 } #return the bit patter that the cpu uses for the current instruction @attributes[:opcode] 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 #codition codes can be applied to many instructions and thus save branches diff --git a/lib/arm/logic_helper.rb b/lib/arm/logic_helper.rb index 4ba66278..2b22c1d6 100644 --- a/lib/arm/logic_helper.rb +++ b/lib/arm/logic_helper.rb @@ -1,7 +1,7 @@ module Arm # 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 :operand diff --git a/lib/arm/logic_instruction.rb b/lib/arm/logic_instruction.rb index f82c8028..03ce11cd 100644 --- a/lib/arm/logic_instruction.rb +++ b/lib/arm/logic_instruction.rb @@ -8,8 +8,8 @@ module Arm def initialize(attributes) super(attributes) - @attributes[:update_status_flag] = 0 - @attributes[:condition_code] = :al + @attributes[:update_status_flag] = 0 if @attributes[:update_status_flag] == nil + @attributes[:condition_code] = :al if @attributes[:condition_code] == nil @operand = 0 @rn = nil diff --git a/lib/arm/memory_instruction.rb b/lib/arm/memory_instruction.rb index c8b84f54..082377f7 100644 --- a/lib/arm/memory_instruction.rb +++ b/lib/arm/memory_instruction.rb @@ -8,8 +8,8 @@ module Arm def initialize(attributes) super(attributes) - @attributes[:update_status_flag] = 0 - @attributes[:condition_code] = :al + @attributes[:update_status_flag] = 0 if @attributes[:update_status_flag] == nil + @attributes[:condition_code] = :al if @attributes[:condition_code] == nil @operand = 0 @pre_post_index = 0 #P flag diff --git a/lib/arm/move_instruction.rb b/lib/arm/move_instruction.rb index 7e042328..72986028 100644 --- a/lib/arm/move_instruction.rb +++ b/lib/arm/move_instruction.rb @@ -8,8 +8,8 @@ module Arm def initialize(attributes) super(attributes) - @attributes[:update_status_flag] = 0 - @attributes[:condition_code] = :al + @attributes[:update_status_flag] = 0 if @attributes[:update_status_flag] == nil + @attributes[:condition_code] = :al if @attributes[:condition_code] == nil @attributes[:opcode] = attributes[:opcode] @operand = 0 diff --git a/lib/arm/stack_instruction.rb b/lib/arm/stack_instruction.rb index a8e60faf..b41c72bd 100644 --- a/lib/arm/stack_instruction.rb +++ b/lib/arm/stack_instruction.rb @@ -13,8 +13,8 @@ module Arm def initialize(attributes) super(attributes) - @attributes[:update_status_flag] = 0 - @attributes[:condition_code] = :al + @attributes[:update_status_flag] = 0 if @attributes[:update_status_flag] == nil + @attributes[:condition_code] = :al if @attributes[:condition_code] == nil @attributes[:opcode] = attributes[:opcode] @operand = 0 diff --git a/lib/ast/function_expression.rb b/lib/ast/function_expression.rb index 701a1671..913a9809 100644 --- a/lib/ast/function_expression.rb +++ b/lib/ast/function_expression.rb @@ -34,15 +34,11 @@ module Ast context.locals = locals context.function = function - into = function.entry + into = function.body block.each do |b| compiled = b.compile(context , into) - if compiled.is_a? Vm::Block - into = compiled - he.breaks.loose - else - function.body.add_code compiled - end + function.return_type = compiled + raise "alarm " unless compiled.is_a? Vm::Word puts compiled.inspect end context.locals = parent_locals diff --git a/lib/core/kernel.rb b/lib/core/kernel.rb index 79e1adbf..4f707b94 100644 --- a/lib/core/kernel.rb +++ b/lib/core/kernel.rb @@ -7,10 +7,12 @@ module Core def main_start block #TODO extract args into array of strings Vm::CMachine.instance.main_start block + block end def main_exit block # Machine.exit mov r7 , 0 + swi 0 Vm::CMachine.instance.main_exit block + block end def function_entry block , f_name Vm::CMachine.instance.function_entry block , f_name diff --git a/lib/vm/block.rb b/lib/vm/block.rb index a75c86fa..dc76ea1e 100644 --- a/lib/vm/block.rb +++ b/lib/vm/block.rb @@ -34,6 +34,7 @@ module Vm end def add_code(kode) + raise "alarm #{kode}" if kode.is_a? Word @codes << kode self end diff --git a/lib/vm/c_machine.rb b/lib/vm/c_machine.rb index 081ad2d6..cb376ad6 100644 --- a/lib/vm/c_machine.rb +++ b/lib/vm/c_machine.rb @@ -36,46 +36,53 @@ module Vm # 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(inst , StackInstruction) + define_instruction_for(inst , StackInstruction) end - [:adc, :add, :and, :bic, :eor, :orr, :rsb, :rsc, :sbc, :sub].each do |inst| - define_instruction(inst , LogicInstruction) + define_instruction_for(inst , LogicInstruction) end [:mov, :mvn].each do |inst| - define_instruction(inst , MoveInstruction) + define_instruction_for(inst , MoveInstruction) end [:cmn, :cmp, :teq, :tst].each do |inst| - define_instruction(inst , CompareInstruction) + define_instruction_for(inst , CompareInstruction) end [:strb, :str , :ldrb, :ldr].each do |inst| - define_instruction(inst , MemoryInstruction) + define_instruction_for(inst , MemoryInstruction) end - [:b, :bl , :swi].each do |inst| - define_instruction(inst , CallInstruction) + [:b, :call , :swi].each do |inst| + 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 def create_method(name, &block) self.class.send(:define_method, name , &block) end - def define_instruction(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 the instruction inst (given as a symbol) on this class as a methods + # As we define a standard set of instructions (or memnonics) , this turns this class into a kind of + # Assembler, in that you can write .mov() or .pop() and those functions mean the same as if they + # were in an assembler file (also options are the same) + # defaults gets merged into the instructions options hash, ie passed on to the (machine specific) + # Instruction constructor and as such can be used to influence that classes behaviour + def define_instruction(inst , clazz , defaults = {} ) create_method(inst) do |options| options = {} if options == nil + options.merge defaults options[:opcode] = inst clazz.new(options) end @@ -87,5 +94,25 @@ module Vm def self.instance= machine @@instance = machine 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 diff --git a/lib/vm/instruction.rb b/lib/vm/instruction.rb index 3a6c7826..26169ded 100644 --- a/lib/vm/instruction.rb +++ b/lib/vm/instruction.rb @@ -43,5 +43,13 @@ module Vm class MoveInstruction < Instruction end 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 diff --git a/test/test_arm.rb b/test/test_arm.rb index 8d170107..64dc01be 100644 --- a/test/test_arm.rb +++ b/test/test_arm.rb @@ -3,7 +3,7 @@ require_relative 'helper' # try to test that the generation of basic instructions works # one instruction at a time, reverse testing from objdump --demangle -Sfghxp # 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 # need Assembler and a block (see those classes) @@ -19,7 +19,7 @@ class TestArmAsm < MiniTest::Test io = StringIO.new code.assemble(io) binary = io.string - assert_equal 4 , binary.length + assert_equal 4 , binary.length , "code length wrong for #{code.inspect}" index = 0 binary.each_byte do |byte | 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 assert_code code , :b , [0xff,0xff,0xff,0xea] #ea ff ff fe end - def test_bl #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 - assert_code code , :bl , [0xff,0xff,0xff,0xeb] #ea ff ff fe + def test_call #see comment above. bx not implemented (as it means into thumb, and no thumb here) + code = @machine.call :left => -4 #this jumps to the next instruction + assert_code code , :call, [0xff,0xff,0xff,0xeb] #ea ff ff fe end def test_bic code = @machine.bic :left => :r2 , :right => :r2 , :extra => :r3 diff --git a/test/test_small_program.rb b/test/test_small_program.rb index 56fb7f19..d35db480 100644 --- a/test/test_small_program.rb +++ b/test/test_small_program.rb @@ -18,10 +18,8 @@ class TestSmallProg < MiniTest::Test start = Vm::Block.new("start") add_code start start.instance_eval do - #subs :left => :r0, :right => :r0, :offset => 1 #2 - #bne :left => :start #3 - mov :left => :r7, :right => 1 #4 - swi :left => 0 #5 5 instructions + sub :left => :r0, :right => :r0, :extra => 1 , :update_status_flag => 1 #2 + bne :left => start #3 end end write( 6 , "loop" ) @@ -36,10 +34,8 @@ class TestSmallProg < MiniTest::Test add :left =>:r1 , :extra => hello # address of "hello Raisa" mov :left =>:r2 , :right => hello.length swi :left => 0 #software interupt, ie kernel syscall - mov :left => :r7, :right => 1 # 1 == exit - swi :left => 0 end - write(9 + hello.length/4 + 1 , 'hello') + write(7 + hello.length/4 + 1 , 'hello') end #helper to write the file