more semi random code, a checkpoint
This commit is contained in:
parent
1c86ecb84e
commit
845a8cab8b
@ -1,4 +1,4 @@
|
|||||||
require_relative "code"
|
require_relative "../vm/code"
|
||||||
|
|
||||||
module Asm
|
module Asm
|
||||||
# The name really says it all.
|
# The name really says it all.
|
||||||
@ -6,7 +6,7 @@ module Asm
|
|||||||
# Currently string are stored "inline" , ie in the code segment.
|
# Currently string are stored "inline" , ie in the code segment.
|
||||||
# Mainly because that works an i aint no elf expert.
|
# 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
|
# currently aligned to 4 (ie padded with 0) and off course 0 at the end
|
||||||
def initialize(str)
|
def initialize(str)
|
||||||
|
@ -5,3 +5,4 @@ require "elf/object_writer"
|
|||||||
require 'parser/composed'
|
require 'parser/composed'
|
||||||
require 'parser/transform'
|
require 'parser/transform'
|
||||||
require "vm/context"
|
require "vm/context"
|
||||||
|
require "vm/machine"
|
||||||
|
41
lib/vm/block.rb
Normal file
41
lib/vm/block.rb
Normal file
@ -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
|
@ -1,8 +1,7 @@
|
|||||||
|
module Vm
|
||||||
module Asm
|
|
||||||
# Base class for anything that we can assemble
|
# 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
|
# The commonality abstracted here is the length and position
|
||||||
# and the ability to assemble itself into the stream
|
# and the ability to assemble itself into the stream
|
||||||
|
@ -1,53 +1,32 @@
|
|||||||
require_relative "kernel"
|
require_relative "kernel"
|
||||||
|
require_relative "program"
|
||||||
|
|
||||||
module Vm
|
module Vm
|
||||||
|
|
||||||
|
#currently just holding the program in here so we can have global access
|
||||||
class Context
|
class Context
|
||||||
def initialize
|
def initialize program
|
||||||
@locals = {}
|
@attributes = {}
|
||||||
|
@attributes["program"] = program
|
||||||
end
|
end
|
||||||
def get name
|
|
||||||
@locals[name]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# ast classes
|
# map any function call to an attribute if possible
|
||||||
module Parser
|
def method_missing name , *args , &block
|
||||||
Expression.class_eval do
|
if args.length > 1 or block_given?
|
||||||
def compile builder , context
|
puts "NO -#{args.length} BLOCK #{block_given?}"
|
||||||
raise "abstract #{self.inspect}"
|
super
|
||||||
end
|
else
|
||||||
end
|
name = name.to_s
|
||||||
|
if args.length == 1 #must be assignemnt for ir attr= val
|
||||||
IntegerExpression.class_eval do
|
if name.include? "="
|
||||||
end
|
return @attributes[name.chop] = args[0]
|
||||||
|
else
|
||||||
NameExpression.class_eval do
|
super
|
||||||
end
|
end
|
||||||
|
else
|
||||||
StringExpression.class_eval do
|
return @attributes[name]
|
||||||
def compile builder , context
|
end
|
||||||
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 )
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
ConditionalExpression.class_eval do
|
|
||||||
end
|
|
||||||
|
|
||||||
AssignmentExpression.class_eval do
|
|
||||||
end
|
|
||||||
FunctionExpression.class_eval do
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
26
lib/vm/conversion.rb
Normal file
26
lib/vm/conversion.rb
Normal file
@ -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
|
52
lib/vm/function.rb
Normal file
52
lib/vm/function.rb
Normal file
@ -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
|
33
lib/vm/function_call.rb
Normal file
33
lib/vm/function_call.rb
Normal file
@ -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
|
@ -1,6 +1,48 @@
|
|||||||
|
|
||||||
module Vm
|
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
|
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
|
end
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
module Vm
|
module Vm
|
||||||
module Kernel
|
module Kernel
|
||||||
|
def self.start
|
||||||
|
#TODO extract args into array of strings
|
||||||
|
end
|
||||||
|
def self.exit
|
||||||
|
# Machine.exit swi 0
|
||||||
|
end
|
||||||
def self.puts
|
def self.puts
|
||||||
"me"
|
"me"
|
||||||
end
|
end
|
||||||
|
@ -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
|
# * 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
|
# 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
|
class Machine
|
||||||
|
|
||||||
|
# hmm, not pretty but for now
|
||||||
|
@@instance = nil
|
||||||
|
|
||||||
attr_reader :registers
|
attr_reader :registers
|
||||||
attr_reader :scratch
|
attr_reader :scratch
|
||||||
attr_reader :pc
|
attr_reader :pc
|
||||||
@ -23,5 +31,12 @@ module Vm
|
|||||||
# Still, using if to express tests makes sense, not just for
|
# Still, using if to express tests makes sense, not just for
|
||||||
# consistency in this code, but also because that is what is actually done
|
# consistency in this code, but also because that is what is actually done
|
||||||
attr_reader :status
|
attr_reader :status
|
||||||
|
|
||||||
|
def self.instance
|
||||||
|
@@instance
|
||||||
|
end
|
||||||
|
def self.instance= machine
|
||||||
|
@@instance = machine
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
75
lib/vm/program.rb
Normal file
75
lib/vm/program.rb
Normal file
@ -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
|
@ -3,14 +3,15 @@ module Vm
|
|||||||
# Values represent the information as it is processed. Different subclasses for different types,
|
# Values represent the information as it is processed. Different subclasses for different types,
|
||||||
# each type with different operations.
|
# each type with different operations.
|
||||||
# The oprerations on values is what makes a machine do things.
|
# 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
|
# For compilation, values are moved to the machines registers and the methods (on values) map
|
||||||
# to machine instructions
|
# to machine instructions
|
||||||
|
|
||||||
# Values are immutable! (that's why they are called values)
|
# Values are immutable! (that's why they are called values)
|
||||||
# Operations on values _always_ produce new values (conceptionally)
|
# Operations on values _always_ produce new values (conceptionally)
|
||||||
|
|
||||||
# Values are a way to reason about (create/validate) instructions. The final executable is mostly
|
# Values are a way to reason about (create/validate) instructions.
|
||||||
# instrucions.
|
# 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
|
# Word Values are what fits in a register. Derived classes
|
||||||
# Float, Reference , Integer(s) must fit the same registers
|
# Float, Reference , Integer(s) must fit the same registers
|
||||||
@ -23,19 +24,18 @@ module Vm
|
|||||||
def byte_size
|
def byte_size
|
||||||
raise "abstract method called #{self.inspect}"
|
raise "abstract method called #{self.inspect}"
|
||||||
end
|
end
|
||||||
attr :register
|
|
||||||
|
|
||||||
def initialize reg = nil
|
|
||||||
@register = nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class Word < Value
|
class Word < Value
|
||||||
|
def load
|
||||||
|
Machine.instance.word_load self
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Unsigned < Word
|
class Unsigned < Word
|
||||||
|
|
||||||
def + unsigned
|
def plus unsigned
|
||||||
unless unsigned.is_a? Unsigned
|
unless unsigned.is_a? Unsigned
|
||||||
unsigned = Conversion.new( unsigned , Unsigned )
|
unsigned = Conversion.new( unsigned , Unsigned )
|
||||||
end
|
end
|
||||||
@ -44,7 +44,7 @@ module Vm
|
|||||||
end
|
end
|
||||||
|
|
||||||
class Signed < Word
|
class Signed < Word
|
||||||
def + signed
|
def plus signed
|
||||||
unless signed.is_a? Signed
|
unless signed.is_a? Signed
|
||||||
signed = Conversion.new( signed , Signed )
|
signed = Conversion.new( signed , Signed )
|
||||||
end
|
end
|
||||||
@ -62,9 +62,22 @@ module Vm
|
|||||||
end
|
end
|
||||||
|
|
||||||
class ObjectReference < Reference
|
class ObjectReference < Reference
|
||||||
end
|
def initialize obj
|
||||||
|
@object = obj
|
||||||
|
end
|
||||||
|
attr_reader :object
|
||||||
|
|
||||||
class Byte < Value
|
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
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
require_relative "conversion"
|
@ -1,5 +1,5 @@
|
|||||||
require_relative 'helper'
|
require_relative 'helper'
|
||||||
|
require "yaml"
|
||||||
class TestRunner < MiniTest::Test
|
class TestRunner < MiniTest::Test
|
||||||
|
|
||||||
# this creates test methods dynamically , one for each file in runners directory
|
# this creates test methods dynamically , one for each file in runners directory
|
||||||
@ -25,10 +25,14 @@ class TestRunner < MiniTest::Test
|
|||||||
#link
|
#link
|
||||||
# execute
|
# execute
|
||||||
# check result ?
|
# check result ?
|
||||||
context = Vm::Context.new
|
program = Vm::Program.new
|
||||||
builder = Asm::Assembler.new
|
expression = tree.to_value
|
||||||
compiled = tree.compile( builder , context )
|
compiled = expression.compile( program.context )
|
||||||
puts compiled.inspect
|
# 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
|
||||||
|
|
||||||
end
|
end
|
Loading…
Reference in New Issue
Block a user