removing builtin as a concept (wip)

the "old" way of generating compilers is now obsolete
we can use ruby code with mom macros to achieve the same
Three step wip
remove old builtin
fix tests (including adding necessary methods)
fixup and inclusion of builtin code to parfait
This commit is contained in:
Torsten Rüger 2019-09-12 13:09:30 +03:00
parent 616dd3487c
commit dced6b12e6
24 changed files with 95 additions and 612 deletions

View File

@ -28,12 +28,6 @@ module Mom
end
end
# Only for init, as init has no return
def _reset_for_init
@mom_instructions = Label.new(source_name, source_name)
@current = @mom_instructions
end
# add a constant (which get created during compilation and need to be linked)
def add_constant(const)
raise "Must be Parfait #{const}" unless const.is_a?(Parfait::Object)

View File

@ -65,7 +65,7 @@ module Mom
# temporary, need to raise really.
factory! << Parfait.object_space.get_factory_for(:Integer)
integer_tmp! << factory[:reserve]
Mom::Builtin.emit_syscall( builder , :died ) #uses integer_tmp
Mom::Macro.emit_syscall( builder , :died ) #uses integer_tmp
add_code ok_label
cache_entry[:cached_method] << callable_method

View File

@ -1,77 +0,0 @@
module Mom
module Builtin
module CompileHelper
def compiler_for( clazz_name , method_name , arguments , locals = {})
frame = Parfait::NamedList.type_for( locals )
args = Parfait::NamedList.type_for( arguments )
Mom::MethodCompiler.compiler_for_class(clazz_name , method_name , args, frame )
end
end
end
class Macro < Instruction
end
end
require_relative "macro/space"
require_relative "macro/integer"
require_relative "macro/object"
require_relative "macro/word"
module Mom
module Builtin
# classes have booted, now create a minimal set of functions
# minimal means only that which can not be coded in ruby
# Methods are grabbed from respective modules by sending the method name.
# This should return the implementation of the method (ie a method compiler),
# not actually try to implement it(as that's impossible in ruby)
#
# We create an empty main for init to jump to, if no code is compiled, that just returns
# See Builtin directory readme and module
def self.boot_functions( options = nil)
space = Parfait.object_space
space_type = space.get_class.instance_type
if @compilers and options == nil
if(space_type.methods.nil?)
@compilers << compiler_for( space_type , Space , :main)
end
return @compilers
end
# TODO go through the virtual parfait layer and adjust function names
# to what they really are
@compilers = []
obj_type = space.get_type_by_class_name(:Object)
[ :__init__ , :exit , :_method_missing, :get_internal_word ,
:set_internal_word ].each do |f|
@compilers << compiler_for( obj_type , Object , f)
end
word_type = space.get_type_by_class_name(:Word)
[:putstring , :get_internal_byte , :set_internal_byte ].each do |f|
@compilers << compiler_for( word_type , Word , f)
end
int_type = space.get_type_by_class_name(:Integer)
Risc.operators.each do |op|
@compilers << operator_compiler( int_type , op)
end
[ :div4, :<,:<= , :>=, :> , :div10 ].each do |f| #div4 is just a forward declaration
@compilers << compiler_for( int_type , Integer , f)
end
return @compilers
end
def self.compiler_for( type , mod , name)
compiler = mod.send(name , nil)
compiler.add_method_to(type)
compiler
end
def self.operator_compiler(int_type , op)
compiler = Integer.operator_method(op)
compiler.add_method_to(int_type)
compiler
end
end
end

View File

@ -1,34 +1,4 @@
module Mom
module Builtin
class Comparison < ::Mom::Instruction
attr_reader :operator
def initialize(name , operator)
super(name)
@operator = operator
end
def to_risc(compiler)
builder = compiler.builder(compiler.source)
operator = @operator # make accessible in block
builder.build do
integer! << message[:receiver]
integer.reduce_int
integer_reg! << message[:arg1] #"other"
integer_reg.reduce_int
swap_names(:integer , :integer_reg) if(operator.to_s.start_with?('<') )
integer.op :- , integer_reg
if_minus false_label
if_zero( false_label ) if operator.to_s.length == 1
object! << Parfait.object_space.true_object
branch merge_label
add_code false_label
object << Parfait.object_space.false_object
add_code merge_label
message[:return_value] << object
end
return compiler
end
end
end
class Comparison < Macro
attr_reader :operator
def initialize(name , operator)

View File

@ -1,67 +1,4 @@
module Mom
module Builtin
class Div10 < ::Mom::Instruction
def to_risc(compiler)
s = "div_10 "
builder = compiler.builder(compiler.source)
integer_tmp = builder.allocate_int
builder.build do
integer_self! << message[:receiver]
integer_self.reduce_int
integer_1! << integer_self
integer_reg! << integer_self
integer_const! << 1
integer_1.op :>> , integer_const
integer_const << 2
integer_reg.op :>> , integer_const
integer_reg.op :+ , integer_1
integer_const << 4
integer_1 << integer_reg
integer_reg.op :>> , integer_1
integer_reg.op :+ , integer_1
integer_const << 8
integer_1 << integer_reg
integer_1.op :>> , integer_const
integer_reg.op :+ , integer_1
integer_const << 16
integer_1 << integer_reg
integer_1.op :>> , integer_const
integer_reg.op :+ , integer_1
integer_const << 3
integer_reg.op :>> , integer_const
integer_const << 10
integer_1 << integer_reg
integer_1.op :* , integer_const
integer_self.op :- , integer_1
integer_1 << integer_self
integer_const << 6
integer_1.op :+ , integer_const
integer_const << 4
integer_1.op :>> , integer_const
integer_reg.op :+ , integer_1
integer_tmp[Parfait::Integer.integer_index] << integer_reg
message[:return_value] << integer_tmp
end
return compiler
end
end
end
class Div10 < Macro
def to_risc(compiler)
s = "div_10 "

View File

@ -1,21 +1,4 @@
module Mom
module Builtin
class Div4 < ::Mom::Instruction
def to_risc(compiler)
builder = compiler.builder(compiler.source)
integer_tmp = builder.allocate_int
builder.build do
integer_self! << message[:receiver]
integer_self.reduce_int
integer_1! << 2
integer_self.op :>> , integer_1
integer_tmp[Parfait::Integer.integer_index] << integer_self
message[:return_value] << integer_tmp
end
return compiler
end
end
end
class Div4 < Macro
def to_risc(compiler)
builder = compiler.builder(compiler.source)

View File

@ -1,14 +1,4 @@
module Mom
module Builtin
class Exit < ::Mom::Instruction
def to_risc(compiler)
builder = compiler.builder(compiler.source)
builder.prepare_int_return # makes integer_tmp variable as return
Builtin.exit_sequence(builder)
return compiler
end
end
end
class Exit < Macro
def to_risc(compiler)
builder = compiler.builder(compiler.source)

View File

@ -1,21 +1,4 @@
module Mom
module Builtin
class GetInternalByte < ::Mom::Instruction
def to_risc(compiler)
builder = compiler.builder(compiler.source)
integer_tmp = builder.allocate_int
builder.build do
object! << message[:receiver]
integer! << message[:arg1] #"at"
integer.reduce_int
object <= object[integer]
integer_tmp[Parfait::Integer.integer_index] << object
message[:return_value] << integer_tmp
end
return compiler
end
end
end
class GetInternalByte < Macro
def to_risc(compiler)
builder = compiler.builder(compiler.source)

View File

@ -1,17 +1,4 @@
module Mom
module Builtin
class GetInternalWord < ::Mom::Instruction
def to_risc(compiler)
compiler.builder(compiler.source).build do
object! << message[:receiver]
integer! << message[:arg1] #"at" is at index 0
integer.reduce_int
object << object[integer]
message[:return_value] << object
end
end
end
end
class GetInternalWord < Macro
def to_risc(compiler)
compiler.builder(compiler.source).build do

View File

@ -1,37 +1,4 @@
module Mom
module Builtin
class Init < ::Mom::Instruction
def to_risc(compiler)
builder = compiler.builder(compiler.source)
builder.build do
factory! << Parfait.object_space.get_factory_for(:Message)
message << factory[:next_object]
next_message! << message[:next_message]
factory[:next_object] << next_message
end
builder.reset_names
Mom::MessageSetup.new(Parfait.object_space.get_main).build_with( builder )
builder.build do
message << message[:next_message]
space? << Parfait.object_space
message[:receiver] << space
end
exit_label = Risc.label(compiler.source , "#{compiler.receiver_type.object_class.name}.#{compiler.source.name}" )
ret_tmp = compiler.use_reg(:Label).set_builder(builder)
builder.build do
ret_tmp << exit_label
message[:return_address] << ret_tmp
add_code Risc.function_call( "__init__ issue call" , Parfait.object_space.get_main)
add_code exit_label
end
compiler.reset_regs
Builtin.exit_sequence(builder)
return compiler
end
end
end
class Init < Macro
def to_risc(compiler)
builder = compiler.builder(compiler.source)
@ -59,7 +26,7 @@ module Mom
add_code exit_label
end
compiler.reset_regs
Builtin.exit_sequence(builder)
Macro.exit_sequence(builder)
return compiler
end
end

View File

@ -1,85 +0,0 @@
require_relative "div4"
require_relative "div10"
require_relative "operator"
require_relative "comparison"
module Mom
module Builtin
# integer related kernel functions
# all these functions (return the function they implement) assume interger input
# Also the returned integer object has to be passed in to avoid having to allocate it.
#
# This means the methods will have to be renamed at some point and wrapped
module Integer
module ClassMethods
include CompileHelper
# div by 4, ie shift right by 2
# Mostly created for testing at this point, as it is short
# return new int with result
def div4(context)
compiler = compiler_for(:Integer,:div4 ,{})
compiler.add_code Div4.new("div4")
return compiler
end
# implemented by the comparison
def >( context )
comparison( :> )
end
# implemented by the comparison
def <( context )
comparison( :< )
end
# implemented by the comparison
def <=( context )
comparison( :<= )
end
# implemented by the comparison
def >=( context )
comparison( :>= )
end
# all (four) comparison operation are quite similar and implemented here
# - reduce the ints (assume int as input)
# - subtract the fixnums
# - check for minus ( < and > )
# - also check for zero (<= and >=)
# - load true or false object into return, depending on check
# - return
def comparison( operator )
compiler = compiler_for(:Integer, operator ,{other: :Integer })
compiler.add_code Comparison.new("comparison" , operator)
return compiler
end
# implemented all known binary operators that map straight to machine codes
# this function (similar to comparison):
# - unpacks the intergers to fixnum
# - applies the operator (at a risc level)
# - gets a new integer and stores the result
# - returns the new int
def operator_method( op_sym )
compiler = compiler_for(:Integer, op_sym ,{other: :Integer })
compiler.add_code Operator.new( "op:#{op_sym}" , op_sym)
return compiler
end
# as the name suggests, this devides the integer (self) by ten
#
# This version is lifted from some arm assembler tricks and is _much_
# faster than the general div versions. I think it was about three
# times less instructions. Useful for itos
#
# In fact it is possible to generate specific div function for any given
# integer and some are even more faster (as eg div4).
def div10( context )
compiler = compiler_for(:Integer,:div10 ,{})
compiler.add_code Div10.new("div10")
return compiler
end
end
extend ClassMethods
end
end
end

63
lib/mom/macro/macro.rb Normal file
View File

@ -0,0 +1,63 @@
module Mom
class Macro < Instruction
# emit the syscall with given name
# there is a Syscall instruction, but the message has to be saved and restored
def self.emit_syscall( builder , name )
save_message( builder )
builder.add_code Risc::Syscall.new("emit_syscall(#{name})", name )
restore_message(builder)
return unless (@clazz and @method)
builder.add_code Risc.label( "#{@clazz.name}.#{@message.name}" , "return_syscall" )
end
# a sort of inline version of exit method.
# Used by exit and __init__ (so it doesn't have to call it)
# Assumes int return value and extracts the fixnum for process exit code
def self.exit_sequence(builder)
save_message( builder )
builder.build do
message << message[:return_value]
message.reduce_int
add_code Risc::Syscall.new("emit_syscall(exit)", :exit )
end
end
# save the current message, as the syscall destroys all context
#
# This relies on linux to save and restore all registers
#
def self.save_message(builder)
r8 = Risc::RegisterValue.new( :r8 , :Message).set_builder(builder)
builder.build {r8 << message}
end
# restore the message that we save in r8
# before th restore, the syscall return, a fixnum, is saved
# The caller of this method is assumed to caal prepare_int_return
# so that the return value already has an integer instance
# This instance is filled with os return value
def self.restore_message(builder)
r8 = Risc::RegisterValue.new( :r8 , :Message)
builder.build do
integer_reg! << message
message << r8
integer_2! << message[:return_value]
integer_2[Parfait::Integer.integer_index] << integer_reg
end
end
end
end
require_relative "comparison"
require_relative "exit"
require_relative "init"
require_relative "putstring"
require_relative "set_internal_word"
require_relative "div10"
require_relative "get_internal_byte"
require_relative "method_missing"
require_relative "div4"
require_relative "get_internal_word"
require_relative "operator"
require_relative "set_internal_byte"

View File

@ -1,14 +1,4 @@
module Mom
module Builtin
class MethodMissing < ::Mom::Instruction
def to_risc(compiler)
builder = compiler.builder(compiler.source)
builder.prepare_int_return # makes integer_tmp variable as return
Builtin.emit_syscall( builder , :exit )
return compiler
end
end
end
class MethodMissing < Macro
def to_risc(compiler)
builder = compiler.builder(compiler.source)

View File

@ -1,107 +1,3 @@
require_relative "get_internal_word"
require_relative "set_internal_word"
require_relative "method_missing"
require_relative "init"
require_relative "exit"
module Mom
module Builtin
class Object
module ClassMethods
include CompileHelper
# self[index] basically. Index is the first arg
# return is stored in return_value
def get_internal_word( context )
compiler = compiler_for(:Object , :get_internal_word ,{at: :Integer})
compiler.add_code GetInternalWord.new("get_internal_word")
return compiler
end
# self[index] = val basically. Index is the first arg , value the second
# return the value passed in
def set_internal_word( context )
compiler = compiler_for(:Object , :set_internal_word , {at: :Integer, value: :Object} )
compiler.add_code SetInternalWord.new("set_internal_word")
return compiler
end
# every object needs a method missing.
# Even if it's just this one, sys_exit (later raise)
def _method_missing( context )
compiler = compiler_for(:Object,:method_missing ,{})
compiler.add_code MethodMissing.new("missing")
return compiler
end
# this is the really really first place the machine starts (apart from the jump here)
# it isn't really a function, ie it is jumped to (not called), exits and may not return
# so it is responsible for initial setup:
# - load fist message, set up Space as receiver
# - call main, ie set up message for that etc
# - exit (exit_sequence) which passes a machine int out to c
def __init__( context )
compiler = compiler_for(:Object,:__init__ ,{})
compiler._reset_for_init # no return, just for init
compiler.add_code Init.new("missing")
return compiler
end
# the exit function
# mainly calls exit_sequence
def exit( context )
compiler = compiler_for(:Object,:exit ,{})
compiler.add_code Exit.new("exit")
return compiler
end
end
extend ClassMethods
end
# emit the syscall with given name
# there is a Syscall instruction, but the message has to be saved and restored
def self.emit_syscall( builder , name )
save_message( builder )
builder.add_code Risc::Syscall.new("emit_syscall(#{name})", name )
restore_message(builder)
return unless (@clazz and @method)
builder.add_code Risc.label( "#{@clazz.name}.#{@message.name}" , "return_syscall" )
end
# a sort of inline version of exit method.
# Used by exit and __init__ (so it doesn't have to call it)
# Assumes int return value and extracts the fixnum for process exit code
def self.exit_sequence(builder)
save_message( builder )
builder.build do
message << message[:return_value]
message.reduce_int
add_code Risc::Syscall.new("emit_syscall(exit)", :exit )
end
end
# save the current message, as the syscall destroys all context
#
# This relies on linux to save and restore all registers
#
def self.save_message(builder)
r8 = Risc::RegisterValue.new( :r8 , :Message).set_builder(builder)
builder.build {r8 << message}
end
# restore the message that we save in r8
# before th restore, the syscall return, a fixnum, is saved
# The caller of this method is assumed to caal prepare_int_return
# so that the return value already has an integer instance
# This instance is filled with os return value
def self.restore_message(builder)
r8 = Risc::RegisterValue.new( :r8 , :Message)
builder.build do
integer_reg! << message
message << r8
integer_2! << message[:return_value]
integer_2[Parfait::Integer.integer_index] << integer_reg
end
end
end
end

View File

@ -1,29 +1,4 @@
module Mom
module Builtin
class Operator < Instruction
attr_reader :operator
def initialize(name , operator)
super(name)
@operator = operator
end
def to_risc(compiler)
builder = compiler.builder(compiler.source)
integer_tmp = builder.allocate_int
operator = @operator # make accessible in block
builder.build do
integer! << message[:receiver]
integer.reduce_int
integer_reg! << message[:arg1] #"other"
integer_reg.reduce_int
integer.op operator , integer_reg
integer_tmp[Parfait::Integer.integer_index] << integer
message[:return_value] << integer_tmp
end
return compiler
end
end
end
class IntOperator < Macro
attr_reader :operator
def initialize(name , operator)

View File

@ -1,18 +1,4 @@
module Mom
module Builtin
class Putstring < ::Mom::Instruction
def to_risc(compiler)
builder = compiler.builder(compiler.source)
builder.prepare_int_return # makes integer_tmp variable as return
builder.build do
word! << message[:receiver]
integer! << word[Parfait::Word.get_length_index]
end
Mom::Builtin.emit_syscall( builder , :putstring )
compiler
end
end
end
class Putstring < Macro
def to_risc(compiler)
builder = compiler.builder(compiler.source)

View File

@ -1,20 +1,4 @@
module Mom
module Builtin
class SetInternalByte < ::Mom::Instruction
def to_risc(compiler)
compiler.builder(compiler.source).build do
word! << message[:receiver]
integer_reg! << message[:arg2] #VALUE
message[:return_value] << integer_reg
integer! << message[:arg1] #"index"
integer.reduce_int
integer_reg.reduce_int
word[integer] <= integer_reg
end
return compiler
end
end
end
class SetInternalByte < Macro
def to_risc(compiler)
compiler.builder(compiler.source).build do

View File

@ -1,19 +1,4 @@
module Mom
module Builtin
class SetInternalWord < ::Mom::Instruction
def to_risc(compiler)
compiler.builder(compiler.source).build do
object! << message[:receiver]
integer! << message[:arg1] # "index"
object_reg! << message[:arg2]#"value"
integer.reduce_int
object[integer] << object_reg
message[:return_value] << object_reg
end
return compiler
end
end
end
class SetInternalWord < Macro
def to_risc(compiler)
compiler.builder(compiler.source).build do

View File

@ -1,18 +0,0 @@
module Mom
module Builtin
class Space
module ClassMethods
include CompileHelper
# main entry point, ie __init__ calls this
# defined here as empty, to be redefined
def main(context)
compiler = compiler_for(:Space , :main ,{args: :Integer})
return compiler
end
end
extend ClassMethods
end
end
end

View File

@ -1,43 +0,0 @@
require_relative "get_internal_byte"
require_relative "set_internal_byte"
require_relative "putstring"
module Mom
module Builtin
module Word
module ClassMethods
include CompileHelper
# wrapper for the syscall
# io/file currently hardcoded to stdout
# set up registers for syscall, ie
# - pointer in r1
# - length in r2
# - emit_syscall (which does the return of an integer, see there)
def putstring( context)
compiler = compiler_for(:Word , :putstring ,{})
compiler.add_code Putstring.new("putstring")
return compiler
end
# self[index] basically. Index is the first arg > 0
# return a word sized new int, in return_value
#
# Note: no index (or type) checking. Method should be internal and check before.
# Which means the returned integer could be passed in, instead of allocated.
def get_internal_byte( context)
compiler = compiler_for(:Word , :get_internal_byte , {at: :Integer})
compiler.add_code GetInternalByte.new("get_internal_byte")
return compiler
end
# self[index] = val basically. Index is the first arg ( >0 , unchecked),
# value the second, which is also returned
def set_internal_byte( context )
compiler = compiler_for(:Word, :set_internal_byte , {at: :Integer , value: :Integer} )
compiler.add_code SetInternalByte.new("set_internal_byte")
return compiler
end
end
extend ClassMethods
end
end
end

View File

@ -87,5 +87,12 @@ module Mom
ret
end
# Only for init, as init has no return
# kind of private
def _reset_for_init
@mom_instructions = Label.new(source_name, source_name)
@current = @mom_instructions
end
end
end

View File

@ -11,16 +11,9 @@
# No send or call, just objects and jump.
# Machine capabilities (instructions) for basic operations. Use of macros for higher level.
module Mom
# boot bubiltin function (subject to change)
def self.boot!(options = {})
Builtin.boot_functions(options)
end
end
require_relative "instruction.rb"
require_relative "mom_collection"
require_relative "callable_compiler"
require_relative "method_compiler"
require_relative "block_compiler"
require_relative "macro"
require_relative "macro/macro"

View File

@ -14,16 +14,15 @@ module Mom
@method_compilers = compilers
end
# lazily instantiate the compilers for boot functions
# (in the hope of only booting the functions once)
def boot_compilers
@boot_compilers ||= Mom::Builtin.boot_functions
# lazily instantiate the compiler for init function
def init_compiler
@init_compilers ||= create_init_compiler
end
# Return all compilers, namely the MethodCompilers passed in, plus the
# boot_function's compilers (boot_compilers)
def compilers
@method_compilers + boot_compilers
@method_compilers << init_compiler
end
# Append another MomCompilers method_compilers to this one.
@ -42,5 +41,23 @@ module Mom
Risc::RiscCollection.new(riscs)
end
# this is the really really first place the machine starts (apart from the jump here)
# it isn't really a function, ie it is jumped to (not called), exits and may not return
# so it is responsible for initial setup:
# - load fist message, set up Space as receiver
# - call main, ie set up message for that etc
# - exit (exit_sequence) which passes a machine int out to c
def create_init_compiler
compiler = self.class.compiler_for(:Object,:__init__ ,{})
compiler._reset_for_init # no return, just for init
compiler.add_code Init.new("missing")
return compiler
end
def self.compiler_for( clazz_name , method_name , arguments , locals = {})
frame = Parfait::NamedList.type_for( locals )
args = Parfait::NamedList.type_for( arguments )
MethodCompiler.compiler_for_class(clazz_name , method_name , args, frame )
end
end
end

View File

@ -31,7 +31,6 @@ module RubyX
def initialize(options)
@options = options
Parfait.boot!(options[:parfait] || {})
Mom.boot!(options[:mom] || {})
Risc.boot!(options[:risc] || {})
end