diff --git a/lib/mom/block_compiler.rb b/lib/mom/block_compiler.rb new file mode 100644 index 00000000..350cb120 --- /dev/null +++ b/lib/mom/block_compiler.rb @@ -0,0 +1,53 @@ +module Mom + + # A BlockCompiler is much like a MehtodCompiler, exept for blocks + # + class BlockCompiler < CallableCompiler + + attr_reader :block , :risc_instructions , :constants + alias :block :callable + + def initialize( block , method) + @method = method + super(block) + end + + def source_name + "#{@method.self_type.name}.init" + end + + # resolve the type of the slot, by inferring from it's name, using the type + # scope related slots are resolved by the compiler by method/block + # + # This mainly calls super, and only for :caller adds extra info + # Using the info, means assuming that the block is not passed around (FIXME in 2020) + def slot_type( slot , type) + new_type = super + if slot == :caller + extra_info = { type_frame: @method.frame_type , + type_arguments: @method.arguments_type , + type_self: @method.self_type} + end + return new_type , extra_info + end + # determine how given name need to be accsessed. + # For blocks the options are args or frame + # or then the methods arg or frame + def slot_type_for(name) + if @callable.arguments_type.variable_index(name) + slot_def = [:arguments] + elsif @callable.frame_type.variable_index(name) + slot_def = [:frame] + elsif @method.arguments_type.variable_index(name) + slot_def = [:caller , :caller ,:arguments ] + elsif @method.frame_type.variable_index(name) + slot_def = [:caller ,:caller , :frame ] + elsif + raise "no variable #{name} , need to resolve at runtime" + end + slot_def << name + end + + + end +end diff --git a/lib/mom/callable_compiler.rb b/lib/mom/callable_compiler.rb index 352e8a55..e710d9ab 100644 --- a/lib/mom/callable_compiler.rb +++ b/lib/mom/callable_compiler.rb @@ -1,8 +1,10 @@ module Mom - # CallableCompiler is used to generate Mom instructions. + # CallableCompiler is used to generate mom instructions. It is an abstact base + # class shared by BlockCompiler and MethodCompiler - # - mom_instructions: The sequence of mom level instructions that vool was compiled to + # - mom_instructions: The sequence of mom level instructions that mom was compiled to + # Instructions derive from class Instruction and form a linked list class CallableCompiler @@ -10,11 +12,11 @@ module Mom @callable = callable @constants = [] @block_compilers = [] - @mom_instructions = Risc.label(source_name, source_name) + @mom_instructions = Label.new(source_name, source_name) @current = start = @risc_instructions - add_code Risc.label( source_name, "return_label") - Mom::ReturnSequence.new.to_risc(self) - add_code Risc.label( source_name, "unreachable") + add_code Label.new( source_name, "return_label") + add_code Mom::ReturnSequence.new + add_code Label.new( source_name, "unreachable") @current = start end attr_reader :risc_instructions , :constants , :block_compilers , :callable , :current @@ -26,20 +28,6 @@ module Mom end end - # convert the given mom instruction to_risc and then add it (see add_code) - # continue down the instruction chain unti depleted - # (adding moves the insertion point so the whole mom chain is added as a risc chain) - def add_mom( instruction ) - while( instruction ) - raise "whats this a #{instruction}" unless instruction.is_a?(Mom::Instruction) - #puts "adding mom #{instruction.to_s}:#{instruction.next.to_s}" - instruction.to_risc( self ) - reset_regs - #puts "adding risc #{risc.to_s}:#{risc.next.to_s}" - instruction = instruction.next - end - 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) @@ -49,18 +37,38 @@ module Mom # add a risc instruction after the current (insertion point) # the added instruction will become the new insertion point def add_code( instruction ) - raise "Not an instruction:#{instruction.to_s}:#{instruction.class.name}" unless instruction.is_a?(Risc::Instruction) - raise instruction.to_s if( instruction.class.name.split("::").first == "Arm") + raise "Not an instruction:#{instruction.to_s}:#{instruction.class.name}" unless instruction.is_a?(Mom::Instruction) new_current = instruction.last #after insertion this point is lost @current.insert(instruction) #insert after current @current = new_current self end + # resolve the type of the slot, by inferring from it's name, using the type + # scope related slots are resolved by the compiler by method/block + def slot_type( slot , type) + case slot + when :frame + new_type = self.frame_type + when :arguments + new_type = self.arg_type + when :receiver + new_type = self.receiver_type + when Symbol + new_type = type.type_for(slot) + raise "Not found object #{slot}: in #{type}" unless new_type + else + raise "Not implemented object #{slot}:#{slot.class}" + end + #puts "RESOLVE in #{@type.class_name} #{slot}->#{type}" + return new_type + end + # return the frame type, ie the blocks frame type def frame_type @callable.frame_type end + # return the frame type, ie the blocks arguments type def arg_type @callable.arguments_type diff --git a/lib/mom/method_compiler.rb b/lib/mom/method_compiler.rb new file mode 100644 index 00000000..e8c61b11 --- /dev/null +++ b/lib/mom/method_compiler.rb @@ -0,0 +1,85 @@ +module Mom + + # MethodCompiler is used to generate Mom instructions for methods + # and to instantiate the methods correctly. + + class MethodCompiler < CallableCompiler + + def initialize( method ) + super(method) + end + + #include block_compilers constants + def constants + block_compilers.inject(@constants.dup){|all, compiler| all += compiler.constants} + end + + def source_name + "#{@callable.self_type.name}.#{@callable.name}" + end + + def get_method + @callable + end + + # sometimes the method is used as source (tb reviewed) + def source + @callable + end + + # helper method for builtin mainly + # the class_name is a symbol, which is resolved to the instance_type of that class + # + # return compiler_for_type with the resolved type + # + def self.compiler_for_class( class_name , method_name , args , frame ) + raise "create_method #{class_name}.#{class_name.class}" unless class_name.is_a? Symbol + clazz = Parfait.object_space.get_class_by_name! class_name + compiler_for_type( clazz.instance_type , method_name , args , frame) + end + + def add_method_to( target ) + target.add_method( @callable ) + end + + def create_block(arg_type , frame_type) + @callable.create_block(arg_type ,frame_type) + end + + # create a method for the given type ( Parfait type object) + # method_name is a Symbol + # args a hash that will be converted to a type + # the created method is set as the current and the given type too + # return the compiler + def self.compiler_for_type( type , method_name , args , frame) + raise "create_method #{type.inspect} is not a Type" unless type.is_a? Parfait::Type + raise "Args must be Type #{args}" unless args.is_a?(Parfait::Type) + raise "create_method #{method_name}.#{method_name.class}" unless method_name.is_a? Symbol + method = type.create_method( method_name , args , frame) + self.new(method) + end + + # determine how given name need to be accsessed. + # For methods the options are args or frame + def slot_type_for(name) + if @callable.arguments_type.variable_index(name) + type = :arguments + else + type = :frame + end + [type , name] + end + + def add_block_compiler(compiler) + @block_compilers << compiler + end + + # return true or false if the given name is in scope (arg/local) + def in_scope?(name) + ret = true if @callable.arguments_type.variable_index(name) + ret = @callable.frame_type.variable_index(name) unless ret + ret + end + + end +end diff --git a/lib/mom/mom_collection.rb b/lib/mom/mom_collection.rb new file mode 100644 index 00000000..06948d1e --- /dev/null +++ b/lib/mom/mom_collection.rb @@ -0,0 +1,86 @@ +module Mom + # The Compiler/Collection for the Mom level is a collection of Mom level Method + # compilers These will transform to Risc MethodCompilers on the way down. + # + # As RubyCompiler pools source at the vool level, when several classes are compiled + # from vool to mom, several MomCompilers get instantiated. They must be merged before + # proceeding with translate. Thus we have a append method. + # + class MomCollection + attr_reader :method_compilers + + # Initialize with an array of risc MethodCompilers + def initialize(compilers = []) + @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 ||= Risc::Builtin.boot_functions + end + + # Return all compilers, namely the MethodCompilers passed in, plus the + # boot_function's compilers (boot_compilers) + def compilers + @method_compilers #+ boot_compilers + end + + # collects constants from all compilers into one array + def constants + compilers.inject([]){|sum ,comp| sum + comp.constants } + end + + # Append another MomCompilers method_compilers to this one. + def append(mom_compiler) + @method_compilers += mom_compiler.method_compilers + self + end + + # Translate code to whatever cpu is specified. + # Currently only :arm and :interpret + # + # Translating means translating the initial jump + # and then translating all methods + def translate( platform_sym ) + platform_sym = platform_sym.to_s.capitalize + platform = Risc::Platform.for(platform_sym) + assemblers = translate_methods( platform.translator ) + Risc::Linker.new(platform , assemblers , constants) + end + + # go through all methods and translate them to cpu, given the translator + def translate_methods(translator) + compilers.collect do |compiler| + #log.debug "Translate method #{compiler.method.name}" + translate_method(compiler , translator) + end.flatten + end + + # translate one method, which means the method itself and all blocks inside it + # returns an array of assemblers + def translate_method( method_compiler , translator) + all = [] + all << translate_cpu( method_compiler , translator ) + method_compiler.block_compilers.each do |block_compiler| + all << translate_cpu(block_compiler , translator) + end + all + end + + # compile the callable (method or block) to cpu + # return an Assembler that will then translate to binary + def translate_cpu(compiler , translator) + risc = compiler.risc_instructions + cpu_instructions = risc.to_cpu(translator) + nekst = risc.next + while(nekst) + cpu = nekst.to_cpu(translator) # returning nil means no replace + cpu_instructions << cpu if cpu + nekst = nekst.next + end + Risc::Assembler.new(compiler.callable , cpu_instructions ) + end + + end +end diff --git a/lib/parfait/block.rb b/lib/parfait/block.rb index dfcd8b7c..438246a1 100644 --- a/lib/parfait/block.rb +++ b/lib/parfait/block.rb @@ -3,10 +3,10 @@ module Parfait # A Block is a callable object, much like a CallableMethod. # Surprisingly similar in fact, as the block is really only missing the name. # - # The difference lies mostly in the way they are compiled + # The difference lies mostly in the way they are compiled (scope and return) # # Also both have a list of blocks defined in their scope. But this is - # notimplemented for blocks yet + # not implemented for blocks yet # class Block < Callable diff --git a/lib/parfait/type.rb b/lib/parfait/type.rb index b659c734..7f190a25 100644 --- a/lib/parfait/type.rb +++ b/lib/parfait/type.rb @@ -1,14 +1,15 @@ module Parfait -# An Object is really a hash like structure. It is dynamic and +# An Object is conceptually a hash like structure. It is dynamic and # you want to store values by name (instance variable names). # -# One could (like mri), store the names in each object, but that is wasteful in both time and space. -# Instead we store only the values, and access them by index. +# One could (like mri), store the names in each object, but that is wasteful in both +# time and space. +# Instead we store only the values, and access them by index (bit like c++). # The Type allows the mapping of names to index. -# The Type of an object describes the memory layout of the object. In a c analogy, it is the -# information defined in a struct. +# The Type of an object describes the memory layout of the object. In a c analogy, +# it is the information defined in a struct. # The Type is a list of the names of instance variables, and their value types (int etc). # # Every object has a Type to describe it, so it's *first* instance variable is **always** @@ -21,14 +22,13 @@ module Parfait # But Objects must also be able to carry methods themselves (ruby calls singleton_methods) # and those too are stored in the Type (both type and class include behaviour) -# The object is an List of values of length n - -# The Type is a list of n names and n types that describe the values stored in an actual object. - +# The object is an "List" (memory location) of values of length n +# The Type is a list of n names and n types that describe the values stored in an +# actual object. # Together they turn the object into a hash like structure -# For types to be a useful concept, they have to be unique and immutable. Any "change", like adding -# a name/type pair, will result in a new instance. +# For types to be a useful concept, they have to be unique and immutable. Any "change", +# like adding a name/type pair, will result in a new type instance. # The Type class carries a hash of types of the systems, which is used to ensure that # there is only one instance of every type. Hash and equality are defined on type diff --git a/lib/parfait/vool_method.rb b/lib/parfait/vool_method.rb index 8059b6fa..788bfa17 100644 --- a/lib/parfait/vool_method.rb +++ b/lib/parfait/vool_method.rb @@ -5,7 +5,7 @@ module Parfait # Type objects are already created for args and locals, but the main attribute # is the source, which is a Vool::Statement # - # Classes store VoolMethods, while Types store CallableMethod + # Classes store VoolMethods, while Types store Risc::CallableMethod # A Type referes to a Class , but a Class (interface) is implemented by many types # as it changes during the course of it's life. Types do not change. Objects have # type, and so only indirectly a class. diff --git a/lib/risc/builder.rb b/lib/risc/builder.rb index aae83391..f75fb9fc 100644 --- a/lib/risc/builder.rb +++ b/lib/risc/builder.rb @@ -4,6 +4,12 @@ module Risc # # The code is added to the method_compiler. # + # Basically this allows to many Risc instructions with extremely readable code. + # example: + # space << Parfait.object_space # load constant + # message[:receiver] << space #make current message's (r0) receiver the space + # See http://ruby-x.org/rubyx/builder.html for details + # class Builder attr_reader :built , :compiler diff --git a/lib/risc/method_compiler.rb b/lib/risc/method_compiler.rb index 4a986759..3037700f 100644 --- a/lib/risc/method_compiler.rb +++ b/lib/risc/method_compiler.rb @@ -1,8 +1,7 @@ module Risc - # MethodCompiler (old name) is used to generate risc instructions for methods - # and to instantiate the methods correctly. Most of the init is typed layer stuff, - # but there is some logic too. + # MethodCompiler is used to generate risc instructions for methods + # and to instantiate the methods correctly. class MethodCompiler < CallableCompiler diff --git a/lib/vool/class_method_statement.rb b/lib/vool/class_method_statement.rb index 916d6989..076dacd4 100644 --- a/lib/vool/class_method_statement.rb +++ b/lib/vool/class_method_statement.rb @@ -11,6 +11,7 @@ module Vool raise "not meta" unless clazz.class == Parfait::MetaClass raise( "no class in #{self}") unless clazz method = clazz.add_method_for(name , make_arg_type , make_frame , body ) +#VoolMethod compiler = method.compiler_for(clazz.instance_type) each {|node| raise "Blocks not implemented" if node.is_a?(BlockStatement)} compiler diff --git a/lib/vool/class_statement.rb b/lib/vool/class_statement.rb index 2b768840..254c2fdc 100644 --- a/lib/vool/class_statement.rb +++ b/lib/vool/class_statement.rb @@ -29,7 +29,7 @@ module Vool raise "Only methods for now #{node.class}:#{node}" end end - Mom::MomCompiler.new(method_compilers) + Mom::MomCollection.new(method_compilers) end def each(&block) diff --git a/test/mom/test_block_compiler.rb b/test/mom/test_block_compiler.rb new file mode 100644 index 00000000..579b03d9 --- /dev/null +++ b/test/mom/test_block_compiler.rb @@ -0,0 +1,85 @@ +require_relative "../helper" + +module Mom + class TestBlockCompiler < MiniTest::Test + include MomCompile + + def setup + Parfait.boot!(Parfait.default_test_options) + @ins = compile_first_block( "local = 5") + end + + def test_block_compiles + assert_equal Mom::SlotLoad , @ins.class , @ins + end + def test_slot_is_set + assert @ins.left + end + def test_slot_starts_at_message + assert_equal :message , @ins.left.known_object + end + def test_slots_left + assert_equal [:frame , :local] , @ins.left.slots + end + def test_slot_assigns_something + assert @ins.right + end + def test_slot_assigns_int + assert_equal Mom::IntegerConstant , @ins.right.known_object.class + end + end + + class TestAssignMomInstanceToLocal < MiniTest::Test + include MomCompile + def setup + Parfait.boot!(Parfait.default_test_options) + @ins = compile_first_block( "local = @a" , "@a = 5") #second arg in method scope + end + def test_class_compiles + assert_equal Mom::SlotLoad , @ins.class , @ins + end + def test_slots_left + assert_equal [:frame, :local] , @ins.left.slots + end + def test_slots_right + assert_equal [:receiver, :a] , @ins.right.slots + end + end + + class TestAssignToArg < MiniTest::Test + include MomCompile + + def setup + Parfait.boot!(Parfait.default_test_options) + @ins = compile_first_block( "arg = 5") + end + + def test_class_compiles + assert_equal Mom::SlotLoad , @ins.class , @ins + end + def test_slot_is_set + assert @ins.left + end + def test_slots_left + assert_equal [:caller,:caller, :arguments, :arg] , @ins.left.slots + end + end + + class TestAssignMomToInstance < MiniTest::Test + include MomCompile + def setup + Parfait.boot!(Parfait.default_test_options) + end + def test_assigns_const + @ins = compile_first_block( "@a = 5") + assert_equal Mom::SlotLoad , @ins.class , @ins + assert_equal Mom::IntegerConstant , @ins.right.known_object.class , @ins + end + def test_assigns_move + @ins = compile_first_block( "@a = arg") + assert_equal Mom::SlotLoad , @ins.class , @ins + assert_equal Mom::SlotDefinition , @ins.right.class , @ins + end + end + +end diff --git a/test/mom/test_method_compiler.rb b/test/mom/test_method_compiler.rb new file mode 100644 index 00000000..f229c26c --- /dev/null +++ b/test/mom/test_method_compiler.rb @@ -0,0 +1,40 @@ +require_relative "helper" + +module Mom + class TestMethodCompiler < MiniTest::Test + include MomCompile + + def setup + Parfait.boot!(Parfait.default_test_options) + @comp = compile_mom( "class Test ; def main(); return 'Hi'; end; end;") + end + + def test_class + assert_equal MomCompiler , @comp.class + end + def test_compilers + assert_equal 23 , @comp.compilers.length + end + def test_boot_compilers + assert_equal 22 , @comp.boot_compilers.length + end + def test_compilers_bare + assert_equal 22 , MomCompiler.new.compilers.length + end + def test_returns_constants + assert_equal Array , @comp.constants.class + end + def test_has_constant + assert_equal "Hi" , @comp.constants[1].to_string + end + def test_has_translate + assert @comp.translate(:interpreter) + end + def test_append_class + assert_equal MomCompiler, (@comp.append @comp).class + end + def test_append_length + assert_equal 2 , @comp.append(@comp).method_compilers.length + end + end +end diff --git a/test/mom/test_mom_collection.rb b/test/mom/test_mom_collection.rb new file mode 100644 index 00000000..913c466c --- /dev/null +++ b/test/mom/test_mom_collection.rb @@ -0,0 +1,40 @@ +require_relative "helper" + +module Mom + class TestMomCollection < MiniTest::Test + include MomCompile + + def setup + Parfait.boot!(Parfait.default_test_options) + @comp = compile_mom( "class Test ; def main(); return 'Hi'; end; end;") + end + + def test_class + assert_equal MomCompiler , @comp.class + end + def test_compilers + assert_equal 23 , @comp.compilers.length + end + def test_boot_compilers + assert_equal 22 , @comp.boot_compilers.length + end + def test_compilers_bare + assert_equal 22 , MomCompiler.new.compilers.length + end + def test_returns_constants + assert_equal Array , @comp.constants.class + end + def test_has_constant + assert_equal "Hi" , @comp.constants[1].to_string + end + def test_has_translate + assert @comp.translate(:interpreter) + end + def test_append_class + assert_equal MomCompiler, (@comp.append @comp).class + end + def test_append_length + assert_equal 2 , @comp.append(@comp).method_compilers.length + end + end +end diff --git a/test/support/compiling.rb b/test/support/compiling.rb index 171724f6..7cc17dfe 100644 --- a/test/support/compiling.rb +++ b/test/support/compiling.rb @@ -17,6 +17,26 @@ module ScopeHelper in_Test("def main(arg) ; #{statements}; end") end end +module VoolCompile + include ScopeHelper + + def compile_method(input) + statements = RubyX::RubyXCompiler.new(RubyX.default_test_options).ruby_to_mom(input) + assert statements.is_a?(Mom::MomCollection) + ret = statements.to_mom(nil) + assert_equal Parfait::Class , statements.clazz.class , statements + @method = statements.clazz.get_method(:main) + assert_equal Parfait::VoolMethod , @method.class + ret + end + def compile_first_method( input ) + ret = compile_method( as_test_main( input )) + assert_equal Mom::MomCompiler , ret.class + compiler = ret.method_compilers.find{|c| c.get_method.name == :main and c.get_method.self_type.object_class.name == :Test} + assert_equal Risc::MethodCompiler , compiler.class + @method.source.to_mom( compiler ) + end +end module MomCompile include ScopeHelper @@ -78,27 +98,3 @@ module MomCompile end end - - - - -class Ignored - def == other - return false unless other.class == self.class - Sof::Util.attributes(self).each do |a| - begin - left = send(a) - rescue NoMethodError - next # not using instance variables that are not defined as attr_readers for equality - end - begin - right = other.send(a) - rescue NoMethodError - return false - end - return false unless left.class == right.class - return false unless left == right - end - return true - end -end diff --git a/test/vool/test_class_statement.rb b/test/vool/test_class_statement.rb new file mode 100644 index 00000000..7d619daa --- /dev/null +++ b/test/vool/test_class_statement.rb @@ -0,0 +1,29 @@ + +require_relative "helper" + +module Vool + class TestClassStatement < MiniTest::Test + include VoolCompile + + def setup + @ins = compile_first_method( "if(@a) ; @a = 5 ; else; @a = 6 ; end") + end + + def test_condition_compiles_to_check + assert_equal TruthCheck , @ins.class , @ins + end + def test_condition_is_slot + assert_equal SlotDefinition , @ins.condition.class , @ins + end + def test_label_after_check + assert_equal Label , @ins.next.class , @ins + end + def test_label_last + assert_equal Label , @ins.last.class , @ins + end + def test_array + check_array [TruthCheck, Label, SlotLoad, Jump, Label, SlotLoad , + Label] , @ins + end + end +end