diff --git a/lib/asm/string_literal.rb b/lib/asm/string_literal.rb index 5c9984e6..36f54800 100644 --- a/lib/asm/string_literal.rb +++ b/lib/asm/string_literal.rb @@ -1,4 +1,4 @@ -require_relative "code" +require_relative "../vm/code" module Asm # The name really says it all. @@ -6,7 +6,7 @@ module Asm # Currently string are stored "inline" , ie in the code segment. # Mainly because that works an i aint no elf expert. - class StringLiteral < Code + class StringLiteral < Vm::Code # currently aligned to 4 (ie padded with 0) and off course 0 at the end def initialize(str) diff --git a/lib/crystal.rb b/lib/crystal.rb index 16f296e1..354862fb 100644 --- a/lib/crystal.rb +++ b/lib/crystal.rb @@ -5,3 +5,4 @@ require "elf/object_writer" require 'parser/composed' require 'parser/transform' require "vm/context" +require "vm/machine" diff --git a/lib/vm/block.rb b/lib/vm/block.rb new file mode 100644 index 00000000..d0bc541f --- /dev/null +++ b/lib/vm/block.rb @@ -0,0 +1,41 @@ +require_relative "values" + +module Vm + + # Think flowcharts: blocks are the boxes. The smallest unit of linear code + + # Blocks must end in control instructions (jump/call/return). + # And the only valid argument for a jump is a Block + + # Blocks for a double linked list so one can traverse back and forth + + # There are four ways for a block to get data (to work on) + # - hard coded constants (embedded in code) + # - memory move + # - values passed in (from previous blocks. ie local variables) + + # See Value description on how to create code/instructions + + class Block < Value + + def initialize(name) + super() + @name = name.to_sym + end + + attr_reader :name + + def verify + raise "Empty #{self.inspect}" if @values.empty? + end + private + + # possibly misguided ?? + def add_arg value + # TODO check + @args << value + end + + end + +end \ No newline at end of file diff --git a/lib/vm/code.rb b/lib/vm/code.rb index 3dc91a2b..f0b5da54 100644 --- a/lib/vm/code.rb +++ b/lib/vm/code.rb @@ -1,8 +1,7 @@ - -module Asm +module Vm # Base class for anything that we can assemble - - # Derived classes include instrucitons, blocks and data(strings) + + # Derived classes include instructions and data(strings) # The commonality abstracted here is the length and position # and the ability to assemble itself into the stream diff --git a/lib/vm/context.rb b/lib/vm/context.rb index 9d0ec2b2..f6ffa354 100644 --- a/lib/vm/context.rb +++ b/lib/vm/context.rb @@ -1,53 +1,32 @@ require_relative "kernel" +require_relative "program" module Vm + + #currently just holding the program in here so we can have global access class Context - def initialize - @locals = {} + def initialize program + @attributes = {} + @attributes["program"] = program end - def get name - @locals[name] - end - end -end - -# ast classes -module Parser - Expression.class_eval do - def compile builder , context - raise "abstract #{self.inspect}" - end - end - - IntegerExpression.class_eval do - end - - NameExpression.class_eval do - end - - StringExpression.class_eval do - def compile builder , context - return string - end - end - - FuncallExpression.class_eval do - def compile builder , context - arguments = args.collect{|arg| arg.compile(builder , context) } - function = context.get(name) - unless function - function = Vm::Kernel.send(name) - context.add_function( name , function ) + + # 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 - - ConditionalExpression.class_eval do - end - - AssignmentExpression.class_eval do - end - FunctionExpression.class_eval do + end end end diff --git a/lib/vm/conversion.rb b/lib/vm/conversion.rb new file mode 100644 index 00000000..9021f030 --- /dev/null +++ b/lib/vm/conversion.rb @@ -0,0 +1,26 @@ +module Vm + # Convert ast to vm-values via visitor pattern + # We do this (what would otherwise seem like foot-shuffling) to keep the layers seperated + # Ie towards the feature goal of reusing the same parse for several binary outputs + + # scope of the funcitons is thus class scope ie self is the expression and all attributes work + # gets included into Value + module Conversion + def to_value + cl_name = self.class.name.to_s.split("::").last.gsub("Expression","").downcase + send "#{cl_name}_value" + end + def funcall_value + FunctionCall.new( name , args.collect{ |a| a.to_value } ) + end + def string_value + ObjectReference.new( string ) + end + end + +end +require_relative "../parser/nodes" + +Parser::Expression.class_eval do + include Vm::Conversion +end \ No newline at end of file diff --git a/lib/vm/function.rb b/lib/vm/function.rb new file mode 100644 index 00000000..06001ad1 --- /dev/null +++ b/lib/vm/function.rb @@ -0,0 +1,52 @@ +require_relative "block" + +module Vm + + # Functions are similar to Blocks. Where Blocks can be jumped to, Functions can be called. + + # Functions also have arguments, though they are handled differently (in register allocation) + + # Functions have a minimum of two blocks, entry and exit, which are created for you + # but there is no branch created between them, this must be done by the programmer. + + + class Function < Value + + def initialize(name , args = []) + super() + @name = name + @args = args + @entry = Block.new("entry_#{name}") + @exit = Block.new("exit_#{name}") + end + attr_reader :name , :args , :entry , :exit + + def arity + @args.length + end + + def compile function_expression , context + arguments = function_expression.args.collect do |arg| + add_arg arg.to_value + end + function = context.program.get_function(name) + unless function + function = Vm::Kernel.send(name) + context.program.get_or_create_function( name , function , arity ) + end + end + + def verify + @entry.verify + @exit.verify + end + + private + def add_arg value + # TODO check + @args << value + end + + end + +end \ No newline at end of file diff --git a/lib/vm/function_call.rb b/lib/vm/function_call.rb new file mode 100644 index 00000000..09f3c249 --- /dev/null +++ b/lib/vm/function_call.rb @@ -0,0 +1,33 @@ +module Vm + + # name and args , return + + class FunctionCall < Value + + def initialize(name , args) + @name = name + @args = args + @function = nil + end + attr_reader :name , :args , :function + + def compile context + @function = context.program.get_function @name + if @function + raise "error #{self}" unless function.arity != @args.length + else + @function = context.program.get_or_create_function @name + end + args.each_with_index do |arg , index| + arg.load + arg.compile context + end + #puts "funcall #{self.inspect}" + self.call + end + + def call + Machine.instance.function_call self + end + end +end diff --git a/lib/vm/instruction.rb b/lib/vm/instruction.rb index c9a46b61..82916177 100644 --- a/lib/vm/instruction.rb +++ b/lib/vm/instruction.rb @@ -1,6 +1,48 @@ module Vm - class Instruction + # 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 + # 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? + + # We have basic classes (literally) of instructions + # - Memory + # - Stack + # - Logic + # - Math + # - Control/Compare + # - Move + # - Call + + # Instruction derives from Code, for the assembly api + class Code ; end + + class Instruction < Code + end + class StackInstruction < Instruction + end + class MemoryInstruction < Instruction + end + class LogicInstruction < Instruction + end + class MathInstruction < Instruction + end + class CompareInstruction < Instruction + end + class MoveInstruction < Instruction + end + class CallInstruction < Instruction + end end diff --git a/lib/vm/kernel.rb b/lib/vm/kernel.rb index fa07409c..0afe1f5c 100644 --- a/lib/vm/kernel.rb +++ b/lib/vm/kernel.rb @@ -1,5 +1,11 @@ module Vm module Kernel + def self.start + #TODO extract args into array of strings + end + def self.exit + # Machine.exit swi 0 + end def self.puts "me" end diff --git a/lib/vm/machine.rb b/lib/vm/machine.rb index 185a2cb1..305bbf2e 100644 --- a/lib/vm/machine.rb +++ b/lib/vm/machine.rb @@ -11,10 +11,18 @@ 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 - # Note: subclasses should/will be used once we have the basic architecture clear and working + # 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 ) class Machine + # hmm, not pretty but for now + @@instance = nil + attr_reader :registers attr_reader :scratch attr_reader :pc @@ -23,5 +31,12 @@ module Vm # 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 + + def self.instance + @@instance + end + def self.instance= machine + @@instance = machine + end end end diff --git a/lib/vm/program.rb b/lib/vm/program.rb new file mode 100644 index 00000000..84130fc7 --- /dev/null +++ b/lib/vm/program.rb @@ -0,0 +1,75 @@ +require_relative "function" +require_relative "function_call" +require "arm/arm_machine" + +module Vm + # A Program represents an executable that we want to build + # it has a list of functions and (global) objects + + # The main entry is a function called (of all things) "main", This _must be supplied by the compling + # There is a start and exit block that call main, which receives an array of strings + + # While data "ususally" would live in a .data section, we may also "inline" it into the code + # in an oo system all data is represented as objects + + # in terms of variables and their visibility, things are simple. They are either local or global + + # throwing in a context for unspecified use (well one is to pass the programm/globals around) + + + class Program < Block + + # should init for a machine and pass that on to start/exit / register alloc and the like + def initialize + super("start") + # this aint pretty. but i'll go soon enough + Machine.instance = Arm::ArmMachine.new + + @context = Context.new(self) + @functions = [] + @objects = [] + @entry = Vm::Kernel::start + @exit = Vm::Kernel::exit + end + attr_reader :context + + def add_object o + @objects << o # TODO check type , no basic values allowed (must be wrapped) + end + + def get_function name + @functions.detect{ |f| f.name == name } + end + + # preferred way of creating new functions (also forward declarations, will flag unresolved later) + def get_or_create_function name + fun = get_function name + unless fun + fun = Function.new(name) + @functions << fun + end + fun + end + + def compile + super + string_table + end + + def wrap_as_main value + + end + def verify + main = @functions.find{|f| f.name == "main"} + raise "No main in Program" unless main + @functions.each do |funct| + funct.verify + end + end + + private + # the main function + def create_main + end + end +end diff --git a/lib/vm/values.rb b/lib/vm/values.rb index 15d56ff0..1e04ead1 100644 --- a/lib/vm/values.rb +++ b/lib/vm/values.rb @@ -3,14 +3,15 @@ module Vm # Values represent the information as it is processed. Different subclasses for different types, # each type with different operations. # The oprerations on values is what makes a machine do things. - # For compilation, values are mopped to the machines registers and the functions (on values) map - # to machine instructions + # For compilation, values are moved to the machines registers and the methods (on values) map + # to machine instructions # Values are immutable! (that's why they are called values) # Operations on values _always_ produce new values (conceptionally) - # Values are a way to reason about (create/validate) instructions. The final executable is mostly - # instrucions. + # Values are a way to reason about (create/validate) instructions. + # In fact a linked lists of values is created by invoking instructions + # the linked list goes from value to instruction to value, backwards # Word Values are what fits in a register. Derived classes # Float, Reference , Integer(s) must fit the same registers @@ -23,19 +24,18 @@ module Vm def byte_size raise "abstract method called #{self.inspect}" end - attr :register - def initialize reg = nil - @register = nil - end end class Word < Value + def load + Machine.instance.word_load self + end end class Unsigned < Word - def + unsigned + def plus unsigned unless unsigned.is_a? Unsigned unsigned = Conversion.new( unsigned , Unsigned ) end @@ -44,7 +44,7 @@ module Vm end class Signed < Word - def + signed + def plus signed unless signed.is_a? Signed signed = Conversion.new( signed , Signed ) end @@ -62,9 +62,22 @@ module Vm end class ObjectReference < Reference - end - - class Byte < Value + def initialize obj + @object = obj + end + attr_reader :object + + def compile context + if object.is_a? String + context.program.add_object object + else + #TODO define object layout more generally and let objects lay themselves out + # as it is the program does this (in the objectwriter/stringtable) + un.done + end + end end end + +require_relative "conversion" \ No newline at end of file diff --git a/test/test_runner.rb b/test/test_runner.rb index df03e24c..85cbbabb 100644 --- a/test/test_runner.rb +++ b/test/test_runner.rb @@ -1,5 +1,5 @@ require_relative 'helper' - +require "yaml" class TestRunner < MiniTest::Test # this creates test methods dynamically , one for each file in runners directory @@ -25,10 +25,14 @@ class TestRunner < MiniTest::Test #link # execute # check result ? - context = Vm::Context.new - builder = Asm::Assembler.new - compiled = tree.compile( builder , context ) - puts compiled.inspect + program = Vm::Program.new + expression = tree.to_value + compiled = expression.compile( program.context ) + # do some stuff with mains and what not ?? + program.wrap_as_main compiled + puts program.to_yaml + program.verify + puts program.to_yaml end end \ No newline at end of file