diff --git a/lib/rubyx/compile.rb b/lib/rubyx/compile.rb new file mode 100644 index 00000000..efcfb384 --- /dev/null +++ b/lib/rubyx/compile.rb @@ -0,0 +1,39 @@ +class RubyXC < Thor + desc "compile FILE" , "Compile given FILE to binary" + long_desc <<-LONGDESC + Compile the give file name to binary object file (see below.) + + Output will be elf object file of the same name, with .o, in root directory. + + Note: Because of Bug #13, you need to run "ld -N file.o" on the file, before + executing it. This can be done on a mac by installing a cross linker + (brew install arm-linux-gnueabihf-binutils), or on the target arm machine. + LONGDESC + + def compile(file) + linker = do_compile(file) + elf = { debug: options[:elf] || false } + writer = Elf::ObjectWriter.new(linker , elf) + + outfile = file.split("/").last.gsub(".rb" , ".o") + writer.save outfile + system "arm-linux-gnu-ld -N #{outfile}" + File.delete outfile + return outfile + end + + private + # Open file, create compiler, compile and return linker + def do_compile(file) + begin + ruby = File.read(file) + rescue + fail MalformattedArgumentError , "No such file #{file}" + end + puts "compiling #{file}" + code = get_preload + ruby + puts "Code= #{code}" + RubyX::RubyXCompiler.new(extract_options).ruby_to_binary( code , :arm ) + end + +end diff --git a/lib/rubyx/execute.rb b/lib/rubyx/execute.rb new file mode 100644 index 00000000..609b3b86 --- /dev/null +++ b/lib/rubyx/execute.rb @@ -0,0 +1,27 @@ +class RubyXC < Thor + + desc "execute FILE" , "Compile given FILE and execute resulting binary" + long_desc <<-LONGDESC + Just like the compile task, this compiles the file to an object/binary file. + + Then rubyxc will link and run the resulting object file. For this to work, + qemu needs to be set up correctly on the system. Specifically, because of + bug #13, arm-linux-gnueabihf-ld needs to exist (it's part of the cross compiled + arm binutils). + + The resulting a.out will be run via qemu-arm. This is part of the qemu "linux" package + and interprets the arm binary on the host, assuming a linux os. + + This whole approach should only be used for preliminary checking that no core-dumps + are generated by the program, or when no benchmarking (as the times will be whatever). + + For simple functional test though, it is a much much quicker way to run the binary + than transferring it to another machine. The a.out is left in place to be run again. + LONGDESC + + def execute(file) + outfile = compile(file) + puts "Running #{outfile}\n\n" + system "qemu-arm ./a.out ;echo $?" + end +end diff --git a/lib/rubyx/interpret.rb b/lib/rubyx/interpret.rb new file mode 100644 index 00000000..e0134fe4 --- /dev/null +++ b/lib/rubyx/interpret.rb @@ -0,0 +1,37 @@ +class RubyXC < Thor + + desc "interpret FILE" , "Interpret given FILE " + long_desc <<-LONGDESC + Compiles the given file to an intermediate RISC format, and runs the + Interpreter. + + RISC is the last abstract layer inside the compiler. It is in nature + very close to arm (without quirks and much smaller). + + An interpreter was originally developed for the RISC layer for debugging purposes. + Running the interpreter is about 50k slower than binary, but it can be used + to veryfy simple programs. + + No output file will be generated, the only output is generated by the + given program. + + The program must define a main method on the Space class, which will be invoked. + LONGDESC + + def interpret(file) + begin + ruby = File.read(file) + rescue + fail MalformattedArgumentError , "No such file #{file}" + end + compiler = RubyX::RubyXCompiler.new(extract_options) + linker = compiler.ruby_to_binary(ruby, :interpreter) + + puts "interpreting #{file}" + + interpreter = Risc::Interpreter.new(linker , STDOUT ) + interpreter.start_program + interpreter.tick while(interpreter.instruction) + + end +end diff --git a/lib/rubyx/rubyxc.rb b/lib/rubyx/rubyxc.rb index 8bf5fd9b..9300c7c8 100644 --- a/lib/rubyx/rubyxc.rb +++ b/lib/rubyx/rubyxc.rb @@ -6,133 +6,23 @@ class RubyXC < Thor class_option :integers , type: :numeric class_option :mesages , type: :numeric class_option :elf , type: :boolean + class_option :preload , type: :boolean - desc "stats FILE" , "Give object statistics on compiled FILE" - long_desc <<-LONGDESC - Compile the give file name and print statistics on the binary. - A Binary file is in essence an ObjectSpace, ie a collection of objects. - - This command tells you how many objects of each kind, the amount of bytes - objects of that class take, and the total amount of bytes taken. - - Together with various options this may be used to tune the executable, or just fyi. - LONGDESC - def stats(file) - compile(file) - by_class = Hash.new(0) - Risc::Position.positions.each do |object , _ | - by_class[object.class] += 1 if Risc::Position.is_object(object) - end - obj, total = 0 , 0 - by_class.each do |clazz , num| - dis = (clazz == Symbol) ? 8 : clazz.memory_size - puts clazz.name.split("::").last + " == #{num} / #{num*dis}" - obj += num - total += num * dis - end - puts "\nTotal Objects=#{obj} Words=#{total}" - end - - desc "compile FILE" , "Compile given FILE to binary" - long_desc <<-LONGDESC - Compile the give file name to binary object file (see below.) - - Output will be elf object file of the same name, with .o, in root directory. - - Note: Because of Bug #13, you need to run "ld -N file.o" on the file, before - executing it. This can be done on a mac by installing a cross linker - (brew install arm-linux-gnueabihf-binutils), or on the target arm machine. - LONGDESC - - def compile(file) - linker = do_compile(file) - elf = { debug: options[:elf] || false } - writer = Elf::ObjectWriter.new(linker , elf) - - outfile = file.split("/").last.gsub(".rb" , ".o") - writer.save outfile - system "arm-linux-gnu-ld -N #{outfile}" - File.delete outfile - return outfile - end - - - desc "interpret FILE" , "Interpret given FILE " - long_desc <<-LONGDESC - Compiles the given file to an intermediate RISC format, and runs the - Interpreter. - - RISC is the last abstract layer inside the compiler. It is in nature - very close to arm (without quirks and much smaller). - - An interpreter was originally developed for the RISC layer for debugging purposes. - Running the interpreter is about 50k slower than binary, but it can be used - to veryfy simple programs. - - No output file will be generated, the only output is generated by the - given program. - - The program must define a main method on the Space class, which will be invoked. - LONGDESC - - def interpret(file) - begin - ruby = File.read(file) - rescue - fail MalformattedArgumentError , "No such file #{file}" - end - compiler = RubyX::RubyXCompiler.new(extract_options) - linker = compiler.ruby_to_binary(ruby, :interpreter) - - puts "interpreting #{file}" - - interpreter = Risc::Interpreter.new(linker , STDOUT ) - interpreter.start_program - interpreter.tick while(interpreter.instruction) - - end - - desc "execute FILE" , "Compile given FILE and execute resulting binary" - long_desc <<-LONGDESC - Just like the compile task, this compiles the file to an object/binary file. - - Then rubyxc will link and run the resulting object file. For this to work, - qemu needs to be set up correctly on the system. Specifically, because of - bug #13, arm-linux-gnueabihf-ld needs to exist (it's part of the cross compiled - arm binutils). - - The resulting a.out will be run via qemu-arm. This is part of the qemu "linux" package - and interprets the arm binary on the host, assuming a linux os. - - This whole approach should only be used for preliminary checking that no core-dumps - are generated by the program, or when no benchmarking (as the times will be whatever). - - For simple functional test though, it is a much much quicker way to run the binary - than transferring it to another machine. The a.out is left in place to be run again. - LONGDESC - - def execute(file) - outfile = compile(file) - puts "Running #{outfile}\n\n" - system "qemu-arm ./a.out ;echo $?" - end - + # + # Actual commands are required at the end, one per file, same name as command + # private - # Open file, create compiler, compile and return linker - def do_compile(file) - begin - ruby = File.read(file) - rescue - fail MalformattedArgumentError , "No such file #{file}" - end - puts "compiling #{file}" - ::RubyX::RubyXCompiler.new(extract_options).ruby_to_binary( ruby , :arm ) - end - def extract_options opt = { Integer: options[:integers] || 1024 , Message: options[:messages] || 1024} return {parfait: opt } end - + def get_preload + options[:preload] ? Vool::Builtin.builtin_code : "" + end end + +require_relative "stats" +require_relative "compile" +require_relative "interpret" +require_relative "execute" diff --git a/lib/rubyx/stats.rb b/lib/rubyx/stats.rb new file mode 100644 index 00000000..70e40f2c --- /dev/null +++ b/lib/rubyx/stats.rb @@ -0,0 +1,28 @@ +class RubyXC < Thor + + desc "stats FILE" , "Give object statistics on compiled FILE" + long_desc <<-LONGDESC + Compile the give file name and print statistics on the binary. + A Binary file is in essence an ObjectSpace, ie a collection of objects. + + This command tells you how many objects of each kind, the amount of bytes + objects of that class take, and the total amount of bytes taken. + + Together with various options this may be used to tune the executable, or just fyi. + LONGDESC + def stats(file) + compile(file) + by_class = Hash.new(0) + Risc::Position.positions.each do |object , _ | + by_class[object.class] += 1 if Risc::Position.is_object(object) + end + obj, total = 0 , 0 + by_class.each do |clazz , num| + dis = (clazz == Symbol) ? 8 : clazz.memory_size + puts clazz.name.split("::").last + " == #{num} / #{num*dis}" + obj += num + total += num * dis + end + puts "\nTotal Objects=#{obj} Words=#{total}" + end +end diff --git a/lib/vool/builtin.rb b/lib/vool/builtin.rb index ee1a44cf..efda20c7 100644 --- a/lib/vool/builtin.rb +++ b/lib/vool/builtin.rb @@ -1,13 +1,44 @@ module Vool - module Macro + module Builtin def self.boot_methods(options) return if options[:boot_methods] == false - load_builtin( :int_plus ) + load_builtin( "Integer.plus" ) end - def self.load_builtin(name) - fname = "./builtin/#{name}.rb" - File.read File.expand_path(fname, File.dirname(__FILE__)) + def self.load_builtin(loads) + clazz , meth = loads.split(".") + "class #{clazz}; #{Vool::Builtin.builtin[loads]};end;" + end + + def self.builtin + { + "Object.get" => "def get_internal_word(at); X.get_internal_word;end", + "Object.missing" => "def method_missing(at); X.method_missing;end", + "Object.init" => "def __init__(at); X.init;end", + "Object.exit" => "def exit; X.exit;end", + "Integer.div4" => "def div4; X.div4;end", + "Integer.div10" => "def div10; X.div10;end", + "Integer.gt" => "def >; X.comparison(:>);end", + "Integer.lt" => "def <; X.comparison(:<);end", + "Integer.ge" => "def >=; X.comparison(:>=);end", + "Integer.le" => "def <=; X.comparison(:<=);end", + "Integer.plus" => "def +; X.int_operator(:+);end", + "Integer.minus" => "def -; X.int_operator(:-);end", + "Integer.mul" => "def *; X.int_operator(:*);end", + "Integer.and" => "def &; X.int_operator(:&);end", + "Integer.or" => "def |; X.int_operator(:|);end", + "Integer.ls" => "def <<; X.int_operator(:<<);end", + "Integer.rs" => "def >>; X.int_operator(:>>);end", + "Word.put" => "def putstring(at); X.putstring;end", + "Word.set" => "def set_internal_byte(at, val); X.set_internal_byte;end", + "Word.get" => "def get_internal_byte(at); X.get_internal_byte;end", + "Space.main" => "def main(args);return;end", + } + end + def self.builtin_code + keys = builtin.keys + keys.pop + keys.collect { |loads| load_builtin(loads)}.join end end end diff --git a/test/support/preloader.rb b/test/support/preloader.rb index c25c56b6..08a25412 100644 --- a/test/support/preloader.rb +++ b/test/support/preloader.rb @@ -1,36 +1,11 @@ module Preloader - def builtin - { - "Object.get" => "def get_internal_word(at); X.get_internal_word;end", - "Object.missing" => "def method_missing(at); X.method_missing;end", - "Object.init" => "def __init__(at); X.init;end", - "Object.exit" => "def exit; X.exit;end", - "Integer.div4" => "def div4; X.div4;end", - "Integer.div10" => "def div10; X.div10;end", - "Integer.gt" => "def >; X.comparison(:>);end", - "Integer.lt" => "def <; X.comparison(:<);end", - "Integer.ge" => "def >=; X.comparison(:>=);end", - "Integer.le" => "def <=; X.comparison(:<=);end", - "Integer.plus" => "def +; X.int_operator(:+);end", - "Integer.minus" => "def -; X.int_operator(:-);end", - "Integer.mul" => "def *; X.int_operator(:*);end", - "Integer.and" => "def &; X.int_operator(:&);end", - "Integer.or" => "def |; X.int_operator(:|);end", - "Integer.ls" => "def <<; X.int_operator(:<<);end", - "Integer.rs" => "def >>; X.int_operator(:>>);end", - "Word.put" => "def putstring(at); X.putstring;end", - "Word.set" => "def set_internal_byte(at, val); X.set_internal_byte;end", - "Word.get" => "def get_internal_byte(at); X.get_internal_byte;end", - "Space.main" => "def main(args);return;end", - } - end def get_preload(preload) return "" unless preload - preload = builtin.keys.join(";") if(preload == "all" ) + preload = Vool::Builtin.builtin.keys.join(";") if(preload == "all" ) preload.split(";").collect do |loads| - raise "no preload #{loads}" unless builtin[loads] + raise "no preload #{loads}" unless Vool::Builtin.builtin[loads] clazz , meth = loads.split(".") - "class #{clazz}; #{builtin[loads]};end;" + "class #{clazz}; #{Vool::Builtin.builtin[loads]};end;" end.join end def preload