more semi random code, a checkpoint

This commit is contained in:
Torsten Ruger 2014-05-03 15:13:44 +03:00
parent 1c86ecb84e
commit 845a8cab8b
14 changed files with 357 additions and 71 deletions

View File

@ -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)

View File

@ -5,3 +5,4 @@ require "elf/object_writer"
require 'parser/composed'
require 'parser/transform'
require "vm/context"
require "vm/machine"

41
lib/vm/block.rb Normal file
View 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

View File

@ -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

View File

@ -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

26
lib/vm/conversion.rb Normal file
View 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
View 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
View 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

View File

@ -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

View File

@ -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

View File

@ -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

75
lib/vm/program.rb Normal file
View 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

View File

@ -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
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
require_relative "conversion"

View File

@ -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