require "thor" require "rubyx" require "risc/interpreter" class RubyXC < Thor class_option :integers , type: :numeric class_option :mesages , type: :numeric class_option :elf , 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 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 end