From 92beb638de891b56a54af216fc69809319fd8bf9 Mon Sep 17 00:00:00 2001 From: Torsten Ruger Date: Fri, 25 Apr 2014 18:37:19 +0300 Subject: [PATCH] better logic with new block class --- lib/asm/block.rb | 107 ++++++++++++++++++++++---- lib/asm/call_instruction.rb | 4 +- lib/asm/instruction.rb | 3 +- lib/asm/logic_instruction.rb | 2 + lib/asm/memory_instruction.rb | 1 + lib/asm/program.rb | 141 +++++++++++++--------------------- lib/asm/stack_instruction.rb | 2 +- lib/crystal.rb | 3 +- test/test_crystal.rb | 63 +++++++-------- 9 files changed, 189 insertions(+), 137 deletions(-) diff --git a/lib/asm/block.rb b/lib/asm/block.rb index ec17be03..271664a1 100644 --- a/lib/asm/block.rb +++ b/lib/asm/block.rb @@ -1,32 +1,111 @@ -require_relative "code" +require_relative 'call_instruction' +require_relative 'stack_instruction' +require_relative 'logic_instruction' +require_relative 'memory_instruction' module Asm - # Labels are, like in assembler, a point to jump/branch to. An address in the stream. - # To allow for forward branches creation does not fix the position. Set does that. - class Label < Code - def initialize(name , asm) - super - @name = name + class Code ; end + + # A Block is the smalles unit of code, a list of instructions as it were + # It is also a point to jump/branch to. An address in the final stream. + # To allow for forward branches creation does not fix the position. Either set or assembling does that. + + # Blocks are also used to create instructions, and so Block has functions for every cpu instruction + # and to make using the apu function easier, there are functions that create registers as well + class Block < Code + + def initialize(asm) + super() + @codes = [] + @position = 0 @asm = asm end - # setting a label fixes it's position in the stream. - # For backwards jumps, positions of labels are known at creation, but for forward off course not. - # So then one can create a label, branch to it and set it later. + ArmMachine::REGISTERS.each do |reg , number| + define_method(reg) { Asm::Register.new(reg , number) } + end + + def instruction(clazz, opcode , condition_code , update_status , *args) + arg_nodes = [] + args.each do |arg| + if (arg.is_a?(Asm::Register)) + arg_nodes << arg + elsif (arg.is_a?(Integer)) + arg_nodes << Asm::NumLiteral.new(arg) + elsif (arg.is_a?(String)) + arg_nodes << @asm.add_string(arg) + elsif (arg.is_a?(Asm::Label)) + arg_nodes << arg + else + raise "Invalid argument #{arg.inspect} for instruction" + end + end + add_code clazz.new(opcode , condition_code , update_status , arg_nodes) + end + + + def self.define_instruction(inst , clazz ) + define_method(inst) do |*args| + instruction clazz , inst , :al , 0 , *args + end + 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 |*args| + instruction clazz , inst , suffix , 0 , *args + end + define_method("#{inst}s#{suffix}") do |*args| + instruction clazz , inst , suffix , 1 , *args + end + end + end + + [:push, :pop].each do |inst| + define_instruction(inst , StackInstruction) + end + + [:adc, :add, :and, :bic, :eor, :orr, :rsb, :rsc, :sbc, :sub].each do |inst| + define_instruction(inst , LogicInstruction) + end + [:mov, :mvn].each do |inst| + define_instruction(inst , MoveInstruction) + end + [:cmn, :cmp, :teq, :tst].each do |inst| + define_instruction(inst , CompareInstruction) + end + [:strb, :str , :ldrb, :ldr].each do |inst| + define_instruction(inst , MemoryInstruction) + end + [:b, :bl , :swi].each do |inst| + define_instruction(inst , CallInstruction) + end + + # setting a block fixes it's position in the stream. + # For backwards jumps, positions of blocks are known at creation, but for forward off course not. + # So then one can create a block, branch to it and set it later. def set! - @asm.add_value self + @asm.add_block self self end # Label has no length , 0 def length - 0 + @codes.sum :length + end + + def add_code(kode) + kode.at(@position) + length = kode.length + @position += length + @codes << kode end - # nothing to write, we check that the position is what was set def assemble(io) - raise "Hmm hmm hmm, me thinks i should be somewhere else" if self.position != io.tell + @codes.each do |obj| + obj.assemble io + end end end diff --git a/lib/asm/call_instruction.rb b/lib/asm/call_instruction.rb index 7547a69a..257fcd4a 100644 --- a/lib/asm/call_instruction.rb +++ b/lib/asm/call_instruction.rb @@ -1,3 +1,5 @@ +require_relative "instruction" + module Asm # There are only three call instructions in arm branch (b), call (bl) and syscall (swi) @@ -17,7 +19,7 @@ module Asm case opcode when :b, :bl arg = args[0] - if arg.is_a? Label + if arg.is_a? Block diff = arg.position - self.position - 8 arg = NumLiteral.new(diff) end diff --git a/lib/asm/instruction.rb b/lib/asm/instruction.rb index 07fd2dd3..ad4535c0 100644 --- a/lib/asm/instruction.rb +++ b/lib/asm/instruction.rb @@ -1,9 +1,10 @@ -require_relative "label" require_relative "assembly_error" require_relative "arm_machine" module Asm + class Code ; end + # Not surprisingly represents an cpu instruction. # This is an abstract base class, with derived classes # Logic / Move / Compare / Stack / Memory (see there) diff --git a/lib/asm/logic_instruction.rb b/lib/asm/logic_instruction.rb index 2a89828b..035e1725 100644 --- a/lib/asm/logic_instruction.rb +++ b/lib/asm/logic_instruction.rb @@ -1,3 +1,5 @@ +require_relative "instruction" + module Asm # ADDRESSING MODE 1 # Logic ,Maths, Move and compare instructions (last three below) diff --git a/lib/asm/memory_instruction.rb b/lib/asm/memory_instruction.rb index d606ab45..b5222486 100644 --- a/lib/asm/memory_instruction.rb +++ b/lib/asm/memory_instruction.rb @@ -1,4 +1,5 @@ require "asm/nodes" +require_relative "instruction" module Asm # ADDRESSING MODE 2 diff --git a/lib/asm/program.rb b/lib/asm/program.rb index 0b495140..76c1d0b6 100644 --- a/lib/asm/program.rb +++ b/lib/asm/program.rb @@ -1,125 +1,90 @@ -require 'asm/call_instruction' -require 'asm/stack_instruction' -require 'asm/logic_instruction' -require 'asm/memory_instruction' require 'asm/nodes' +require 'asm/block' require 'stream_reader' require 'stringio' require "asm/string_literal" module Asm - - class ArmAssembler - - ArmMachine::REGISTERS.each do |reg , number| - define_method(reg) { Asm::Register.new(reg , number) } - end + + # Program is the the top-level of the code hierachy, except it is not derived from code + # instead a Program is a list of blocks (and string constants) + + # All code is created in blocks (see there) and there are two styles for that, for forward of backward + # referencing. Read function block and add_block and Block.set + + + class Program def initialize - @codes = [] - @position = 0 # marks not set - @labels = [] + @blocks = [] @string_table = {} end - attr_reader :codes , :position - def instruction(clazz, opcode , condition_code , update_status , *args) - arg_nodes = [] - args.each do |arg| - if (arg.is_a?(Asm::Register)) - arg_nodes << arg - elsif (arg.is_a?(Integer)) - arg_nodes << Asm::NumLiteral.new(arg) - elsif (arg.is_a?(String)) - arg_nodes << add_string(arg) - elsif (arg.is_a?(Asm::Label)) - arg_nodes << arg - else - raise "Invalid argument #{arg.inspect} for instruction" - end - end - add_code clazz.new(opcode , condition_code , update_status , arg_nodes) - end - - - def self.define_instruction(inst , clazz ) - define_method(inst) do |*args| - instruction clazz , inst , :al , 0 , *args - end - 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 |*args| - instruction clazz , inst , suffix , 0 , *args - end - define_method("#{inst}s#{suffix}") do |*args| - instruction clazz , inst , suffix , 1 , *args - end - end - end + attr_reader :blocks - [:push, :pop].each do |inst| - define_instruction(inst , StackInstruction) - end - - [:adc, :add, :and, :bic, :eor, :orr, :rsb, :rsc, :sbc, :sub].each do |inst| - define_instruction(inst , LogicInstruction) - end - [:mov, :mvn].each do |inst| - define_instruction(inst , MoveInstruction) - end - [:cmn, :cmp, :teq, :tst].each do |inst| - define_instruction(inst , CompareInstruction) - end - [:strb, :str , :ldrb, :ldr].each do |inst| - define_instruction(inst , MemoryInstruction) - end - [:b, :bl , :swi].each do |inst| - define_instruction(inst , CallInstruction) - end - + # Assembling to string will return a binary string of the whole program, ie all blocks and the + # strings they use + # As a memory reference this would be callable, but more likely you will hand it over to + # an ObjectWriter as the .text section and then link it. And then execute it :-) def assemble_to_string #put the strings at the end of the assembled code. # adding them will fix their position and make them assemble after @string_table.values.each do |data| - add_code data + add_block data end io = StringIO.new assemble(io) io.string end + # Add a string to the string table. Strings are global and constant. So only one copy of each + # string exists + # Internally StringLiterals are created and stored and during assembly written after the blocks def add_string str code = @string_table[str] return code if code data = Asm::StringLiteral.new(str) @string_table[str] = data end - - def strings - @string_table.values - end - - def add_code(kode) - kode.at(@position) - length = kode.length - @position += length - @codes << kode - end - - def label name - label = Label.new(name , self) - @labels << label - label + + # Length of all blocks. Does not take strings into account as they are added after all blocks. + # This is used to determine where a block when it is added after creation (see add_block) + def length + @blocks.inject(0) {| sum , item | sum + item.length} end + # call block to create a new (code) block. The simple way is to do this with a block and + # use the yielded block to add code, ie something like: + # prog.block do |loop| + # loop.instance_eval do #this part you can acheive with calls too + # mov r0 , 10 + # subs r0 , 1 + # bne block + # end + # end + # Easy, because it's a backward jump. For forward branches that doesn't work and so you have to + # create the block without a ruby block. You can then jumpt to it immediately + # But the block is not part of the program (since we don't know where) and so you have to add it later + def block + block = Block.new(self) + yield block.set! if block_given? #yield the block (which set returns) + block + end + + # This is how you add a forward declared block. This is called automatically when you + # call block with ruby block, but has to be done manually if not + def add_block block + block.at self.length + @blocks << block + end + + private + def assemble(io) - @codes.each do |obj| + @blocks.each do |obj| obj.assemble io end end - end end diff --git a/lib/asm/stack_instruction.rb b/lib/asm/stack_instruction.rb index daf938d8..77376f45 100644 --- a/lib/asm/stack_instruction.rb +++ b/lib/asm/stack_instruction.rb @@ -1,4 +1,4 @@ -require "asm/instruction" +require_relative "instruction" module Asm # ADDRESSING MODE 4 diff --git a/lib/crystal.rb b/lib/crystal.rb index 3cd55220..9cc2169f 100644 --- a/lib/crystal.rb +++ b/lib/crystal.rb @@ -2,8 +2,7 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', ".." , "parslet",'lib')) require 'parslet' -require "asm/stack_instruction" -require "asm/arm_assembler" +require "asm/program" require "elf/object_writer" require 'vm/parser' require 'vm/nodes' diff --git a/test/test_crystal.rb b/test/test_crystal.rb index 5204c0e6..7ad6683e 100644 --- a/test/test_crystal.rb +++ b/test/test_crystal.rb @@ -6,17 +6,20 @@ require_relative 'helper' # adc add and bic eor orr rsb rsc sbc sub mov mvn cmn cmp teq tst b bl bx swi strb class TestArmAsm < MiniTest::Test - # need a code generator, for arm + # need Program and a block (see those classes) def setup - @assembler = Asm::ArmAssembler.new + @program = Asm::Program.new + #no ruby block given, ie need to add this block later by hand + @block = @program.block end # code is what the generator spits out, at least one instruction worth (.first) # the op code is wat was witten as assembler in the first place and the binary result # is reversed and in 4 bytes as ruby can only do 31 bits and so we can't test with just one int (?) - def assert_code code , op , should + def assert_code code , op , should + @program.add_block @block #assume not added before assert_equal op , code.opcode - binary = @assembler.assemble_to_string + binary = @program.assemble_to_string assert_equal 4 , binary.length index = 0 binary.each_byte do |byte | @@ -25,111 +28,111 @@ class TestArmAsm < MiniTest::Test end end def test_adc - code = @assembler.instance_eval { adc r1, r3, r5}.first + code = @block.instance_eval { adc r1, r3, r5}.first assert_code code , :adc , [0x05,0x10,0xa3,0xe0] #e0 a3 10 05 end def test_add - code = @assembler.instance_eval { add r1 , r1, r3}.first + code = @block.instance_eval { add r1 , r1, r3}.first assert_code code , :add , [0x03,0x10,0x81,0xe0] #e0 81 10 03 end def test_and # inst eval doesn't really work with and - code = @assembler.and( @assembler.r1 , @assembler.r2 , @assembler.r3).first + code = @block.and( @block.r1 , @block.r2 , @block.r3).first assert_code code , :and , [0x03,0x10,0x02,0xe0] #e0 01 10 03 end def test_b # the address is what an assembler calculates (a signed number for the amount of instructions), # ie the relative (to pc) address -8 (pipeline) /4 so save space # so the cpu adds the value*4 and starts loading that (load, decode, execute) - code = @assembler.instance_eval { b -1 }.first #this jumps to the next instruction + code = @block.instance_eval { b -1 }.first #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 = @assembler.instance_eval { bl -1 }.first #this jumps to the next instruction + code = @block.instance_eval { bl -1 }.first #this jumps to the next instruction assert_code code , :bl , [0xff,0xff,0xff,0xeb] #ea ff ff fe end def test_bic - code = @assembler.instance_eval { bic r2 , r2 , r3 }.first + code = @block.instance_eval { bic r2 , r2 , r3 }.first assert_code code , :bic , [0x03,0x20,0xc2,0xe1] #e3 c2 20 44 end def test_cmn - code = @assembler.instance_eval { cmn r1 , r2 }.first + code = @block.instance_eval { cmn r1 , r2 }.first assert_code code , :cmn , [0x02,0x00,0x71,0xe1] #e1 71 00 02 end def test_cmp - code = @assembler.instance_eval { cmp r1 , r2 }.first + code = @block.instance_eval { cmp r1 , r2 }.first assert_code code , :cmp , [0x02,0x00,0x51,0xe1] #e1 51 00 02 end def test_eor - code = @assembler.instance_eval { eor r2 , r2 , r3 }.first + code = @block.instance_eval { eor r2 , r2 , r3 }.first assert_code code , :eor , [0x03,0x20,0x22,0xe0] #e0 22 20 03 end def test_ldr - code = @assembler.instance_eval { ldr r0, r0 }.first + code = @block.instance_eval { ldr r0, r0 }.first assert_code code, :ldr , [0x00,0x00,0x90,0xe5] #e5 90 00 00 end def test_ldr2 - code = @assembler.instance_eval { ldr r0, r0 + 4 }.first + code = @block.instance_eval { ldr r0, r0 + 4 }.first assert_code code, :ldr , [0x04,0x00,0x90,0xe5] #e5 90 00 04 end def test_ldrb - code = @assembler.instance_eval { ldrb r0, r0 }.first + code = @block.instance_eval { ldrb r0, r0 }.first assert_code code, :ldrb , [0x00,0x00,0xd0,0xe5] #e5 d0 00 00 end def test_orr - code = @assembler.instance_eval { orr r2 , r2 , r3 }.first + code = @block.instance_eval { orr r2 , r2 , r3 }.first assert_code code , :orr , [0x03,0x20,0x82,0xe1] #e1 82 20 03 end def test_push - code = @assembler.instance_eval { push lr }.first + code = @block.instance_eval { push lr }.first assert_code code , :push , [0x00,0x40,0x2d,0xe9] #e9 2d 40 00 end def test_pop - code = @assembler.instance_eval { pop pc }.first + code = @block.instance_eval { pop pc }.first assert_code code , :pop , [0x00,0x80,0xbd,0xe8] #e8 bd 80 00 end def test_rsb - code = @assembler.instance_eval { rsb r1 , r2 , r3 }.first + code = @block.instance_eval { rsb r1 , r2 , r3 }.first assert_code code , :rsb , [0x03,0x10,0x62,0xe0]#e0 62 10 03 end def test_rsc - code = @assembler.instance_eval { rsc r2 , r3 , r4 }.first + code = @block.instance_eval { rsc r2 , r3 , r4 }.first assert_code code , :rsc , [0x04,0x20,0xe3,0xe0]#e0 e3 20 04 end def test_sbc - code = @assembler.instance_eval { sbc r3, r4 , r5 }.first + code = @block.instance_eval { sbc r3, r4 , r5 }.first assert_code code , :sbc , [0x05,0x30,0xc4,0xe0]#e0 c4 30 05 end def test_str - code = @assembler.instance_eval { str r0, r0 }.first + code = @block.instance_eval { str r0, r0 }.first assert_code code, :str , [0x00,0x00,0x80,0xe5] #e5 81 00 00 end def test_strb - code = @assembler.instance_eval { strb r0, r0 }.first + code = @block.instance_eval { strb r0, r0 }.first assert_code code, :strb , [0x00,0x00,0xc0,0xe5] #e5 c0 00 00 end def test_sub - code = @assembler.instance_eval { sub r2, r0, 1 }.first + code = @block.instance_eval { sub r2, r0, 1 }.first assert_code code, :sub , [0x01,0x20,0x40,0xe2] #e2 40 20 01 end def test_swi - code = @assembler.instance_eval { swi 0x05 }.first + code = @block.instance_eval { swi 0x05 }.first assert_code code , :swi , [0x05,0x00,0x00,0xef]#ef 00 00 05 end def test_teq - code = @assembler.instance_eval{ teq r1 , r2}.first + code = @block.instance_eval{ teq r1 , r2}.first assert_code code , :teq , [0x02,0x00,0x31,0xe1] #e1 31 00 02 end def test_tst - code = @assembler.instance_eval{ tst r1 , r2}.first + code = @block.instance_eval{ tst r1 , r2}.first assert_code code , :tst , [0x02,0x00,0x11,0xe1] #e1 11 00 02 end def test_mov - code = @assembler.instance_eval { mov r0, 5 }.first + code = @block.instance_eval { mov r0, 5 }.first assert_code code , :mov , [0x05,0x00,0xa0,0xe3] #e3 a0 10 05 end def test_mvn - code = @assembler.instance_eval { mvn r1, 5 }.first + code = @block.instance_eval { mvn r1, 5 }.first assert_code code , :mvn , [0x05,0x10,0xe0,0xe3] #e3 e0 10 05 end end