diff --git a/lib/arm/arm_machine.rb b/lib/arm/arm_machine.rb index 18f6548f..b6a4e305 100644 --- a/lib/arm/arm_machine.rb +++ b/lib/arm/arm_machine.rb @@ -1,8 +1,35 @@ require "vm/machine" +require_relative "instruction" +require_relative "stack_instruction" +require_relative "logic_instruction" +require_relative "memory_instruction" +require_relative "call_instruction" module Arm class ArmMachine < Vm::Machine + # 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 options 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 |options| + instruction clazz , inst , suffix , 0 , *args + end + define_method("#{inst}s#{suffix}") do |options| + instruction clazz , inst , suffix , 1 , *args + end + end + end + def word_load value "word" end @@ -12,9 +39,17 @@ module Arm def main_entry e = Vm::Block.new("main_entry") + e.add_code( mov( :left => :fp , :right => 0 )) end def main_exit e = Vm::Block.new("main_exit") + e.join( syscall(0) ) + end + def syscall num + e = Vm::Block.new("syscall") + e.add_code( MoveInstruction.new( 7 , num ) ) + e.add_code( CallInstruction.new( :swi ) ) + e end end end \ No newline at end of file diff --git a/lib/arm/call_instruction.rb b/lib/arm/call_instruction.rb index 257fcd4a..4b13fd18 100644 --- a/lib/arm/call_instruction.rb +++ b/lib/arm/call_instruction.rb @@ -1,6 +1,6 @@ require_relative "instruction" -module Asm +module Arm # There are only three call instructions in arm branch (b), call (bl) and syscall (swi) # A branch could be called a jump as it has no notion of returning @@ -13,7 +13,7 @@ module Asm # in Arm the register layout is different and so we have to place the syscall code into register 7 # Registers 0-6 hold the call values as for a normal c call - class CallInstruction < Instruction + class CallInstruction < Vm::CallInstruction def assemble(io) case opcode diff --git a/lib/arm/constants.rb b/lib/arm/constants.rb new file mode 100644 index 00000000..9c0bb818 --- /dev/null +++ b/lib/arm/constants.rb @@ -0,0 +1,82 @@ +module Arm + + module Constants + 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, + + :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 + + #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 @condition_code variable, which signals the conditional code + def cond_bit_code + COND_CODES[@condition_code] or throw "no code found for #{@condition_code}" + end + + 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/arm/instruction.rb b/lib/arm/instruction.rb index ad4535c0..7b408c84 100644 --- a/lib/arm/instruction.rb +++ b/lib/arm/instruction.rb @@ -1,24 +1,12 @@ -require_relative "assembly_error" -require_relative "arm_machine" +require "vm/instruction" +require_relative "constants" -module Asm + Vm::Instruction.class_eval do + include Arm::Constants - 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) - # - # Opcode is a (<= three) letter accronym (same as in assembly code). Though in arm, suffixes can - # make the opcode longer, we chop those off in the constructor - # Argurments are registers or labels or string/num Literals - - class Instruction < Code - include ArmMachine + COND_POSTFIXES = Regexp.union( Arm::Constants::COND_CODES.keys.collect{|k|k.to_s} ).source - COND_POSTFIXES = Regexp.union( COND_CODES.keys.collect{|k|k.to_s} ).source - - def initialize(opcode , condition_code , update_status , args) + def initializ(opcode , condition_code , update_status , args) @update_status_flag = update_status @condition_code = condition_code.to_sym @opcode = opcode @@ -43,4 +31,3 @@ module Asm 4 end end -end \ No newline at end of file diff --git a/lib/arm/logic_instruction.rb b/lib/arm/logic_instruction.rb index 035e1725..1d330715 100644 --- a/lib/arm/logic_instruction.rb +++ b/lib/arm/logic_instruction.rb @@ -1,12 +1,12 @@ require_relative "instruction" -module Asm +module Arm # ADDRESSING MODE 1 # Logic ,Maths, Move and compare instructions (last three below) - class LogicInstruction < Instruction + class LogicInstruction < Vm::LogicInstruction - def initialize(opcode , condition_code , update_status , args) + def initializ(opcode , condition_code , update_status , args) super(opcode , condition_code , update_status , args) @rn = nil @i = 0 @@ -84,7 +84,7 @@ module Asm io.write_uint32 val end end - class CompareInstruction < LogicInstruction + class CompareInstruction < Vm::CompareInstruction def initialize(opcode , condition_code , update_status , args) super(opcode , condition_code , update_status , args) @update_status_flag = 1 @@ -95,11 +95,12 @@ module Asm do_build args[1] end end - class MoveInstruction < LogicInstruction - def initialize(opcode , condition_code , update_status , args) + 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 end + def build do_build args[1] end diff --git a/lib/arm/memory_instruction.rb b/lib/arm/memory_instruction.rb index b5222486..7fc33b30 100644 --- a/lib/arm/memory_instruction.rb +++ b/lib/arm/memory_instruction.rb @@ -1,10 +1,10 @@ require "asm/nodes" require_relative "instruction" -module Asm +module Arm # ADDRESSING MODE 2 # Implemented: immediate offset with offset=0 - class MemoryInstruction < Instruction + class MemoryInstruction < Vm::MemoryInstruction def initialize(opcode , condition_code , update_status , args) super(opcode , condition_code , update_status , args) diff --git a/lib/arm/stack_instruction.rb b/lib/arm/stack_instruction.rb index 77376f45..be106fe1 100644 --- a/lib/arm/stack_instruction.rb +++ b/lib/arm/stack_instruction.rb @@ -1,10 +1,10 @@ require_relative "instruction" -module Asm +module Arm # ADDRESSING MODE 4 - class StackInstruction < Instruction + class StackInstruction < Vm::StackInstruction - def initialize(opcode , condition_code , update_status , args) + 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 diff --git a/lib/support/hash_attributes.rb b/lib/support/hash_attributes.rb new file mode 100644 index 00000000..5ac0c4ad --- /dev/null +++ b/lib/support/hash_attributes.rb @@ -0,0 +1,24 @@ +# Make hash attributes to object attributes + +module Support + module HashAttributes + # map any function call to an attribute if possible + def method_missing name , *args , &block + if args.length > 1 or block_given? + puts "NO -#{args.length} BLOCK #{block_given?}" + super + else + name = name.to_s + if args.length == 1 #must be assignemnt for ir attr= val + if name.include? "=" + return @attributes[name.chop] = args[0] + else + super + end + else + return @attributes[name] + end + end + end + end +end \ No newline at end of file diff --git a/lib/vm/block.rb b/lib/vm/block.rb index c321e6f2..9eb8bc19 100644 --- a/lib/vm/block.rb +++ b/lib/vm/block.rb @@ -16,7 +16,7 @@ module Vm # See Value description on how to create code/instructions - class Block < Value + class Block < Code def initialize(name) super() @@ -31,6 +31,20 @@ module Vm def verify end + def add_code(kode) + kode.at(@position) + length = kode.length + puts "length #{length}" + @position += length + @codes << kode + end + + def assemble(io) + @codes.each do |obj| + obj.assemble io + end + end + # set the next executed block after self. # why is this useful? if it's unconditional, why not merge them: # So the second block can be used as a jump target. You standard loop needs a block to setup diff --git a/lib/vm/code.rb b/lib/vm/code.rb index f0b5da54..e3dc2ea5 100644 --- a/lib/vm/code.rb +++ b/lib/vm/code.rb @@ -1,3 +1,5 @@ +require_relative "values" + module Vm # Base class for anything that we can assemble @@ -9,7 +11,7 @@ module Vm # All code is position independant once assembled. # But for jumps and calls two passes are neccessary. # The first setting the position, the second assembling - class Code + class Code < Value # just sets position to nil, so we can sell that it has not been set def initialize diff --git a/lib/vm/context.rb b/lib/vm/context.rb index f6ffa354..524f475a 100644 --- a/lib/vm/context.rb +++ b/lib/vm/context.rb @@ -5,28 +5,13 @@ module Vm #currently just holding the program in here so we can have global access class Context + # Make hash attributes to object attributes + include Support::HashAttributes + def initialize program @attributes = {} @attributes["program"] = program end - # map any function call to an attribute if possible - def method_missing name , *args , &block - if args.length > 1 or block_given? - puts "NO -#{args.length} BLOCK #{block_given?}" - super - else - name = name.to_s - if args.length == 1 #must be assignemnt for ir attr= val - if name.include? "=" - return @attributes[name.chop] = args[0] - else - super - end - else - return @attributes[name] - end - end - end end end diff --git a/lib/vm/instruction.rb b/lib/vm/instruction.rb index 82916177..2fef02aa 100644 --- a/lib/vm/instruction.rb +++ b/lib/vm/instruction.rb @@ -1,16 +1,11 @@ - +require_relative "code" +require "support/hash_attributes" module Vm - # Instruction represent the actions that affect change on Values - # In an OO way of thinking the Value is data, Instruction the functionality - # But to allow flexibility, the value api bounces back to the machine api, so machines instantiate - # intructions. - - # When Instructions are instantiated the create a linked list of Values and Instructions. - # So Value links to Instruction and Instruction links to Value - # Also, because the idea of what one instruction does, does not always map one to one to real machine + # Because the idea of what one instruction does, does not always map one to one to real machine # instructions, and instruction may link to another instruction thus creating an arbitrary list # to get the job (the original instruciton) done + # Admittately it would be simpler just to create the (abstract) instructions and let the machine # encode them into what-ever is neccessary, but this approach leaves more possibility to # optimize the actual instruction stream (not just the crystal instruction stream). Makes sense? @@ -25,10 +20,14 @@ module Vm # - Call # Instruction derives from Code, for the assembly api - class Code ; end - + class Instruction < Code + # Make hash attributes to object attributes + include Support::HashAttributes + def initialize options + @options = options + end end class StackInstruction < Instruction diff --git a/lib/vm/machine.rb b/lib/vm/machine.rb index 305bbf2e..9d3f60c2 100644 --- a/lib/vm/machine.rb +++ b/lib/vm/machine.rb @@ -11,13 +11,17 @@ module Vm # * Note that register content is typed externally. Not as in mri, where int's are tagged. Floats can's # be tagged and lambda should be it's own type, so tagging does not work - # Programs are created by invoking methods on subclasses of Value. - # But executable code is a sequence of Instructions and subclasses. - # A Machines main responsibility in the framework is to instantiate Instruction + # Value functions are mapped to machines by concatenating the values class name + the methd name # Example: SignedValue.plus( value ) -> Machine.signed_plus (value ) + # Also, shortcuts are created to easily instantiate Instruction objects. The "standard" set of instructions + # (arm-influenced) provides for normal operations on a register machine, + # Example: pop -> StackInstruction.new( {:opcode => :pop}.merge(options) ) + # Instructions work with options, so you can pass anything in, and the only thing the functions does + # is save you typing the clazz.new. It passes the function name as the :opcode + class Machine # hmm, not pretty but for now @@ -32,6 +36,51 @@ module Vm # consistency in this code, but also because that is what is actually done attr_reader :status + + # 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) + 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 + + 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 + create_method(inst) do |options| + options = {} if options == nil + options[:opcode] = inst + clazz.new(options) + end + end + def self.instance @@instance end diff --git a/test/test_runner.rb b/test/test_runner.rb index 8cb56d77..9e0a59ca 100644 --- a/test/test_runner.rb +++ b/test/test_runner.rb @@ -21,8 +21,6 @@ class TestRunner < MiniTest::Test syntax = Parser::Composed.new.parse(string) tree = Parser::Transform.new.apply(syntax) - puts tree.to_yaml - program = Vm::Program.new "Arm" expression = tree.to_value compiled = expression.compile( program.context ) @@ -30,7 +28,7 @@ class TestRunner < MiniTest::Test program.wrap_as_main compiled puts program.to_yaml program.verify - puts program.to_yaml +# puts program.to_yaml end end \ No newline at end of file