diff --git a/lib/arm/arm_machine.rb b/lib/arm/arm_machine.rb new file mode 100644 index 00000000..ff45a2d5 --- /dev/null +++ b/lib/arm/arm_machine.rb @@ -0,0 +1,148 @@ +require_relative "instruction" + +module Arm + + # Our virtual c-machine has a number of registers of a given size and uses a stack + # So much so standard + # But our machine is oo, meaning that the register contents is typed. + # Off course current hardware does not have that (a perceived issue), but for our machine we pretend. + # So internally we have at least 8 word registers, one of which is used to keep track of types* + # and any number of scratch registers + # but externally it's all Values (see there) + + # * 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 + + # 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: IntegerValue.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 ArmMachine + + # hmm, not pretty but for now + @@instance = nil + + attr_reader :registers + attr_reader :scratch + attr_reader :pc + attr_reader :stack + # is often a pseudo register (ie doesn't support move or other operations). + # Still, using if to express tests makes sense, not just for + # 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_one(inst , StackInstruction) + end + [:adc, :add, :and, :bic, :eor, :orr, :rsb, :rsc, :sbc, :sub].each do |inst| + define_instruction_three(inst , LogicInstruction) + end + [:mov, :mvn].each do |inst| + define_instruction_two(inst , MoveInstruction) + end + [:cmn, :cmp, :teq, :tst].each do |inst| + define_instruction_two(inst , CompareInstruction) + end + [:strb, :str , :ldrb, :ldr].each do |inst| + define_instruction_three(inst , MemoryInstruction) + end + [:b, :call , :swi].each do |inst| + define_instruction_one(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_one("b#{suffix}".to_sym , CallInstruction) + define_instruction_one("call#{suffix}".to_sym , CallInstruction) + end + end + + def create_method(name, &block) + self.class.send(:define_method, name , &block) + end + + + def self.instance + if(@@instance.nil?) + @@instance = Arm::ArmMachine.new + end + @@instance + end + def self.instance= machine + @@instance = machine + end + def class_for clazz + c_name = clazz.name + my_module = self.class.name.split("::").first + clazz_name = clazz.name.split("::").last + if(my_module != Register ) + module_class = eval("#{my_module}::#{clazz_name}") rescue nil + clazz = module_class if module_class + end + clazz + end + + private + #defining the instruction (opcode, symbol) as an given class. + # the class is a Register::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 Register::MoveInstruction + # for an Arm machine, a class Arm::MoveInstruction < Register::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_one(inst , clazz , defaults = {} ) + clazz = self.class_for(clazz) + create_method(inst) do |first , options = nil| + options = {} if options == nil + options.merge defaults + options[:opcode] = inst + first = Register::RegisterReference.new(first) if first.is_a? Symbol + clazz.new(first , options) + end + end + + # same for two args (left right, from to etc) + def define_instruction_two(inst , clazz , defaults = {} ) + clazz = self.class_for(clazz) + create_method(inst) do |left ,right , options = nil| + options = {} if options == nil + options.merge defaults + left = Register::RegisterReference.new(left) if left.is_a? Symbol + right = Register::RegisterReference.new(right) if right.is_a? Symbol + options[:opcode] = inst + clazz.new(left , right ,options) + end + end + + # same for three args (result = left right,) + def define_instruction_three(inst , clazz , defaults = {} ) + clazz = self.class_for(clazz) + create_method(inst) do |result , left ,right = nil , options = nil| + options = {} if options == nil + options.merge defaults + options[:opcode] = inst + result = Register::RegisterReference.new(result) if result.is_a? Symbol + left = Register::RegisterReference.new(left) if left.is_a? Symbol + right = Register::RegisterReference.new(right) if right.is_a? Symbol + clazz.new(result, left , right ,options) + end + end + end +end diff --git a/lib/arm/machine_code.rb b/lib/arm/machine_code.rb index 5ade3b0f..f685492a 100644 --- a/lib/arm/machine_code.rb +++ b/lib/arm/machine_code.rb @@ -1,20 +1,5 @@ module Arm - class ArmMachine < Register::RegisterMachine - # The constants are here for readablility, the code uses access functions below - RETURN_REG = :r0 - TYPE_REG = :r1 - RECEIVER_REG = :r2 - SYSCALL_REG = :r7 - - def return_register - RETURN_REG - end - def type_register - TYPE_REG - end - def receiver_register - RECEIVER_REG - end + class MachineCode def function_call into , call raise "Not CallSite #{call.inspect}" unless call.is_a? Virtual::CallSite @@ -94,10 +79,3 @@ module Arm end end -require_relative "stack_instruction" -require_relative "logic_instruction" -require_relative "move_instruction" -require_relative "compare_instruction" -require_relative "memory_instruction" -require_relative "call_instruction" -require_relative "constants" diff --git a/lib/register/instruction.rb b/lib/register/instruction.rb index 47398864..f7c0c2a1 100644 --- a/lib/register/instruction.rb +++ b/lib/register/instruction.rb @@ -1,24 +1,7 @@ module Register - # The register machine model is close to current hardware and has following instruction classes - # - Memory - # - Stack - # - Logic - # - Math - # - Control/Compare - # - Move - # - Call - - # Instruction derives from Code, for the assembly api - - class Instruction < Virtual::Object - def initialize options - @attributes = options - end - attr_reader :attributes - def opcode - @attributes[:opcode] - end + class Instruction + # returns an array of registers (RegisterReferences) that this instruction uses. # ie for r1 = r2 + r3 # which in assembler is add r1 , r2 , r3 @@ -48,136 +31,4 @@ module Register end end - class StackInstruction < Instruction - def initialize first , options = {} - @first = first - super(options) - end - # when calling we place a dummy push/pop in the stream and calculate later what registers actually need saving - def set_registers regs - @first = regs.collect{ |r| r.symbol } - end - def is_push? - opcode == :push - end - def is_pop? - !is_push? - end - def uses - is_push? ? regs : [] - end - def assigns - is_pop? ? regs : [] - end - def regs - @first - end - def to_s - "#{opcode} [#{@first.collect {|f| f.to_asm}.join(',') }] #{super}" - end - end - class MemoryInstruction < Instruction - def initialize result , left , right = nil , options = {} - @result = result - @left = left - @right = right - super(options) - end - def uses - ret = [@left.register ] - ret << @right.register unless @right.nil? - ret - end - def assigns - [@result.register] - end - end - class LogicInstruction < Instruction - # result = left op right - # - # Logic instruction are your basic operator implementation. But unlike the (normal) code we write - # these Instructions must have "place" to write their results. Ie when you write 4 + 5 in ruby - # the result is sort of up in the air, but with Instructions the result must be assigned - def initialize result , left , right , options = {} - @result = result - @left = left - @right = right.is_a?(Fixnum) ? Virtual::IntegerConstant.new(right) : right - super(options) - end - attr_accessor :result , :left , :right - def uses - ret = [] - ret << @left.register if @left and not @left.is_a? Constant - ret << @right.register if @right and not @right.is_a?(Constant) - ret - end - def assigns - [@result.register] - end - end - class CompareInstruction < Instruction - def initialize left , right , options = {} - @left = left - @right = right.is_a?(Fixnum) ? IntegerConstant.new(right) : right - super(options) - end - def uses - ret = [@left.register ] - ret << @right.register unless @right.is_a? Constant - ret - end - def assigns - [] - end - def to_s - "#{opcode} #{@left.to_asm} , #{@right.to_asm} #{super}" - end - end - class MoveInstruction < Instruction - def initialize to , from , options = {} - @to = to - @from = from.is_a?(Fixnum) ? Virtual::IntegerConstant.new(from) : from - raise "move must have from set #{inspect}" unless from - super(options) - end - attr_accessor :to , :from - def uses - @from.is_a?(Constant) ? [] : [@from.register] - end - def assigns - [@to.register] - end - end - class CallInstruction < Instruction - def initialize first , options = {} - @first = first - super(options) - 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 - if opcode.length == 6 and opcode[0] == "c" - @attributes[:condition_code] = opcode[4,2].to_sym - @attributes[:opcode] = :call - end - end - def uses - if opcode == :call - @first.args.collect {|arg| arg.register } - else - [] - end - end - def assigns - if opcode == :call - [RegisterReference.new(RegisterMachine.instance.return_register)] - else - [] - end - end - def to_s - "#{opcode} #{@first.to_asm} #{super}" - end - end end diff --git a/lib/register/optimisations.rb b/lib/register/optimisations.rb index 34c1dc7a..72695c1b 100644 --- a/lib/register/optimisations.rb +++ b/lib/register/optimisations.rb @@ -79,30 +79,4 @@ module Register end end end - - # We insert push/pops as dummies to fill them later in CallSaving - # as we can not know ahead of time which locals wil be live in the code to come - # and also we don't want to "guess" later where the push/pops should be - - # Here we check which registers need saving and add them - # Or sometimes just remove the push/pops, when no locals needed saving - class SaveLocals - def run block - push = block.call_block? - return unless push - return unless block.function - locals = block.function.locals_at block - pop = block.next.codes.first - if(locals.empty?) - #puts "Empty #{block.name}" - block.codes.delete(push) - block.next.codes.delete(pop) - else - #puts "PUSH #{push}" - push.set_registers(locals) - #puts "POP #{pop}" - pop.set_registers(locals) - end - end - end end diff --git a/lib/register/register_machine.rb b/lib/register/register_machine.rb index d71c2585..5c397bbd 100644 --- a/lib/register/register_machine.rb +++ b/lib/register/register_machine.rb @@ -1,149 +1,3 @@ -module Register - - # Our virtual c-machine has a number of registers of a given size and uses a stack - # So much so standard - # But our machine is oo, meaning that the register contents is typed. - # Off course current hardware does not have that (a perceived issue), but for our machine we pretend. - # So internally we have at least 8 word registers, one of which is used to keep track of types* - # and any number of scratch registers - # but externally it's all Values (see there) - - # * 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 - - # 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: IntegerValue.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 RegisterMachine - - # hmm, not pretty but for now - @@instance = nil - - attr_reader :registers - attr_reader :scratch - attr_reader :pc - attr_reader :stack - # is often a pseudo register (ie doesn't support move or other operations). - # Still, using if to express tests makes sense, not just for - # 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_one(inst , StackInstruction) - end - [:adc, :add, :and, :bic, :eor, :orr, :rsb, :rsc, :sbc, :sub].each do |inst| - define_instruction_three(inst , LogicInstruction) - end - [:mov, :mvn].each do |inst| - define_instruction_two(inst , MoveInstruction) - end - [:cmn, :cmp, :teq, :tst].each do |inst| - define_instruction_two(inst , CompareInstruction) - end - [:strb, :str , :ldrb, :ldr].each do |inst| - define_instruction_three(inst , MemoryInstruction) - end - [:b, :call , :swi].each do |inst| - define_instruction_one(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_one("b#{suffix}".to_sym , CallInstruction) - define_instruction_one("call#{suffix}".to_sym , CallInstruction) - end - end - - def create_method(name, &block) - self.class.send(:define_method, name , &block) - end - - - def self.instance - if(@@instance.nil?) - @@instance = Arm::ArmMachine.new - end - @@instance - end - def self.instance= machine - @@instance = machine - end - def class_for clazz - c_name = clazz.name - my_module = self.class.name.split("::").first - clazz_name = clazz.name.split("::").last - if(my_module != Register ) - module_class = eval("#{my_module}::#{clazz_name}") rescue nil - clazz = module_class if module_class - end - clazz - end - - private - #defining the instruction (opcode, symbol) as an given class. - # the class is a Register::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 Register::MoveInstruction - # for an Arm machine, a class Arm::MoveInstruction < Register::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_one(inst , clazz , defaults = {} ) - clazz = self.class_for(clazz) - create_method(inst) do |first , options = nil| - options = {} if options == nil - options.merge defaults - options[:opcode] = inst - first = Register::RegisterReference.new(first) if first.is_a? Symbol - clazz.new(first , options) - end - end - - # same for two args (left right, from to etc) - def define_instruction_two(inst , clazz , defaults = {} ) - clazz = self.class_for(clazz) - create_method(inst) do |left ,right , options = nil| - options = {} if options == nil - options.merge defaults - left = Register::RegisterReference.new(left) if left.is_a? Symbol - right = Register::RegisterReference.new(right) if right.is_a? Symbol - options[:opcode] = inst - clazz.new(left , right ,options) - end - end - - # same for three args (result = left right,) - def define_instruction_three(inst , clazz , defaults = {} ) - clazz = self.class_for(clazz) - create_method(inst) do |result , left ,right = nil , options = nil| - options = {} if options == nil - options.merge defaults - options[:opcode] = inst - result = Register::RegisterReference.new(result) if result.is_a? Symbol - left = Register::RegisterReference.new(left) if left.is_a? Symbol - right = Register::RegisterReference.new(right) if right.is_a? Symbol - clazz.new(result, left , right ,options) - end - end - end -end require_relative "instruction" require_relative "register_reference" require "arm/arm_machine" diff --git a/lib/virtual/instruction.rb b/lib/virtual/instruction.rb index a6f57bd6..c62431b4 100644 --- a/lib/virtual/instruction.rb +++ b/lib/virtual/instruction.rb @@ -37,76 +37,8 @@ module Virtual end end - # the first instruction we need is to stop. Off course in a real machine this would be a syscall, but that is just - # an implementation (in a programm it would be a function). But in a virtual machine, not only do we need this instruction, - # it is indeed the first instruction as just this instruction is the smallest possible programm for the machine. - # As such it is the next instruction for any first instruction that we generate. - class Halt < Instruction - end - - # following classes are stubs. currently in brainstorming mode, so anything may change anytime - class MethodEnter < Instruction - end - class MethodReturn < Instruction - end - - # a branch must branch to a block. This is an abstract class, names indicate the actual test - class Branch < Instruction - def initialize to - @to = to - end - attr_reader :to - end - - # implicit means there is no explcit test involved. - # normal ruby rules are false and nil are false, EVERYTHING else is true (and that includes 0) - class ImplicitBranch < Branch - end - - class UnconditionalBranch < Branch - end - - class NewMessage < Instruction - end - class NewFrame < Instruction - end - - class MessageSend < Instruction - def initialize name , me , args = [] - @name = name.to_sym - @me = me - @args = args - end - attr_reader :name , :me , :args - end - - class FunctionCall < Instruction - def initialize method - @method = method - end - attr_reader :method - end - - # class for Set instructions, A set is basically a mem move. - # to and from are indexes into the known objects(frame,message,self and new_message), these are represented as slots - # (see there) - # from may be a Constant (Object,Integer,String,Class) - class Set < Instruction - def initialize to , from - @to = to - # hard to find afterwards where it came from, so ensure it doesn't happen - raise "From must be slot or constant, not symbol #{from}" if from.is_a? Symbol - @from = from - end - attr_reader :to , :from - end - - # Get a instance variable by _name_ . So we have to resolve the name to an index to trnsform into a Slot - # The slot may the be used in a set on left or right hand. The transformation is done by GetImplementation - class InstanceGet < Instruction - def initialize name - @name = name.to_sym - end - attr_reader :name - end end + +require_relative "instructions/access.rb" +require_relative "instructions/control.rb" +require_relative "instructions/messaging.rb" diff --git a/lib/virtual/instructions/access.rb b/lib/virtual/instructions/access.rb new file mode 100644 index 00000000..a3d9f97c --- /dev/null +++ b/lib/virtual/instructions/access.rb @@ -0,0 +1,25 @@ +module Virtual + + # class for Set instructions, A set is basically a mem move. + # to and from are indexes into the known objects(frame,message,self and new_message), these are represented as slots + # (see there) + # from may be a Constant (Object,Integer,String,Class) + class Set < Instruction + def initialize to , from + @to = to + # hard to find afterwards where it came from, so ensure it doesn't happen + raise "From must be slot or constant, not symbol #{from}" if from.is_a? Symbol + @from = from + end + attr_reader :to , :from + end + + # Get a instance variable by _name_ . So we have to resolve the name to an index to trnsform into a Slot + # The slot may the be used in a set on left or right hand. The transformation is done by GetImplementation + class InstanceGet < Instruction + def initialize name + @name = name.to_sym + end + attr_reader :name + end +end diff --git a/lib/virtual/instructions/control.rb b/lib/virtual/instructions/control.rb new file mode 100644 index 00000000..f7b3e690 --- /dev/null +++ b/lib/virtual/instructions/control.rb @@ -0,0 +1,30 @@ +module Virtual + + + # the first instruction we need is to stop. Off course in a real machine this would be a syscall, but that is just + # an implementation (in a programm it would be a function). But in a virtual machine, not only do we need this instruction, + # it is indeed the first instruction as just this instruction is the smallest possible programm for the machine. + # As such it is the next instruction for any first instruction that we generate. + class Halt < Instruction + end + + class MethodReturn < Instruction + end + + # a branch must branch to a block. This is an abstract class, names indicate the actual test + class Branch < Instruction + def initialize to + @to = to + end + attr_reader :to + end + + # implicit means there is no explcit test involved. + # normal ruby rules are false and nil are false, EVERYTHING else is true (and that includes 0) + class ImplicitBranch < Branch + end + + class UnconditionalBranch < Branch + end + +end diff --git a/lib/virtual/instructions/messaging.rb b/lib/virtual/instructions/messaging.rb new file mode 100644 index 00000000..2b9f8f5e --- /dev/null +++ b/lib/virtual/instructions/messaging.rb @@ -0,0 +1,28 @@ +module Virtual + + # following classes are stubs. currently in brainstorming mode, so anything may change anytime + class MethodEnter < Instruction + end + + class NewMessage < Instruction + end + class NewFrame < Instruction + end + + class MessageSend < Instruction + def initialize name , me , args = [] + @name = name.to_sym + @me = me + @args = args + end + attr_reader :name , :me , :args + end + + class FunctionCall < Instruction + def initialize method + @method = method + end + attr_reader :method + end + +end