diff --git a/lib/arm/arm_machine.rb b/lib/arm/arm_machine.rb index ed849a62..2f178c9b 100644 --- a/lib/arm/arm_machine.rb +++ b/lib/arm/arm_machine.rb @@ -29,14 +29,14 @@ module Arm end end end - + def word_load value , reg e = Vm::Block.new("load_#{value}") e.add_code( MoveInstruction.new( :left => reg , :right => value ) ) end def function_call call raise "Not FunctionCall #{call.inspect}" unless call.is_a? Vm::FunctionCall - bl( :function => call.function ) + bl( :left => call.function ) end def main_entry @@ -47,7 +47,7 @@ module Arm end def syscall num mov( :left => 7 , :right => num ) - swi( {} ) + swi( :left => 0 ) end end end \ No newline at end of file diff --git a/lib/arm/assembler.rb b/lib/arm/assembler.rb index b095f2bc..70915deb 100644 --- a/lib/arm/assembler.rb +++ b/lib/arm/assembler.rb @@ -1,10 +1,10 @@ -require 'asm/nodes' -require 'asm/block' +require 'arm/nodes' +require 'arm/block' require 'stream_reader' require 'stringio' -require "asm/string_literal" +require "arm/string_literal" -module Asm +module Arm # Assembler is the the top-level of the code hierachy, except it is not derived from code # instead a Assembler is a list of blocks (and string constants) @@ -43,7 +43,7 @@ module Asm def add_string str code = @string_table[str] return code if code - data = Asm::StringLiteral.new(str) + data = Arm::StringLiteral.new(str) @string_table[str] = data end diff --git a/lib/arm/assembly_error.rb b/lib/arm/assembly_error.rb deleted file mode 100644 index c3d7dd33..00000000 --- a/lib/arm/assembly_error.rb +++ /dev/null @@ -1,8 +0,0 @@ -module Asm - class AssemblyError < StandardError - def initialize(message) - super(message) - end - end -end - \ No newline at end of file diff --git a/lib/arm/block.rb b/lib/arm/block.rb index ab783b6a..0f7168f7 100644 --- a/lib/arm/block.rb +++ b/lib/arm/block.rb @@ -3,7 +3,7 @@ require_relative 'stack_instruction' require_relative 'logic_instruction' require_relative 'memory_instruction' -module Asm +module Arm class Code ; end @@ -48,76 +48,6 @@ module Asm end attr_reader :name - 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 << @program.add_string(arg) - elsif (arg.is_a?(Asm::Block)) - arg_nodes << arg - elsif (arg.is_a?(Symbol)) - block = @program.get_block arg - arg_nodes << block - 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 - - # codeing a block fixes it's position in the stream. - # You must call with a block, which is instance_eval'd and provides the actual code for the block - def code &block - @program.add_block self - self.instance_eval block - end - # length of the codes. In arm it would be the length * 4 # (strings are stored globally in the Assembler) def length diff --git a/lib/arm/call_instruction.rb b/lib/arm/call_instruction.rb index 4b13fd18..1a1dd946 100644 --- a/lib/arm/call_instruction.rb +++ b/lib/arm/call_instruction.rb @@ -1,4 +1,5 @@ require_relative "instruction" +require_relative "nodes" module Arm # There are only three call instructions in arm branch (b), call (bl) and syscall (swi) @@ -14,33 +15,54 @@ module Arm # Registers 0-6 hold the call values as for a normal c call class CallInstruction < Vm::CallInstruction + include Arm::Constants + + # arm intrucioons are pretty sensible, and always 4 bytes (thumb not supported) + def length + 4 + end + + def initialize(options) + super(options) + @update_status_flag = 0 + @condition_code = :al + @opcode = options[:opcode] + @args = [options[:left] , options[:right] , options[:extra]] + @operand = 0 + end def assemble(io) - case opcode + case @opcode when :b, :bl - arg = args[0] - if arg.is_a? Block - diff = arg.position - self.position - 8 - arg = NumLiteral.new(diff) + arg = @args[0] + if( arg.is_a? Fixnum ) #HACK to not have to change the code just now + arg = Arm::NumLiteral.new( arg ) end - if (arg.is_a?(Asm::NumLiteral)) + if arg.is_a? Vm::Code + diff = arg.position - self.position - 8 + arg = Arm::NumLiteral.new(diff) + end + if (arg.is_a?(Arm::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}" + raise "else not coded #{inspect}" end io.write_uint8 OPCODES[opcode] | (COND_CODES[@condition_code] << 4) when :swi - arg = args[0] - if (arg.is_a?(Asm::NumLiteral)) + arg = @args[0] + if( arg.is_a? Fixnum ) #HACK to not have to change the code just now + arg = Arm::NumLiteral.new( arg ) + end + if (arg.is_a?(Arm::NumLiteral)) packed = [arg.value].pack('L')[0,3] io << packed io.write_uint8 0b1111 | (COND_CODES[@condition_code] << 4) else - raise Asm::AssemblyError.new("invalid operand argument expected literal not #{arg}") + raise "invalid operand argument expected literal not #{arg} #{inspect}" end end end diff --git a/lib/arm/constants.rb b/lib/arm/constants.rb index 9c0bb818..e5dd9e1d 100644 --- a/lib/arm/constants.rb +++ b/lib/arm/constants.rb @@ -51,11 +51,13 @@ module Arm '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]) + code = reg_code name + raise "no such register #{name}" unless code + Arm::Register.new(name.to_sym , code ) + end + def reg_code name + REGISTERS[name.to_s] end - - def calculate_u8_with_rr(arg) parts = arg.value.to_s(2).rjust(32,'0').scan(/^(0*)(.+?)0*$/).flatten diff --git a/lib/arm/instruction.rb b/lib/arm/instruction.rb index 7b408c84..fb159e33 100644 --- a/lib/arm/instruction.rb +++ b/lib/arm/instruction.rb @@ -1,10 +1,7 @@ require "vm/instruction" require_relative "constants" - Vm::Instruction.class_eval do - include Arm::Constants - - COND_POSTFIXES = Regexp.union( Arm::Constants::COND_CODES.keys.collect{|k|k.to_s} ).source +class Saved def initializ(opcode , condition_code , update_status , args) @update_status_flag = update_status diff --git a/lib/arm/logic_instruction.rb b/lib/arm/logic_instruction.rb index 1d330715..c2b3795b 100644 --- a/lib/arm/logic_instruction.rb +++ b/lib/arm/logic_instruction.rb @@ -1,33 +1,31 @@ require_relative "instruction" module Arm - # ADDRESSING MODE 1 - # Logic ,Maths, Move and compare instructions (last three below) - - class LogicInstruction < Vm::LogicInstruction - - def initializ(opcode , condition_code , update_status , args) - super(opcode , condition_code , update_status , args) - @rn = nil - @i = 0 - @rd = args[0] - end - attr_accessor :i, :rn, :rd - + module LogicHelper + # ADDRESSING MODE 1 + # Logic ,Maths, Move and compare instructions (last three below) # Build representation for source value def build - @rn = args[1] - do_build args[2] + @rn = @args[1] + do_build @args[2] end + # arm intrucioons are pretty sensible, and always 4 bytes (thumb not supported) + def length + 4 + end + #(stays in subclases, while build is overriden to provide different arguments) def do_build(arg) - if arg.is_a?(Asm::StringLiteral) + if arg.is_a?(Arm::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 ) + arg = Arm::NumLiteral.new( arg.position - self.position - 8 ) end - if (arg.is_a?(Asm::NumLiteral)) + if( arg.is_a? Fixnum ) #HACK to not have to change the code just now + arg = Arm::NumLiteral.new( arg ) + end + if (arg.is_a?(Arm::NumLiteral)) if (arg.value.fits_u8?) # no shifting needed @operand = arg.value @@ -36,28 +34,28 @@ module Arm @operand = op_with_rot @i = 1 else - raise Asm::AssemblyError.new("cannot fit numeric literal argument in operand #{arg}") + raise "cannot fit numeric literal argument in operand #{arg}" end - elsif (arg.is_a?(Asm::Register)) + elsif (arg.is_a?(Arm::Register)) @operand = arg @i = 0 - elsif (arg.is_a?(Asm::Shift)) + elsif (arg.is_a?(Arm::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) + raise "cannot rotate by zero #{arg} #{inspect}" end arg1 = arg.value - if (arg1.is_a?(Asm::NumLiteral)) + if (arg1.is_a?(Arm::NumLiteral)) if (arg1.value >= 32) - raise Asm::AssemblyError.new('cannot shift by more than 31', arg1) + raise "cannot shift by more than 31 #{arg1} #{inspect}" end shift_imm = arg1.value - elsif (arg1.is_a?(Asm::Register)) + elsif (arg1.is_a?(Arm::Register)) shift_op val |= 0x1; shift_imm = arg1.number << 1 elsif (arg.type == 'rrx') @@ -66,43 +64,78 @@ module Arm @operand = rm_ref | (shift_op << 4) | (shift_imm << 4+3) else - raise Asm::AssemblyError.new("invalid operand argument #{arg.inspect}") + raise "invalid operand argument #{arg.inspect}" end end def assemble(io) build instuction_class = 0b00 # OPC_DATA_PROCESSING - 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 = @operand.is_a?(Symbol) ? reg_code(@operand) : @operand + val |= (reg_code(@rd) << 12) + val |= (reg_code(@rn) << 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 |= (@i << 12+4+4 +1+4) val |= (instuction_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 LogicInstruction < Vm::LogicInstruction + include Arm::Constants + include LogicHelper + + def initialize(options) + super(options) + @update_status_flag = 0 + @condition_code = :al + @opcode = options[:opcode] + @args = [options[:left] , options[:right] , options[:extra]] + @operand = 0 + + @rn = nil + @i = 0 + @rd = args[0] + end + attr_accessor :i, :rn, :rd + + end class CompareInstruction < Vm::CompareInstruction - def initialize(opcode , condition_code , update_status , args) - super(opcode , condition_code , update_status , args) + def initialize(options) + super(options) + @condition_code = :al + @opcode = options[:opcode] + @args = [options[:left] , options[:right] , options[:extra]] + @operand = 0 + @i = 0 @update_status_flag = 1 - @rn = args[0] - @rd = reg "r0" + @rn = @args[0] + @rd = :r0 end def build do_build args[1] end end class MoveInstruction < Vm::MoveInstruction - def initializ(opcode , condition_code , update_status , args) - super(opcode , condition_code , update_status , args) - @rn = reg "r0" # register zero = zero bit pattern + include Arm::Constants + include LogicHelper + + def initialize(options) + super(options) + @update_status_flag = 0 + @condition_code = :al + @opcode = options[:opcode] + @args = [options[:left] , options[:right] , options[:extra]] + @operand = 0 + + @i = 0 + @rd = @args[0] + @rn = :r0 # register zero = zero bit pattern end def build - do_build args[1] + do_build @args[1] end end end \ No newline at end of file diff --git a/lib/arm/memory_instruction.rb b/lib/arm/memory_instruction.rb index 7fc33b30..88ef6b29 100644 --- a/lib/arm/memory_instruction.rb +++ b/lib/arm/memory_instruction.rb @@ -1,10 +1,11 @@ -require "asm/nodes" +require_relative "nodes" require_relative "instruction" module Arm # ADDRESSING MODE 2 # Implemented: immediate offset with offset=0 class MemoryInstruction < Vm::MemoryInstruction + include Arm::Constants def initialize(opcode , condition_code , update_status , args) super(opcode , condition_code , update_status , args) @@ -20,6 +21,11 @@ module Arm attr_accessor :i, :pre_post_index, :add_offset, :byte_access, :w, :is_load, :rn, :rd + # arm intrucioons are pretty sensible, and always 4 bytes (thumb not supported) + def length + 4 + end + # Build representation for target address def build if( @is_load ) @@ -30,7 +36,7 @@ module Arm arg = args[0] end #str / ldr are _serious instructions. With BIG possibilities not half are implemented - if (arg.is_a?(Asm::Register)) + if (arg.is_a?(Arm::Register)) @rn = arg if(arg.offset != 0) @operand = arg.offset @@ -42,16 +48,16 @@ module Arm @add_offset = 1 end if (@operand.abs > 4095) - raise Asm::AssemblyError.new("reference offset too large/small (max 4095) #{argr.right}" ) + raise "reference offset too large/small (max 4095) #{arg} #{inspect}" end end - elsif (arg.is_a?(Asm::Label) or arg.is_a?(Asm::NumLiteral)) + elsif (arg.is_a?(Arm::Label) or arg.is_a?(Arm::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}") + raise "invalid operand argument #{arg.inspect} #{inspect}" end end diff --git a/lib/arm/nodes.rb b/lib/arm/nodes.rb index 7297019b..112a6f52 100644 --- a/lib/arm/nodes.rb +++ b/lib/arm/nodes.rb @@ -1,4 +1,4 @@ -module Asm +module Arm class Shift attr_accessor :type, :value, :argument @@ -30,7 +30,7 @@ module Asm attr_accessor :registers def initialize regs @registers = regs - regs.each{ |reg| raise "not a reg #{sym} , #{reg}" unless reg.is_a?(Asm::Register) } + regs.each{ |reg| raise "not a reg #{sym} , #{reg}" unless reg.is_a?(Arm::Register) } end end diff --git a/lib/arm/stack_instruction.rb b/lib/arm/stack_instruction.rb index be106fe1..2441a85e 100644 --- a/lib/arm/stack_instruction.rb +++ b/lib/arm/stack_instruction.rb @@ -3,9 +3,21 @@ require_relative "instruction" module Arm # ADDRESSING MODE 4 class StackInstruction < Vm::StackInstruction + include Arm::Constants + + # arm intrucioons are pretty sensible, and always 4 bytes (thumb not supported) + def length + 4 + end + + def initialize(options) + super(options) + @update_status_flag = 0 + @condition_code = :al + @opcode = options[:opcode] + @args = [options[:left] , options[:right] , options[:extra]] + @operand = 0 - def initializ(opcode , condition_code , update_status , args) - super(opcode , condition_code , update_status , args) @update_status_flag= 0 @rn = reg "r0" # register zero = zero bit pattern # downward growing, decrement before memory access @@ -51,7 +63,7 @@ module Arm @operand |= (1 << reg.bits) end else - raise Asm::AssemblyError.new("invalid operand argument #{args.inspect}") + raise "invalid operand argument #{args.inspect}" end end end diff --git a/lib/arm/string_literal.rb b/lib/arm/string_literal.rb index 36f54800..04a0d908 100644 --- a/lib/arm/string_literal.rb +++ b/lib/arm/string_literal.rb @@ -1,6 +1,6 @@ -require_relative "../vm/code" +require "vm/code" -module Asm +module Arm # The name really says it all. # The only interesting thing is storage. # Currently string are stored "inline" , ie in the code segment. diff --git a/lib/crystal.rb b/lib/crystal.rb index 354862fb..7ef6a116 100644 --- a/lib/crystal.rb +++ b/lib/crystal.rb @@ -1,6 +1,6 @@ require 'parslet' -require "asm/assembler" +require "arm/assembler" require "elf/object_writer" require 'parser/composed' require 'parser/transform' diff --git a/lib/vm/code.rb b/lib/vm/code.rb index 64c3c5b1..85271cc1 100644 --- a/lib/vm/code.rb +++ b/lib/vm/code.rb @@ -13,7 +13,7 @@ module Vm # set the position to zero, will have to reset later def initialize - @position = 0 + @address = 0 end # the position in the stream. Think of it as an address if you want. The difference is small. diff --git a/test/test_runner.rb b/test/test_runner.rb index e1fcbd7f..8aa8adf4 100644 --- a/test/test_runner.rb +++ b/test/test_runner.rb @@ -33,8 +33,8 @@ class TestRunner < MiniTest::Test assembly = program.assemble(StringIO.new) - writer.set_text assembly - writer.save("#{file}_test.o") + writer.set_text assembly.string + writer.save(file.gsub(".rb" , ".o")) puts program.to_yaml end