From f97205300fa4b04ef36ac67cec36ac3cdfb3645f Mon Sep 17 00:00:00 2001 From: Torsten Ruger Date: Wed, 23 Apr 2014 23:40:35 +0300 Subject: [PATCH] cleaned up BIG time, instruction hierachy, better names, DRYd up a lot --- lib/asm/arm_assembler.rb | 232 ++++++++++++++++--------------- lib/asm/call_instruction.rb | 43 ++++++ lib/asm/instruction.rb | 135 +++++------------- lib/asm/instruction_tools.rb | 149 ++++++++++---------- lib/asm/logic_instruction.rb | 105 ++++++++++++++ lib/asm/memory_access_builder.rb | 74 ---------- lib/asm/memory_instruction.rb | 80 +++++++++++ lib/asm/nodes.rb | 5 +- lib/asm/normal_builder.rb | 82 ----------- lib/asm/stack_instruction.rb | 110 +++++++-------- 10 files changed, 516 insertions(+), 499 deletions(-) create mode 100644 lib/asm/call_instruction.rb create mode 100644 lib/asm/logic_instruction.rb delete mode 100644 lib/asm/memory_access_builder.rb create mode 100644 lib/asm/memory_instruction.rb delete mode 100644 lib/asm/normal_builder.rb diff --git a/lib/asm/arm_assembler.rb b/lib/asm/arm_assembler.rb index 50cd2589..42c08cf4 100644 --- a/lib/asm/arm_assembler.rb +++ b/lib/asm/arm_assembler.rb @@ -1,4 +1,7 @@ -require 'asm/instruction' +require 'asm/call_instruction' +require 'asm/stack_instruction' +require 'asm/logic_instruction' +require 'asm/memory_instruction' require 'asm/nodes' require 'stream_reader' require 'stringio' @@ -6,118 +9,123 @@ require "asm/string_literal" module Asm - class ArmAssembler - - %w(r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 - r13 r14 r15 a1 a2 a3 a4 v1 v2 v3 v4 v5 v6 - rfp sl fp ip sp lr pc - ).each { |reg| - define_method(reg) { - Asm::Register.new(reg) - } - } - - def initialize - @values = [] - @position = 0 # marks not set - @labels = [] - @string_table = {} - end - attr_reader :values , :position - - def instruction(clazz,name, *args) - opcode = name.to_s - arg_nodes = [] - - args.each { |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 `%s\' for instruction' % arg.inspect - end - } - - add_value clazz.new(opcode , arg_nodes) - end - - - def self.define_instruction(inst , clazz ) - define_method(inst) do |*args| - instruction clazz , inst.to_sym, *args - end - define_method(inst+'s') do |*args| - instruction clazz , (inst+'s').to_sym, *args - end - %w(al eq ne cs mi hi cc pl ls vc lt le ge gt vs).each do |cond_suffix| - define_method(inst+cond_suffix) do |*args| - instruction clazz , (inst+cond_suffix).to_sym, *args - end - define_method(inst+'s'+cond_suffix) do |*args| - instruction clazz , (inst+'s'+cond_suffix).to_sym, *args - end - end - end - - ["push", "pop"].each do |inst| - define_instruction(inst , StackInstruction) - end - - %w(adc add and bic eor orr rsb rsc sbc sub mov mvn cmn cmp teq tst b bl bx - swi str strb ldr ldrb ).each do |inst| - define_instruction(inst , Instruction) - end - - 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_value data - end - io = StringIO.new - assemble(io) - io.string - end - - def add_string str - value = @string_table[str] - return value if value - data = Asm::StringLiteral.new(str) - @string_table[str] = data - end - - def strings - @string_table.values - end - - def add_value(val) - val.at(@position) - length = val.length - @position += length - @values << val - end - - def label name - label = Label.new(name , self) - @labels << label - label - end - - def label! name - label(name).set! - end - - def assemble(io) - @values.each do |obj| - obj.assemble io, self - end - end + class ArmAssembler + InstructionTools::REGISTERS.each do |reg , number| + define_method(reg) { Asm::Register.new(reg , number) } end + + def initialize + @values = [] + @position = 0 # marks not set + @labels = [] + @string_table = {} + end + attr_reader :values , :position + + def instruction(clazz,name, *args) + opcode = name.to_s + 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_value clazz.new(opcode , arg_nodes) + end + + + def self.define_instruction(inst , clazz ) + define_method(inst) do |*args| + instruction clazz , inst , *args + end + define_method(inst.to_s+'s') do |*args| + instruction clazz , inst.to_s+'s' , *args + end + InstructionTools::COND_CODES.keys.each do |cond_suffix| + suffix = cond_suffix.to_s + define_method(inst.to_s + suffix) do |*args| + instruction clazz , inst + suffix , *args + end + define_method(inst.to_s + 's'+ suffix) do |*args| + instruction clazz , inst.to_s + 's' + suffix, *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 + + 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_value data + end + io = StringIO.new + assemble(io) + io.string + end + + def add_string str + value = @string_table[str] + return value if value + data = Asm::StringLiteral.new(str) + @string_table[str] = data + end + + def strings + @string_table.values + end + + def add_value(val) + val.at(@position) + length = val.length + @position += length + @values << val + end + + def label name + label = Label.new(name , self) + @labels << label + label + end + + def label! name + label(name).set! + end + + def assemble(io) + @values.each do |obj| + obj.assemble io, self + end + end + + end end diff --git a/lib/asm/call_instruction.rb b/lib/asm/call_instruction.rb new file mode 100644 index 00000000..12cef595 --- /dev/null +++ b/lib/asm/call_instruction.rb @@ -0,0 +1,43 @@ +module Asm + # ADDRESSING MODE 4 , Calling + + class CallInstruction < Instruction + include Asm::InstructionTools + + def initialize(opcode , args) + super(opcode,args) + end + + def assemble(io, as) + s = @update_status_flag? 1 : 0 + case opcode + when :b, :bl + arg = args[0] + if arg.is_a? Label + diff = arg.position - self.position - 8 + arg = NumLiteral.new(diff) + end + if (arg.is_a?(Asm::NumLiteral)) + jmp_val = arg.value >> 2 + packed = [jmp_val].pack('l') + # signed 32-bit, condense to 24-bit + # TODO add check that the value fits into 24 bits + io << packed[0,3] + else + raise "else not coded #{arg.inspect}" + end + io.write_uint8 OPCODES[opcode] | (COND_CODES[@cond] << 4) + when :swi + arg = args[0] + if (arg.is_a?(Asm::NumLiteral)) + packed = [arg.value].pack('L')[0,3] + io << packed + io.write_uint8 0b1111 | (COND_CODES[@cond] << 4) + else + raise Asm::AssemblyError.new("invalid operand argument expected literal not #{arg}") + end + end + end + + end#class +end \ No newline at end of file diff --git a/lib/asm/instruction.rb b/lib/asm/instruction.rb index 4016abac..48311540 100644 --- a/lib/asm/instruction.rb +++ b/lib/asm/instruction.rb @@ -1,110 +1,47 @@ require "asm/assembly_error" require "asm/instruction_tools" -require "asm/normal_builder" -require "asm/memory_access_builder" require "asm/label" module Asm - class Instruction - include InstructionTools + class Instruction + include InstructionTools - COND_POSTFIXES = Regexp.union(%w(eq ne cs cc mi pl vs vc hi ls ge lt gt le al)).source - def initialize(opcode , args) + COND_POSTFIXES = Regexp.union( COND_CODES.keys.collect{|k|k.to_s} ).source - opcode = opcode.downcase - @cond = 0b1011 - if (opcode =~ /(#{COND_POSTFIXES})$/) - @cond = $1.to_sym - opcode = opcode[0..-3] - end unless opcode == 'teq' - if (opcode =~ /s$/) - @s = true - opcode = opcode[0..-2] - else - @s = false - end - @opcode = opcode.downcase.to_sym - @args = args - end - attr_reader :opcode, :args , :position - - def affect_status - @s - end - - def at position - @position = position - end - - def length - 4 - end - - def assemble(io, as) - s = @s ? 1 : 0 - case opcode - when :adc, :add, :and, :bic, :eor, :orr, :rsb, :rsc, :sbc, :sub - builder = NormalBuilder.new(OPC_DATA_PROCESSING, OPCODES[opcode], s) - builder.cond = COND_CODES[@cond] - builder.rd = reg_ref(args[0]) - builder.rn = reg_ref(args[1]) - builder.build_operand args[2] , self.position - builder.assemble io, as - when :cmn, :cmp, :teq, :tst - builder = NormalBuilder.new(OPC_DATA_PROCESSING, OPCODES[opcode], 1) - builder.cond = COND_CODES[@cond] - builder.rn = reg_ref(args[0]) - builder.rd = 0 - builder.build_operand args[1] - builder.assemble io, as - when :mov, :mvn - builder = NormalBuilder.new(OPC_DATA_PROCESSING, OPCODES[opcode], s) - builder.cond = COND_CODES[@cond] - builder.rn = 0 - builder.rd = reg_ref(args[0]) - builder.build_operand args[1] - builder.assemble io, as - when :strb, :str - builder = MemoryAccessBuilder.new(OPC_MEMORY_ACCESS, (opcode == :strb ? 1 : 0), 0) - builder.cond = COND_CODES[@cond] - builder.rd = reg_ref(args[1]) - builder.build_operand args[0] - builder.assemble io, as, self - when :ldrb, :ldr - builder = MemoryAccessBuilder.new(OPC_MEMORY_ACCESS, (opcode == :ldrb ? 1 : 0), 1) - builder.cond = COND_CODES[@cond] - builder.rd = reg_ref(args[0]) - builder.build_operand args[1] - builder.assemble io, as, self - when :b, :bl - arg = args[0] - if arg.is_a? Label - diff = arg.position - self.position - 8 - arg = NumLiteral.new(diff) - end - if (arg.is_a?(Asm::NumLiteral)) - jmp_val = arg.value >> 2 - packed = [jmp_val].pack('l') - # signed 32-bit, condense to 24-bit - # TODO add check that the value fits into 24 bits - io << packed[0,3] - else - raise "else not coded #{arg.inspect}" - end - io.write_uint8 OPCODES[opcode] | (COND_CODES[@cond] << 4) - when :swi - arg = args[0] - if (arg.is_a?(Asm::NumLiteral)) - packed = [arg.value].pack('L')[0,3] - io << packed - io.write_uint8 0b1111 | (COND_CODES[@cond] << 4) - else - raise Asm::AssemblyError.new("invalid operand argument expected literal not #{arg}") - end - else - raise Asm::AssemblyError.new("unknown instruction #{opcode} #{self}") - end + def initialize(opcode , args) + opcode = opcode.to_s.downcase + @cond = :al + if (opcode =~ /(#{COND_POSTFIXES})$/) + @cond = $1.to_sym + opcode = opcode[0..-3] + end unless opcode == 'teq' + if (opcode =~ /s$/) + @update_status_flag= 1 + opcode = opcode[0..-2] + else + @update_status_flag= 0 end + @opcode = opcode.downcase.to_sym + @args = args + @operand = 0 end + attr_reader :opcode, :args , :position , :cond , :operand , :update_status_flag + + def affect_status + @s + end + + def at position + @position = position + end + + def length + 4 + end + + def assemble(io, as) + raise "Abstract class, should not be called/instantiated #{self.inspect}" + end + end end \ No newline at end of file diff --git a/lib/asm/instruction_tools.rb b/lib/asm/instruction_tools.rb index 8a6b7743..05c3bda4 100644 --- a/lib/asm/instruction_tools.rb +++ b/lib/asm/instruction_tools.rb @@ -1,81 +1,86 @@ module Asm - module InstructionTools - OPCODES = { - :adc => 0b0101, :add => 0b0100, - :and => 0b0000, :bic => 0b1110, - :eor => 0b0001, :orr => 0b1100, - :rsb => 0b0011, :rsc => 0b0111, - :sbc => 0b0110, :sub => 0b0010, + module InstructionTools + OPCODES = { + :adc => 0b0101, :add => 0b0100, + :and => 0b0000, :bic => 0b1110, + :eor => 0b0001, :orr => 0b1100, + :rsb => 0b0011, :rsc => 0b0111, + :sbc => 0b0110, :sub => 0b0010, - # for these Rn is sbz (should be zero) - :mov => 0b1101, - :mvn => 0b1111, - # for these Rd is sbz and S=1 - :cmn => 0b1011, - :cmp => 0b1010, - :teq => 0b1001, - :tst => 0b1000, + # for these Rn is sbz (should be zero) + :mov => 0b1101, + :mvn => 0b1111, + # for these Rd is sbz and S=1 + :cmn => 0b1011, + :cmp => 0b1010, + :teq => 0b1001, + :tst => 0b1000, - :b => 0b1010, - :bl => 0b1011, - :bx => 0b00010010 - } - COND_CODES = { - :al => 0b1110, :eq => 0b0000, - :ne => 0b0001, :cs => 0b0010, - :mi => 0b0100, :hi => 0b1000, - :cc => 0b0011, :pl => 0b0101, - :ls => 0b1001, :vc => 0b0111, - :lt => 0b1011, :le => 0b1101, - :ge => 0b1010, :gt => 0b1100, - :vs => 0b0110 - } - OPC_DATA_PROCESSING = 0b00 - OPC_MEMORY_ACCESS = 0b01 - OPC_STACK = 0b10 - - def reg_ref(arg) - if (not arg.is_a?(Asm::Register)) - raise Asm::AssemblyError.new("argument must be a register not #{arg}") - end + :b => 0b1010, + :bl => 0b1011, + :bx => 0b00010010 + } + #return the bit patter that the cpu uses for the current instruction @opcode + def op_bit_code + OPCODES[@opcode] or throw "no code found for #{@opcode.inspect}" + end - ref = - {'r0' => 0, 'r1' => 1, 'r2' => 2, 'r3' => 3, 'r4' => 4, 'r5' => 5, - 'r6' => 6, 'r7' => 7, 'r8' => 8, 'r9' => 9, 'r10' => 10, 'r11' => 11, - 'r12' => 12, 'r13' => 13, 'r14' => 14, 'r15' => 15, 'a1' => 0, 'a2' => 1, - 'a3' => 2, 'a4' => 3, 'v1' => 4, 'v2' => 5, 'v3' => 6, 'v4' => 7, 'v5' => 8, - 'v6' => 9, 'rfp' => 9, 'sl' => 10, 'fp' => 11, 'ip' => 12, 'sp' => 13, - 'lr' => 14, 'pc' => 15}[arg.name.downcase] - - if (not ref) - raise Asm::AssemblyError.new("unknown register #{arg}") - end - - ref - end + #codition codes can be applied to many instructions and thus save branches + # :al => always , :eq => equal and so on + # eq mov if equal :moveq r1 r2 (also exists as function) will only execute if the last operation was 0 + COND_CODES = { + :al => 0b1110, :eq => 0b0000, + :ne => 0b0001, :cs => 0b0010, + :mi => 0b0100, :hi => 0b1000, + :cc => 0b0011, :pl => 0b0101, + :ls => 0b1001, :vc => 0b0111, + :lt => 0b1011, :le => 0b1101, + :ge => 0b1010, :gt => 0b1100, + :vs => 0b0110 + } + #return the bit pattern for the @cond variable, which signals the conditional code + def cond_bit_code + COND_CODES[@cond] or throw "no code found for #{@cond}" end - def calculate_u8_with_rr(arg) - parts = arg.value.to_s(2).rjust(32,'0').scan(/^(0*)(.+?)0*$/).flatten - pre_zeros = parts[0].length - imm_len = parts[1].length - if ((pre_zeros+imm_len) % 2 == 1) - u8_imm = (parts[1]+'0').to_i(2) - imm_len += 1 - else - u8_imm = parts[1].to_i(2) - end - if (u8_imm.fits_u8?) - # can do! - rot_imm = (pre_zeros+imm_len) / 2 - if (rot_imm > 15) - return nil - end - return u8_imm | (rot_imm << 8) - else - return nil - end - end + OPC_DATA_PROCESSING = 0b00 + OPC_MEMORY_ACCESS = 0b01 + OPC_STACK = 0b10 + REGISTERS = { 'r0' => 0, 'r1' => 1, 'r2' => 2, 'r3' => 3, 'r4' => 4, 'r5' => 5, + 'r6' => 6, 'r7' => 7, 'r8' => 8, 'r9' => 9, 'r10' => 10, 'r11' => 11, + 'r12' => 12, 'r13' => 13, 'r14' => 14, 'r15' => 15, 'a1' => 0, 'a2' => 1, + 'a3' => 2, 'a4' => 3, 'v1' => 4, 'v2' => 5, 'v3' => 6, 'v4' => 7, 'v5' => 8, + 'v6' => 9, 'rfp' => 9, 'sl' => 10, 'fp' => 11, 'ip' => 12, 'sp' => 13, + 'lr' => 14, 'pc' => 15 } + def reg name + raise "no such register #{reg}" unless REGISTERS[name] + Asm::Register.new(name , REGISTERS[name]) + end + + + + def calculate_u8_with_rr(arg) + parts = arg.value.to_s(2).rjust(32,'0').scan(/^(0*)(.+?)0*$/).flatten + pre_zeros = parts[0].length + imm_len = parts[1].length + if ((pre_zeros+imm_len) % 2 == 1) + u8_imm = (parts[1]+'0').to_i(2) + imm_len += 1 + else + u8_imm = parts[1].to_i(2) + end + if (u8_imm.fits_u8?) + # can do! + rot_imm = (pre_zeros+imm_len) / 2 + if (rot_imm > 15) + return nil + end + return u8_imm | (rot_imm << 8) + else + return nil + end + end + end end \ No newline at end of file diff --git a/lib/asm/logic_instruction.rb b/lib/asm/logic_instruction.rb new file mode 100644 index 00000000..8e289db4 --- /dev/null +++ b/lib/asm/logic_instruction.rb @@ -0,0 +1,105 @@ +module Asm + # ADDRESSING MODE 1 + # Logic ,Maths, Move and compare instructions (last three below) + + class LogicInstruction < Instruction + include Asm::InstructionTools + + def initialize( opcode , args) + super(opcode , args) + @inst_class = OPC_DATA_PROCESSING + @i = 0 + @rd = args[0] + end + attr_accessor :inst_class, :i, :rn, :rd + + # Build representation for source value + def build + @rn = args[1] + do_build args[2] + end + + #(stays in subclases, while build is overriden to provide different arguments) + def do_build(arg) + if arg.is_a?(Asm::StringLiteral) + # do pc relative addressing with the difference to the instuction + # 8 is for the funny pipeline adjustment (ie oc pointing to fetch and not execute) + arg = Asm::NumLiteral.new( arg.position - self.position - 8 ) + end + if (arg.is_a?(Asm::NumLiteral)) + if (arg.value.fits_u8?) + # no shifting needed + @operand = arg.value + @i = 1 + elsif (op_with_rot = calculate_u8_with_rr(arg)) + @operand = op_with_rot + @i = 1 + else + raise Asm::AssemblyError.new("cannot fit numeric literal argument in operand #{arg}") + end + elsif (arg.is_a?(Asm::Register)) + @operand = arg + @i = 0 + elsif (arg.is_a?(Asm::Shift)) + rm_ref = arg.argument + @i = 0 + shift_op = {'lsl' => 0b000, 'lsr' => 0b010, 'asr' => 0b100, + 'ror' => 0b110, 'rrx' => 0b110}[arg.type] + if (arg.type == 'ror' and arg.value.nil?) + # ror #0 == rrx + raise Asm::AssemblyError.new('cannot rotate by zero', arg) + end + + arg1 = arg.value + if (arg1.is_a?(Asm::NumLiteral)) + if (arg1.value >= 32) + raise Asm::AssemblyError.new('cannot shift by more than 31', arg1) + end + shift_imm = arg1.value + elsif (arg1.is_a?(Asm::Register)) + shift_op val |= 0x1; + shift_imm = arg1.number << 1 + elsif (arg.type == 'rrx') + shift_imm = 0 + end + + @operand = rm_ref | (shift_op << 4) | (shift_imm << 4+3) + else + raise Asm::AssemblyError.new("invalid operand argument #{arg.inspect}") + end + end + + def assemble(io, as) + build + val = operand.is_a?(Register) ? operand.bits : operand + val |= (rd.bits << 12) + val |= (rn.bits << 12+4) + val |= (update_status_flag << 12+4+4)#20 + val |= (op_bit_code << 12+4+4 +1) + val |= (i << 12+4+4 +1+4) + val |= (inst_class << 12+4+4 +1+4+1) + val |= (cond_bit_code << 12+4+4 +1+4+1+2) + io.write_uint32 val + end + end + class CompareInstruction < LogicInstruction + def initialize( opcode , args) + super(opcode , args) + @update_status_flag = 1 + @rn = args[0] + @rd = reg "r0" + end + def build + do_build args[1] + end + end + class MoveInstruction < LogicInstruction + def initialize( opcode , args) + super(opcode , args) + @rn = reg "r0" # register zero = zero bit pattern + end + def build + do_build args[1] + end + end +end \ No newline at end of file diff --git a/lib/asm/memory_access_builder.rb b/lib/asm/memory_access_builder.rb deleted file mode 100644 index 29c18e71..00000000 --- a/lib/asm/memory_access_builder.rb +++ /dev/null @@ -1,74 +0,0 @@ -require "asm/nodes" - -module Asm - # ADDRESSING MODE 2 - # Implemented: immediate offset with offset=0 - class MemoryAccessBuilder - include Asm::InstructionTools - - def initialize(inst_class, byte_access, load_store) - @cond = 0b1110 - @inst_class = 0 - @i = 0 #I flag (third bit) - @pre_post_index = 0 #P flag - @add_offset = 0 #U flag - @byte_access = 0 #B flag - @w = 0 #W flag - @load_store = 0 #L flag - @rn = 0 - @rd = 0 - @operand = 0 - @inst_class = inst_class - @byte_access = byte_access - @load_store = load_store - end - attr_accessor :cond, :inst_class, :i, :pre_post_index, :add_offset, - :byte_access, :w, :load_store, :rn, :rd, :operand - - # Build representation for target address - def build_operand(arg) - #str / ldr are _serious instructions. With BIG possibilities not half are implemented - @i = 0 - @pre_post_index = 0 - @w = 0 - @operand = 0 - if (arg.is_a?(Asm::Register)) - @rn = reg_ref(arg) - if(arg.offset != 0) - @operand = arg.offset - if (@operand < 0) - @add_offset = 0 - #TODO test/check/understand - @operand *= -1 - else - @add_offset = 1 - end - if (@operand.abs > 4095) - raise Asm::AssemblyError.new("reference offset too large/small (max 4095) #{argr.right}" ) - end - end - elsif (arg.is_a?(Asm::Label) or arg.is_a?(Asm::NumLiteral)) - @pre_post_index = 1 - @rn = 15 # pc - @use_addrtable_reloc = true - @addrtable_reloc_target = arg - else - raise Asm::AssemblyError.new("invalid operand argument #{arg.inspect}") - end - end - - def assemble(io, as, inst) - #not sure about these 2 constants. They produce the correct output for str r0 , r1 - # but i can't help thinking that that is because they are not used in that instruction and - # so it doesn't matter. Will see - @add_offset = 1 - @pre_post_index = 1 - val = operand | (rd << 12 ) | (rn << 12 + 4) | - (load_store << 12+4+4) | (w << 12+4+4+1) | - (byte_access << 12+4+4+1+1) | (add_offset << 12+4+4+1+1+1) | - (pre_post_index << 12+4+4+1+1+1+1) | (i << 12+4+4+1+1+1+1+1) | - (inst_class << 12+4+4+1+1+1+1+1+1) | (cond << 12+4+4+1+1+1+1+1+1+2) - io.write_uint32 val - end - end -end \ No newline at end of file diff --git a/lib/asm/memory_instruction.rb b/lib/asm/memory_instruction.rb new file mode 100644 index 00000000..e4f03acd --- /dev/null +++ b/lib/asm/memory_instruction.rb @@ -0,0 +1,80 @@ +require "asm/nodes" + +module Asm + # ADDRESSING MODE 2 + # Implemented: immediate offset with offset=0 + class MemoryInstruction < Instruction + include Asm::InstructionTools + + def initialize(opcode , args) + super( opcode , args ) + @inst_class = OPC_MEMORY_ACCESS + @i = 0 #I flag (third bit) + @pre_post_index = 0 #P flag + @add_offset = 0 #U flag + @byte_access = opcode.to_s[-1] == "b" ? 1 : 0 #B (byte) flag + @w = 0 #W flag + @is_load = opcode.to_s[0] == "l" ? 1 : 0 #L (load) flag + @rn = reg "r0" # register zero = zero bit pattern + @rd = reg "r0" # register zero = zero bit pattern + end + attr_accessor :inst_class, :i, :pre_post_index, :add_offset, + :byte_access, :w, :is_load, :rn, :rd + + # Build representation for target address + def build + if( @is_load ) + @rd = args[0] + arg = args[1] + else #store + @rd = args[1] + arg = args[0] + end + #str / ldr are _serious instructions. With BIG possibilities not half are implemented + if (arg.is_a?(Asm::Register)) + @rn = arg + if(arg.offset != 0) + @operand = arg.offset + if (@operand < 0) + @add_offset = 0 + #TODO test/check/understand + @operand *= -1 + else + @add_offset = 1 + end + if (@operand.abs > 4095) + raise Asm::AssemblyError.new("reference offset too large/small (max 4095) #{argr.right}" ) + end + end + elsif (arg.is_a?(Asm::Label) or arg.is_a?(Asm::NumLiteral)) + @pre_post_index = 1 + @rn = pc + @use_addrtable_reloc = true + @addrtable_reloc_target = arg + else + raise Asm::AssemblyError.new("invalid operand argument #{arg.inspect}") + end + end + + def assemble(io, as) + build + #not sure about these 2 constants. They produce the correct output for str r0 , r1 + # but i can't help thinking that that is because they are not used in that instruction and + # so it doesn't matter. Will see + @add_offset = 1 + @pre_post_index = 1 + val = operand + val |= (rd.bits << 12 ) + val |= (rn.bits << 12+4) #16 + val |= (is_load << 12+4 +4) + val |= (w << 12+4 +4+1) + val |= (byte_access << 12+4 +4+1+1) + val |= (add_offset << 12+4 +4+1+1+1) + val |= (pre_post_index << 12+4 +4+1+1+1+1)#24 + val |= (i << 12+4 +4+1+1+1+1 +1) + val |= (inst_class << 12+4 +4+1+1+1+1 +1+1) + val |= (cond_bit_code << 12+4 +4+1+1+1+1 +1+1+2) + io.write_uint32 val + end + end +end \ No newline at end of file diff --git a/lib/asm/nodes.rb b/lib/asm/nodes.rb index d2f64185..7297019b 100644 --- a/lib/asm/nodes.rb +++ b/lib/asm/nodes.rb @@ -9,9 +9,10 @@ module Asm # Arm has addressing modes abound, and so can add to a register before actually using it # If can actually shift or indeed shift what it adds, but not implemented class Register - attr_accessor :name , :offset - def initialize name + attr_accessor :name , :offset , :bits + def initialize name , bits @name = name + @bits = bits @offset = 0 end diff --git a/lib/asm/normal_builder.rb b/lib/asm/normal_builder.rb deleted file mode 100644 index 7945717b..00000000 --- a/lib/asm/normal_builder.rb +++ /dev/null @@ -1,82 +0,0 @@ -module Asm - # ADDRESSING MODE 1 - # Complete! - class NormalBuilder - include Asm::InstructionTools - - def initialize(inst_class, opcode, s) - @cond = 0b1110 - @inst_class = 0 - @i = 0 - @s = 0 - @rn = 0 - @rd = 0 - @operand = 0 - @inst_class = inst_class - @opcode = opcode - @s = s - end - attr_accessor :cond, :inst_class, :i, :opcode, :s, - :rn, :rd, :operand - - # Build representation for source value - def build_operand(arg , position = 0) - #position only needed for calculating relative addresses to data objects - #there is a design stink here which makes my head ache. But shanti shanti - if arg.is_a?(Asm::StringLiteral) - # do pc relative addressing with the difference to the instuction - # 8 is for the funny pipeline adjustment (ie oc pointing to fetch and not execute) - arg = Asm::NumLiteral.new( arg.position - position - 8 ) - end - if (arg.is_a?(Asm::NumLiteral)) - if (arg.value.fits_u8?) - # no shifting needed - @operand = arg.value - @i = 1 - elsif (op_with_rot = calculate_u8_with_rr(arg)) - @operand = op_with_rot - @i = 1 - else - raise Asm::AssemblyError.new("cannot fit numeric literal argument in operand #{arg}") - end - elsif (arg.is_a?(Asm::Register)) - @operand = reg_ref(arg) - @i = 0 - elsif (arg.is_a?(Asm::Shift)) - rm_ref = reg_ref(arg.argument) - @i = 0 - shift_op = {'lsl' => 0b000, 'lsr' => 0b010, 'asr' => 0b100, - 'ror' => 0b110, 'rrx' => 0b110}[arg.type] - if (arg.type == 'ror' and arg.value.nil?) - # ror #0 == rrx - raise Asm::AssemblyError.new('cannot rotate by zero', arg) - end - - arg1 = arg.value - if (arg1.is_a?(Asm::NumLiteral)) - if (arg1.value >= 32) - raise Asm::AssemblyError.new('cannot shift by more than 31', arg1) - end - shift_imm = arg1.value - elsif (arg1.is_a?(Asm::Register)) - shift_op |= 0x1; - shift_imm = reg_ref(arg1) << 1 - elsif (arg.type == 'rrx') - shift_imm = 0 - end - - @operand = rm_ref | (shift_op << 4) | (shift_imm << 4+3) - else - raise Asm::AssemblyError.new("invalid operand argument #{arg.inspect}") - end - end - - def assemble(io, as) - val = operand | (rd << 12) | (rn << 12+4) | - (s << 12+4+4) | (opcode << 12+4+4+1) | - (i << 12+4+4+1+4) | (inst_class << 12+4+4+1+4+1) | - (cond << 12+4+4+1+4+1+2) - io.write_uint32 val - end - end -end \ No newline at end of file diff --git a/lib/asm/stack_instruction.rb b/lib/asm/stack_instruction.rb index 9533a99c..706b3171 100644 --- a/lib/asm/stack_instruction.rb +++ b/lib/asm/stack_instruction.rb @@ -1,65 +1,59 @@ require "asm/instruction" module Asm - # ADDRESSING MODE 4 - class StackInstruction < Instruction - include Asm::InstructionTools + # ADDRESSING MODE 4 + class StackInstruction < Instruction + include Asm::InstructionTools - def initialize(opcode , args) - super(opcode,args) - @operand = 0 - @cond = 0b1110 - @inst_class = Asm::Instruction::OPC_STACK - @s = 0 - @rn = 0 - # downward growing, decrement before memory access - # official ARM style stack as used by gas - @write_base = 1 - if (opcode == :push) - @pre_post_index = 1 - @up_down = 0 - @store_load = 0 - else #pop - @pre_post_index = 0 - @up_down = 1 - @store_load = 1 - end + def initialize(opcode , args) + super(opcode,args) + @inst_class = Asm::Instruction::OPC_STACK + @update_status_flag= 0 + @rn = reg "r0" # register zero = zero bit pattern + # downward growing, decrement before memory access + # official ARM style stack as used by gas + @write_base = 1 + if (opcode == :push) + @pre_post_index = 1 + @up_down = 0 + @is_pop = 0 + else #pop + @pre_post_index = 0 + @up_down = 1 + @is_pop = 1 end - attr_accessor :cond, :inst_class, :pre_post_index, :up_down, - :s, :write_base, :store_load, :rn, :operand - - def assemble(io, as) - cond = @cond.is_a?(Symbol) ? COND_CODES[@cond] : @cond - rn = 13 # sp - build_operand args - - #assemble of old - val = @operand - val |= (rn << 16) - val |= (store_load << 16+4) #20 - val |= (write_base << 16+4+ 1) - val |= (s << 16+4+ 1+1) - val |= (up_down << 16+4+ 1+1+1) - val |= (pre_post_index << 16+4+ 1+1+1+1)#24 - val |= (inst_class << 16+4+ 1+1+1+1 +2) - val |= (cond << 16+4+ 1+1+1+1 +2+2) - puts "#{self.inspect}" - io.write_uint32 val - end - - private - # Build representation for source value - def build_operand(arg) - if (arg.is_a?(Array)) - @operand = 0 - arg.each do |reg | - reg = reg_ref(reg) - @operand |= (1 << reg) - end - else - raise Asm::AssemblyError.new("invalid operand argument #{arg.inspect}") - end - end - end + attr_accessor :cond, :inst_class, :pre_post_index, :up_down, + :update_status_flag, :write_base, :is_pop, :rn, :operand + + def assemble(io, as) + build + cond = @cond.is_a?(Symbol) ? COND_CODES[@cond] : @cond + rn = reg "sp" # sp register + #assemble of old + val = operand + val |= (rn.bits << 16) + val |= (is_pop << 16+4) #20 + val |= (write_base << 16+4+ 1) + val |= (update_status_flag << 16+4+ 1+1) + val |= (up_down << 16+4+ 1+1+1) + val |= (pre_post_index << 16+4+ 1+1+1+1)#24 + val |= (inst_class << 16+4+ 1+1+1+1 +2) + val |= (cond << 16+4+ 1+1+1+1 +2+2) + io.write_uint32 val + end + + private + # Build representation for source value + def build + if (args.is_a?(Array)) + @operand = 0 + args.each do |reg | + @operand |= (1 << reg.bits) + end + else + raise Asm::AssemblyError.new("invalid operand argument #{args.inspect}") + end + end + end end