diff --git a/Rakefile b/Rakefile index 2d97dc4..4b0fee6 100644 --- a/Rakefile +++ b/Rakefile @@ -8,7 +8,7 @@ require "steep/cli" task default: %i[test] Rake::TestTask.new do |t| - t.test_files = FileList['test/test*.rb'] + t.test_files = FileList['test/test*.rb', 'test/**/test*.rb'] end namespace :steep do diff --git a/exe/vaporware b/exe/vaporware index a87b053..821c288 100755 --- a/exe/vaporware +++ b/exe/vaporware @@ -4,11 +4,13 @@ require "vaporware" require "optparse" opt = OptionParser.new options = {} -opt.on("-c", "--compiler [VAL]", "this option is selecting compiler precompiled file, default: gcc") { |v| options[:compiler] = v } +opt.on("-c", "--compiler[=VAL]", "this option is selecting compiler precompiled file, default: \"self\"") { |v| options[:compiler] = v } +opt.on("-a", "--assembler[=VAL]", "this option is selecting assembler assembler file, default: \"as\"") { |v| options[:assembler] = v } opt.on("-D", "--debug") { |v| options[:debug] = v } -opt.on("-o", "--objects [VAL]") { |v| options[:dest] = v } +opt.on("-o", "--objects[=VAL]") { |v| options[:dest] = v } opt.on("--compiler-options[=VAL]", "compiler options") { |v| options[:compiler_options] = v.split(",") } -opt.on("-s", "--shared") { |v| options[:shared] = v } +opt.on("-s", "--shared-library") { |v| options[:shared] = v } +opt.on("-l", "--linker[=VAL]", "selecting linker: gold, lld, and mold, default: \"gold\".") { |v| options[:linker] = v } begin opt.parse!(ARGV) diff --git a/lib/vaporware/compiler.rb b/lib/vaporware/compiler.rb index 96f6493..5ee940a 100644 --- a/lib/vaporware/compiler.rb +++ b/lib/vaporware/compiler.rb @@ -1,46 +1,26 @@ # frozen_string_literal: true require_relative "compiler/generator" +require_relative "compiler/assembler" +require_relative "compiler/linker" -module Vaporware - # Your code goes here... - class Compiler - def self.compile(source, compiler: "gcc", dest: "tmp", debug: false, compiler_options: ["-O0"], shared: false) - _precompile = "#{dest}.s" - s = new(source, _precompile: _precompile, debug:, shared:) - s.compile(compiler:, compiler_options:) - end +class Vaporware::Compiler + attr_reader *%i(generator assembler linker) - def initialize(source, _precompile: "tmp.s", debug: false, shared: false) - @generator = Vaporware::Compiler::Generator.new(source, precompile: _precompile, debug:, shared:) - end - - def compile(compiler: "gcc", compiler_options: ["-O0"]) - @generator.register_var_and_method(@generator.ast) + def self.compile(source, assembler: "as", linker: "ld", dest: "tmp", debug: false, compiler_options: ["-O0"], shared: false) + compiler = new(input: source, output: dest, debug:, shared:, linker:, assembler:) + compiler.compile(compiler_options:) + compiler.assemble(input: dest.to_s + ".s", assembler:, debug:) + compiler.link + end - output = File.open(@generator.precompile, "w") - # prologue - output.puts ".intel_syntax noprefix" - if @generator.defined_methods.empty? - @generator.main = true - output.puts ".globl main" - output.puts "main:" - output.puts " push rbp" - output.puts " mov rbp, rsp" - output.puts " sub rsp, #{@generator.defined_variables.size * 8}" - @generator.to_asm(@generator.ast, output) - # epilogue - @generator.epilogue(output) - else - @generator.prologue_methods(output) - output.puts ".globl main" unless @generator.shared - @generator.to_asm(@generator.ast, output) - # epilogue - @generator.epilogue(output) - end - output.close - compiler_options += @generator.compile_shared_option if @generator.shared - @generator.call_compiler(compiler:, compiler_options:) - end + def initialize(input:, output: File.basename(input, ".*"), linker: "ld", assembler: "as", debug: false, shared: false) + @generator = Vaporware::Compiler::Generator.new(input:, output: output + ".s", debug:, shared:) + @assembler = Vaporware::Compiler::Assembler.new(input: @generator.precompile, output: output + ".o", assembler:, debug:) + @linker = Vaporware::Compiler::Linker.new(input: @assembler.obj_file, output:, linker:, debug:, shared:) end + + def assemble(input:, output: File.basename(input, ".*") + ".o", assembler: "as", assembler_options: [], debug: false) = @assembler.assemble(input:, output:, assembler:, assembler_options:, debug:) + def link = @linker.link + def compile(compiler_options: ["-O0"]) = @generator.compile end diff --git a/lib/vaporware/compiler/assembler.rb b/lib/vaporware/compiler/assembler.rb new file mode 100644 index 0000000..ceba424 --- /dev/null +++ b/lib/vaporware/compiler/assembler.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true +require_relative "assembler/elf" +require_relative "assembler/elf/utils" +require_relative "assembler/elf/header" +require_relative "assembler/elf/sections" +require_relative "assembler/elf/section_header" + +class Vaporware::Compiler::Assembler + def self.assemble!(input, output = File.basename(input, ".*") + ".o") = new(input:, output:).assemble + + def initialize(input:, output: File.basename(input, ".*") + ".o", assembler: "as", type: :relocatable, debug: false) + @input, @output = input, output + @elf = ELF.new(type:, input:, output:, debug:) + @assembler = assembler + @debug = debug + end + + def assemble(assembler: @assembler, assembler_options: [], input: @input, output: @output, debug: false) + if ["gcc", "as"].include?(assembler) + IO.popen([assembler, *assembler_options, "-o", output, input].join(" ")).close + else + to_elf(input:, output:, debug:) + end + output + end + def obj_file = @output + def to_elf(input: @input, output: @output, debug: false) = @elf.build(input:, output:, debug:) +end diff --git a/lib/vaporware/compiler/assembler/elf.rb b/lib/vaporware/compiler/assembler/elf.rb new file mode 100644 index 0000000..c0d42d3 --- /dev/null +++ b/lib/vaporware/compiler/assembler/elf.rb @@ -0,0 +1,77 @@ +class Vaporware::Compiler::Assembler + class ELF + class Error < StandardError; end + class Section; end + class SectionHeader; end + module Utils; end + + def initialize(type:, input:, output:, debug:) + @input, @output = input, output + @header = Header.new(type:) + @sections = Sections.new + end + + def build(input: @input, output: @output, debug: false) + program_size = 0 + read!(input:) + init_assemble! + + offset = 0x40 + section_headers = [] + names = [] + bodies = { + null: nil, + text: nil, + data: nil, + bss: nil, + note: nil, + symtab: nil, + strtab: nil, + shstrtab: nil, + } + name_idx = 0 + padding = nil + @sections.each do |section| + name = section.name + names << name + section.body.set!(name: names.join) if name == "\0.shstrtab" + bin = section.body.build + size = bin.bytesize + bin << "\0" until (bin.bytesize % 8) == 0 if ["\0.text", "\0.shstrtab"].include?(name) + bin << "\0" until ((bin.bytesize + offset) % 8) == 0 if ["\0.shstrtab"].include?(name) + bodies[section.section_name.to_sym] = bin + header = section.header + if offset > 0x40 && size > 0 && padding&.>(0) + offset += padding + padding = nil + end + padding = bin.size - size if size > 0 + header.set!(name: name_idx, offset:, size:) unless name == "" + offset += size + section_headers << header.build + name_idx += name == "" ? 1 : name.size + end + @header.set!(shoffset: offset + padding) + w = File.open(output, "wb") + w.write([@header.build, *bodies.values, *section_headers].join) + w.close + [@header.build, *bodies.values, *section_headers] + end + + private + def init_assemble! = (note!; symtab!) + def read!(input: @input, text: @sections.text.body) + read = { main: false } + File.open(input, "r") do |r| + r.each_line do |line| + read[:main] = line.match(/main:/) unless read[:main] + next unless read[:main] && !/main:/.match(line) + next if /\.L.+:/.match(line) + text.assemble!(line) + end + end + end + def note! = @sections.note.body.null! + def symtab! = @sections.symtab.body.set!(entsize: 0x18, name: 1, info: 0x10, other: 0, shndx: 1) + end +end diff --git a/lib/vaporware/compiler/assembler/elf/header.rb b/lib/vaporware/compiler/assembler/elf/header.rb new file mode 100644 index 0000000..ee2756c --- /dev/null +++ b/lib/vaporware/compiler/assembler/elf/header.rb @@ -0,0 +1,62 @@ +require_relative "../elf" + +class Vaporware::Compiler::Assembler::ELF::Header + include Vaporware::Compiler::Assembler::ELF::Utils + IDENT = [0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00].freeze + ELF_FILE_TYPE = { NONE: 0, REL: 1, EXEC: 2, DYN: 3, CORE: 4 }.freeze + + def initialize(endian: :littel, type: :rel, arc: :amd64) + @ident = IDENT + @type = num2bytes(ELF_FILE_TYPE[elf(type)], 2) + @arch = arch(arc) + @version = num2bytes(1, 4) + @entry = num2bytes(0x00, 8) + @phoffset = num2bytes(0x00, 8) + @shoffset = num2bytes(0x00, 8) + @flags = num2bytes(0x00, 4) + @ehsize = num2bytes(0x40, 2) + @phsize = num2bytes(0x00, 2) + @phnum = num2bytes(0x00, 2) + @shentsize = num2bytes(0x40, 2) + @shnum = num2bytes(0x08, 2) + @shstrndx = num2bytes(0x07, 2) + end + + def build = bytes.flatten.pack("C*") + + def set!(entry: nil, phoffset: nil, shoffset: nil, shnum: nil, shstrndx: nil) + @entry = num2bytes(entry, 8) if check(entry, 8) + @phoffset = num2bytes(phoffset, 8) if check(phoffset, 8) + @shoffset = num2bytes(shoffset, 8) if check(shoffset, 8) + @shnum = num2bytes(shnum, 4) if check(shnum, 4) + @shstrndx = num2bytes(shstrndx, 4) if check(shstrndx, 4) + end + + private + + def bytes = [ + @ident, @type, @arch, @version, @entry, @phoffset, + @shoffset, @flags, @ehsize, @phsize, @phnum, @shentsize, + @shnum, @shstrndx + ] + + def arch(machine) + case machine.to_s + in "amd64" | "x86_64" | "x64" + [0x3e, 0x00] + end + end + + def elf(type) + case type.to_s + in "relocatable" | "rel" + :REL + in "exe" | "ex" | "exec" + :EXEC + in "shared" | "share" | "dynamic" | "dyn" + :DYN + else + :NONE + end + end +end diff --git a/lib/vaporware/compiler/assembler/elf/section.rb b/lib/vaporware/compiler/assembler/elf/section.rb new file mode 100644 index 0000000..57af7c3 --- /dev/null +++ b/lib/vaporware/compiler/assembler/elf/section.rb @@ -0,0 +1,25 @@ +require_relative "section/text" +require_relative "section/bss" +require_relative "section/data" +require_relative "section/note" +require_relative "section/null" +require_relative "section/symtab" +require_relative "section/strtab" +require_relative "section/shstrtab" +require_relative "section_header" + +class Vaporware::Compiler::Assembler::ELF::Section + attr_reader :header, :body, :name, :section_name + def initialize(type:, options: {}) + type_string = type.to_s.capitalize + type_string = type_string.upcase if type_string == "Bss" + @section_name = type_string.downcase + @name = section_name == "null" ? "" : "\0.#{section_name}" + @header = Vaporware::Compiler::Assembler::ELF::SectionHeader.new.send("#{@section_name}!") + @body = Module.const_get("Vaporware::Compiler::Assembler::ELF::Section::#{type_string}").new(**options) + end + + def name=(name) + @name = name + end +end diff --git a/lib/vaporware/compiler/assembler/elf/section/bss.rb b/lib/vaporware/compiler/assembler/elf/section/bss.rb new file mode 100644 index 0000000..cc30d58 --- /dev/null +++ b/lib/vaporware/compiler/assembler/elf/section/bss.rb @@ -0,0 +1,10 @@ +class Vaporware::Compiler::Assembler::ELF::Section::BSS + include Vaporware::Compiler::Assembler::ELF::Utils + def initialize(**opts) = nil + def build = bytes.flatten.pack("C*") + def set! = self + + private + def bytes = [] + def check(val, bytes) = false +end diff --git a/lib/vaporware/compiler/assembler/elf/section/data.rb b/lib/vaporware/compiler/assembler/elf/section/data.rb new file mode 100644 index 0000000..1b5b4c3 --- /dev/null +++ b/lib/vaporware/compiler/assembler/elf/section/data.rb @@ -0,0 +1,7 @@ +class Vaporware::Compiler::Assembler::ELF::Section::Data + include Vaporware::Compiler::Assembler::ELF::Utils + def initialize(**opts) = nil + def build = bytes.flatten.pack("C*") + def set! = self + private def bytes = [] +end diff --git a/lib/vaporware/compiler/assembler/elf/section/note.rb b/lib/vaporware/compiler/assembler/elf/section/note.rb new file mode 100644 index 0000000..f273ce1 --- /dev/null +++ b/lib/vaporware/compiler/assembler/elf/section/note.rb @@ -0,0 +1,33 @@ +class Vaporware::Compiler::Assembler::ELF::Section::Note + include Vaporware::Compiler::Assembler::ELF::Utils + + def self.gnu_property = new.gnu_property!.build + def self.null = new.null!.build + + def initialize(type: nil) + @nsize = nil + @dsize = nil + @type = nil + @name = nil + @desc = nil + gnu_property! if type == :gnu + end + + def set!(nsize: nil, dsize: nil, type: nil, name: nil, desc: nil) + @nsize = num2bytes(nsize, 4) if check(nsize, 4) + @dsize = num2bytes(dsize, 4) if check(dsize, 4) + @type = num2bytes(type, 4) if check(type, 4) + @name = name!(name) if name + @desc = desc!(desc) if desc + self + end + + def gnu_property! = set!(nsize: 0x04, dsize: 0x20, type: 0x05, name: "GNU", desc: %w(02 00 01 c0 04 00 00 00 00 00 00 00 00 00 00 00 01 00 01 c0 04 00 00 00 01 00 00 00 00 00 00 00).map { |val| val.to_i(16) }) + def null! = set!(nsize: 0, dsize: 0, type: 0, name: "NULL", desc: [0]) + + private + + def name!(name) = align(@name = name.bytes, 4) + def desc!(desc) = align(@desc = desc.is_a?(Array) ? desc : desc.bytes, 4) + def bytes = [@nsize, @dsize, @type, @name, @desc] +end diff --git a/lib/vaporware/compiler/assembler/elf/section/null.rb b/lib/vaporware/compiler/assembler/elf/section/null.rb new file mode 100644 index 0000000..3ff5c42 --- /dev/null +++ b/lib/vaporware/compiler/assembler/elf/section/null.rb @@ -0,0 +1,7 @@ +class Vaporware::Compiler::Assembler::ELF::Section::Null + include Vaporware::Compiler::Assembler::ELF::Utils + def initialize(**opts) = nil + def build = bytes.flatten.pack("C*") + def set! = self + private def bytes = [] +end diff --git a/lib/vaporware/compiler/assembler/elf/section/shstrtab.rb b/lib/vaporware/compiler/assembler/elf/section/shstrtab.rb new file mode 100644 index 0000000..e5ec246 --- /dev/null +++ b/lib/vaporware/compiler/assembler/elf/section/shstrtab.rb @@ -0,0 +1,22 @@ +class Vaporware::Compiler::Assembler::ELF::Section::Shstrtab + include Vaporware::Compiler::Assembler::ELF::Utils + def initialize(**opts) = @name = [] + def build = bytes.flatten.pack("C*") + def set!(name:) = (@name << name!(name); self) + + private + def bytes = [@name, [0]] + def name!(name) + case name + when String + (name.match(/\A\0\..+\z/) ? name : "\0.#{name}").bytes + when Array + raise Vaporware::Compiler::Assembler::ELF::Error, "unaccepted type in Array" unless name.all? { |elem| elem.is_a?(Integer) } + n = name + n.unshift(0) && n.push(0) unless n.first == 0 && n.last == 0 + n + else + raise Vaporware::Compiler::Assembler::ELF::Error, "unsupported type" + end + end +end diff --git a/lib/vaporware/compiler/assembler/elf/section/strtab.rb b/lib/vaporware/compiler/assembler/elf/section/strtab.rb new file mode 100644 index 0000000..eb57289 --- /dev/null +++ b/lib/vaporware/compiler/assembler/elf/section/strtab.rb @@ -0,0 +1,5 @@ +class Vaporware::Compiler::Assembler::ELF::Section::Strtab + include Vaporware::Compiler::Assembler::ELF::Utils + def initialize(names = "\0main\0", **opts) = @names = names + def build = @names.bytes.pack("C*") +end diff --git a/lib/vaporware/compiler/assembler/elf/section/symtab.rb b/lib/vaporware/compiler/assembler/elf/section/symtab.rb new file mode 100644 index 0000000..faf9b88 --- /dev/null +++ b/lib/vaporware/compiler/assembler/elf/section/symtab.rb @@ -0,0 +1,37 @@ +class Vaporware::Compiler::Assembler::ELF::Section::Symtab + include Vaporware::Compiler::Assembler::ELF::Utils + def initialize(**opts) + @entsize = [] + @name = num2bytes(0, 4) + @info = num2bytes(0, 1) + @other = num2bytes(0, 1) + @shndx = num2bytes(0, 2) + @value = num2bytes(0, 8) + @size = num2bytes(0, 8) + end + + def set!(entsize: nil, name: nil, info: nil, other: nil, shndx: nil, value: nil, size: nil) + @entsize = [0] * entsize unless entsize.nil? + @name = name2bytes(name, 4) if check(name, 4) + @info = num2bytes(info, 1) if check(info, 1) + @other = num2bytes(other, 1) if check(other, 1) + @shndx = num2bytes(shndx, 2) if check(shndx, 2) + @value = num2bytes(value, 8) if check(value, 8) + @size = num2bytes(size, 8) if check(size, 8) + end + + private + def bytes = [@entsize, @name, @info, @other, @shndx, @value, @size] + def name2bytes(name, bytes) + case name + when String + name.bytes.reverse + when Array + name[0..bytes] + when Integer + num2bytes(name, bytes) + else + [0] * bytes + end + end +end diff --git a/lib/vaporware/compiler/assembler/elf/section/text.rb b/lib/vaporware/compiler/assembler/elf/section/text.rb new file mode 100644 index 0000000..848e54c --- /dev/null +++ b/lib/vaporware/compiler/assembler/elf/section/text.rb @@ -0,0 +1,172 @@ +class Vaporware::Compiler::Assembler::ELF::Section::Text + PREFIX = { + REX_W: 0x48, + }.freeze + + REGISTER_CODE = { + RAX: 0, + RDI: 7, + }.freeze + + OPECODE = { + ADD: [0x01], + CMP: [0x39], + CQO: [0x99], + IDIV: [0xf7], + IMUL: [0x0f], + MOV: [0x89], + MOVR: [0x8B], + MOVXZ: [0x0f, 0xb7], + SUB: [0x83], + }.freeze + + def initialize(**opts) = @bytes = [] + + def assemble!(line) + op, *operands = line.split(/\s+/).reject { |o| o.empty? }.map { |op| op.gsub(/,/, "") } + @bytes << opecode(op, *operands) + end + + def build = @bytes.flatten.pack("C*") + def size = build.bytesize + def align(val, bytes) = (val << [0] until build.bytesize % bytes == 0) + + private + + def opecode(op, *operands) + case op + when "push" + push(operands) + when "mov", "movzb" + [PREFIX[:REX_W], *mov(op, operands)] + when "sub", "add", "imul", "cqo", "idiv" + [PREFIX[:REX_W], *calc(op, operands)] + when "pop" + pop(operands) + when "cmp" + [PREFIX[:REX_W], *cmp(op, operands)] + when "sete", "setl" + sete(op, operands) + when "je", "jmp" + jump(op, operands) + when "ret" + [0xc3] + else + raise Vaporware::Compiler::Assembler::ELF::Error, "yet implemented operations: #{op}" + end + end + + def jump(op, operands) + case op + when "je" + [0x74, *jaddr(operands)] + when "jmp" + [0xeb, *jaddr(operands)] + end + end + def jaddr(operands) + case operands + in [".Lend0"] + [0x08] + in [".Lelse0"] + [0x0a] + end + end + + def mov(op, operands) + reg = case operands + in ["rax", "rbp"] + [0xe8] + in ["rbp", "rsp"] + [0xe5] + in ["rsp", "rbp"] + [0xec] + in ["[rax]", "rdi"] + [0x38] + in ["rax", "al"] + op = "MOVXZ" + [0xc0] + in ["rax", "[rax]"] + op = "MOVR" + [0x00] + else + operands&.map { reg(_1) } + end # steep:ignore + [OPECODE[op.upcase.to_sym], reg].flatten + end + + def calc(op, operands) + ope_code = OPECODE[op.upcase.to_sym] + case [op, *operands] + in ["sub", "rax", "rdi"] + [0x29, 0xf8] + in ["add", "rax", "rdi"] + [ope_code, 0xf8] + in ["imul", "rax", "rdi"] + [ope_code, 0xaf, 0xc7] + in ["idiv", "rdi"] + [ope_code, 0xff] + in ["sub", "rsp", *num] + [ope_code, 0xec, *num.map { |n| n.to_i(16) }] + in ["sub", "rax", *num] + [ope_code, 0xe8, *num.map { |n| n.to_i(16) }] + in ["cqo"] + [0x99] + end # steep:ignore + end + + def cmp(op, operands) + case operands + in ["rax", "rdi"] + [0x39, 0xf8] + in ["rax", "0"] + [0x83, 0xf8, 0x00] + end + end + + def sete(op, operands) + case [op, operands] + in ["sete", ["al"]] + [0x0f, 0x94, 0xc0] + in ["setl", ["al"]] + [0x0f, 0x9c, 0xc0] + end + end + + def push(operands) + case operands + in ["rbp"] | ["rdi"] + [0x55] + in ["rax"] + [0x50] + else + [0x6a, *operands.map { reg(_1) }] + end # steep:ignore + end + + def pop(operands) + case operands + in ["rax"] | ["rdi"] + [0x58 + REGISTER_CODE[operands.first.upcase.to_sym]] + in ["rbp"] + [0x5d] + end # steep:ignore + end + + def reg(r) + case r + when "rsp" + 0xec + when "rbp" + 0x5e + when "rax" + 0x29 + when "rdi" + 0xf8 + when /\d+/ + ("%02x" % r).to_i(16) + else + raise Vaporware::Compiler::Assembler::ELF::Error, "yet implemented operand address: #{r}" + end + end +end diff --git a/lib/vaporware/compiler/assembler/elf/section_header.rb b/lib/vaporware/compiler/assembler/elf/section_header.rb new file mode 100644 index 0000000..6134ec9 --- /dev/null +++ b/lib/vaporware/compiler/assembler/elf/section_header.rb @@ -0,0 +1,44 @@ +class Vaporware::Compiler::Assembler::ELF::SectionHeader + include Vaporware::Compiler::Assembler::ELF::Utils + def initialize + @name = nil + @type = nil + @flags = nil + @addr = nil + @offset = nil + @size = nil + @link = nil + @info = nil + @addralign = nil + @entsize = nil + end + + def build = bytes.flatten.pack("C*") + + def set!(name: nil, type: nil, flags: nil, addr: nil, + offset: nil, size: nil, link: nil, info: nil, + addralign: nil, entsize: nil) + @name = num2bytes(name, 4) if check(name, 4) + @type = num2bytes(type, 4) if check(type, 4) + @flags = num2bytes(flags, 8) if check(flags, 8) + @addr = num2bytes(addr, 8) if check(addr, 8) + @offset = num2bytes(offset, 8) if check(offset, 8) + @size = num2bytes(size, 8) if check(size, 8) + @link = num2bytes(link, 4) if check(link, 4) + @info = num2bytes(info, 4) if check(info, 4) + @addralign = num2bytes(addralign, 8) if check(addralign, 8) + @entsize = num2bytes(entsize, 8) if check(entsize, 8) + self + end + + def null! = set!(name: 0, type: 0, flags: 0, addr: 0, offset: 0, size: 0, link: 0, info: 0, addralign: 0, entsize: 0) + def text! = set!(flags: 0x06, addralign: 0x01, addr: 0, type: 1, entsize: 0, link: 0, info: 0) + def data! = set!(type: 0x01, flags: 0x03, addralign: 1, addr: 0, info: 0, link: 0, entsize: 0) + def bss! = set!(type: 0x8, flags: 3, addralign: 1, addr: 0, info: 0, link: 0, entsize: 0) + def note! = set!(type: 0x07, flags: 0x02, size: 0x30, addralign: 0x08, addr: 0, link: 0, info: 0, entsize: 0) + def symtab! = set!(type: 2, info: 1, addr: 0, link: 6, entsize: 0x18, addralign: 8, flags: 0) + def strtab! = set!(type: 3, info: 0, addr: 0, link: 0, entsize: 0, addralign: 1, flags: 0) + def shstrtab! = set!(type: 3, info: 0, addr: 0, link: 0, entsize: 0, addralign: 1, flags: 0) + + private def bytes = [@name, @type, @flags, @addr, @offset, @size, @link, @info, @addralign, @entsize] +end diff --git a/lib/vaporware/compiler/assembler/elf/sections.rb b/lib/vaporware/compiler/assembler/elf/sections.rb new file mode 100644 index 0000000..d4d6962 --- /dev/null +++ b/lib/vaporware/compiler/assembler/elf/sections.rb @@ -0,0 +1,19 @@ +require_relative "section" + +class Vaporware::Compiler::Assembler::ELF::Sections + ATTRIBUTES = %i|null text data bss note symtab strtab shstrtab| + attr_reader *ATTRIBUTES + + def initialize + @null = Vaporware::Compiler::Assembler::ELF::Section.new(type: :null) + @text = Vaporware::Compiler::Assembler::ELF::Section.new(type: :text) + @data = Vaporware::Compiler::Assembler::ELF::Section.new(type: :data) + @bss = Vaporware::Compiler::Assembler::ELF::Section.new(type: :bss) + @note = Vaporware::Compiler::Assembler::ELF::Section.new(type: :note, options: {type: :gnu}) + @symtab = Vaporware::Compiler::Assembler::ELF::Section.new(type: :symtab) + @strtab = Vaporware::Compiler::Assembler::ELF::Section.new(type: :strtab) + @shstrtab = Vaporware::Compiler::Assembler::ELF::Section.new(type: :shstrtab) + end + + def each(&block) = ATTRIBUTES.each { |t| yield send(t) } +end diff --git a/lib/vaporware/compiler/assembler/elf/utils.rb b/lib/vaporware/compiler/assembler/elf/utils.rb new file mode 100644 index 0000000..f19f011 --- /dev/null +++ b/lib/vaporware/compiler/assembler/elf/utils.rb @@ -0,0 +1,24 @@ +module Vaporware::Compiler::Assembler::ELF::Utils + def build = (build_errors; bytes.flatten.pack("C*")) + def size = build.bytesize + def set! = (raise Vaporware::Compiler::Assembler::ELF::Error, "should be implementing #{self.class}") + def empties = must_be_filled_section_fields + + private + def align(val, bytes) + val << 0 until val.size % bytes == 0 + val + end + def bytes = (raise Vaporware::Compiler::Assembler::ELF::Error, "should be implementing #{self.class}") + def must_be_filled_section_fields = instance_variables.reject { |i| instance_variable_get(i) } + def num2bytes(val, bytes) = hexas(val, bytes).reverse + def check(val, bytes) = ((val.is_a?(Array) && val.all? { |v| v.is_a?(Integer) } && val.size == bytes) || (val.is_a?(Integer) && (hexas(val, bytes).size == bytes))) + def hexas(val, hex) = ("%0#{hex*2}x" % val).scan(/.{1,2}/).map { |v| v.to_i(16) }.then { |list| list.unshift(0) until list.size >= hex; list } + def build_errors + if bytes.any?(&:nil?) + errors = [] + bytes.each_with_index { |v, idx| errors << instance_variables[idx] if v.nil? } + raise Vaporware::Compiler::Assembler::ELF::Error, "unaccepted types: #{errors.join(",")}" + end + end +end diff --git a/lib/vaporware/compiler/generator.rb b/lib/vaporware/compiler/generator.rb index b0eb4eb..9c59e5f 100644 --- a/lib/vaporware/compiler/generator.rb +++ b/lib/vaporware/compiler/generator.rb @@ -5,15 +5,38 @@ class Compiler class Generator REGISTER = %w(rdi rsi rdx rcx r8 r9) attr_accessor :main - attr_reader :ast, :precompile, :debug, :seq, :defined_variables, :doned, :shared, :defined_methods - def initialize(source, precompile:, debug:, shared:) - @precompile, @debug, @shared = precompile, debug, shared + attr_reader :precompile, :shared + def initialize(input:, output: File.basename(input, "*") + ".s", debug: false, shared: false) + @source, @precompile, @debug, @shared = input, output, debug, shared @doned, @defined_methods, @defined_variables = Set.new, Set.new, Set.new @seq, @main = 0, false - @ast = RubyVM::AbstractSyntaxTree.parse_file(source) + @ast = RubyVM::AbstractSyntaxTree.parse_file(@source) end def compile_shared_option = %w(-shared -fPIC) + def compile + register_var_and_method(@ast) + + output = File.open(@precompile, "w") + # prologue + output.puts " .intel_syntax noprefix" + if @defined_methods.empty? + @main = true + output.puts " .globl main" + output.puts "main:" + output.puts " push rbp" + output.puts " mov rbp, rsp" + output.puts " sub rsp, #{@defined_variables.size * 8}" + to_asm(@ast, output) + epilogue(output) + else + prologue_methods(output) + output.puts " .globl main" unless @shared + to_asm(@ast, output) + epilogue(output) + end + output.close + end def register_var_and_method(node) return unless node.kind_of?(RubyVM::AbstractSyntaxTree::Node) @@ -29,17 +52,7 @@ def register_var_and_method(node) nil end - def already_build_methods? = defined_methods.sort == @doned.to_a.sort - - def call_compiler(output: precompile, compiler: "gcc", compiler_options: ["-O0"]) - base_name = File.basename(output, ".*") - name = shared ? "lib#{base_name}.so" : base_name - compile_commands = [compiler, *compiler_options, "-o", name, output].compact - IO.popen(compile_commands).close - - File.delete(output) unless debug - nil - end + def already_build_methods? = @defined_methods.sort == @doned.to_a.sort def epilogue(output) output.puts " mov rsp, rbp" @@ -48,7 +61,7 @@ def epilogue(output) end def prologue_methods(output) - defined_methods.each do |name| + @defined_methods.each do |name| output.puts ".globl #{name}" output.puts ".type #{name}, @function" if shared end @@ -85,10 +98,10 @@ def call_method(node, output, method_tree) output.puts " idiv rdi" output.puts " mov rax, 0" output.puts " cmp rdi, 0" - output.puts " jne .Lprecall#{seq}" + output.puts " jne .Lprecall#{@seq}" output.puts " push 0" output.puts " mov rax, 1" - output.puts ".Lprecall#{seq}:" + output.puts ".Lprecall#{@seq}:" output.puts " push rax" _, name, *args = node.children args.each_with_index do |arg, i| @@ -99,9 +112,9 @@ def call_method(node, output, method_tree) output.puts " call #{name}" output.puts " pop rdi" output.puts " cmp rdi, 0" - output.puts " je .Lpostcall#{seq}" + output.puts " je .Lpostcall#{@seq}" output.puts " pop rdi" - output.puts ".Lpostcall#{seq}:" + output.puts ".Lpostcall#{@seq}:" output.puts " push rax" @seq += 1 nil @@ -144,7 +157,7 @@ def to_asm(node, output, method_tree = false) when :LIT output.puts " push #{node.children.last}" return - when :LIST, :BLOCK + when :LIST, :BLOCK, :BEGIN node.children.each { |n| to_asm(n, output, method_tree) } return when :SCOPE @@ -194,33 +207,33 @@ def to_asm(node, output, method_tree = false) output.puts " push rax" output.puts " cmp rax, 0" if fblock - output.puts " je .Lelse#{seq}" + output.puts " je .Lelse#{@seq}" to_asm(tblock, output, method_tree) ret(output) - output.puts " jmp .Lend#{seq}" - output.puts ".Lelse#{seq}:" + output.puts " jmp .Lend#{@seq}" + output.puts ".Lelse#{@seq}:" to_asm(fblock, output, method_tree) ret(output) - output.puts ".Lend#{seq}:" + output.puts ".Lend#{@seq}:" else - output.puts " je .Lend#{seq}" + output.puts " je .Lend#{@seq}" to_asm(tblock, output, method_tree) ret(output) - output.puts ".Lend#{seq}:" + output.puts ".Lend#{@seq}:" end @seq += 1 return when :WHILE cond, tblock = node.children - output.puts ".Lbegin#{seq}:" + output.puts ".Lbegin#{@seq}:" to_asm(cond, output, method_tree) output.puts " pop rax" output.puts " push rax" output.puts " cmp rax, 0" - output.puts " je .Lend#{seq}" + output.puts " je .Lend#{@seq}" to_asm(tblock, output, method_tree) - output.puts " jmp .Lbegin#{seq}" - output.puts ".Lend#{seq}:" + output.puts " jmp .Lbegin#{@seq}" + output.puts ".Lend#{@seq}:" @seq += 1 return when :OPCALL diff --git a/lib/vaporware/compiler/linker.rb b/lib/vaporware/compiler/linker.rb new file mode 100644 index 0000000..2c436e5 --- /dev/null +++ b/lib/vaporware/compiler/linker.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true +class Vaporware::Compiler::Linker + def self.link!(source, dest = "a.out", linker: "mold", options: []) = new(input: source, output: dest, linker:, options:).link + + def initialize(input:, output: "a.out", linker: "mold", linker_options: [], shared: false, debug: false) + @input, @output, @linker = input, output, linker + @options = linker_options + @debug, @shared = debug, shared + end + + def link(input: @input, output: @output, debug: @debug, shared: @shared) = IO.popen(link_command).close + + def link_command(input: @input, output: @output, debug: @debug, shared: @shared) + ld_path = [] + if @shared + ld_path << "--shared" + ld_path << "#{libpath}/crti.o" + ld_path << "#{gcc_libpath}/crtbeginS.o" + ld_path << "#{gcc_libpath}/crtendS.o" + else + ld_path << "-dynamic-linker" + ld_path << "/lib64/ld-linux-x86-64.so.2" + ld_path << "#{libpath}/crt1.o" + ld_path << "#{libpath}/crti.o" + ld_path << "#{gcc_libpath}/crtbegin.o" + # for not static compile + ld_path << "#{gcc_libpath}/crtend.o" + end + + ld_path << "#{libpath}/libc.so" + ld_path << "#{libpath}/crtn.o" + cmd = [@linker, "-o", @output, "-m", "elf_x86_64", *@options, *ld_path, @input].join(' ') + puts cmd if @debug + cmd + end + + def libpath = @libpath ||= File.dirname(Dir.glob("/usr/lib*/**/crti.o").last) + def gcc_libpath = @gcc_libpath ||= File.dirname(Dir.glob("/usr/lib/gcc/x86_64-*/*/crtbegin.o").last) +end diff --git a/rbs_collection.lock.yaml b/rbs_collection.lock.yaml index 621039e..7335a6e 100644 --- a/rbs_collection.lock.yaml +++ b/rbs_collection.lock.yaml @@ -1,10 +1,4 @@ --- -sources: -- type: git - name: ruby/gem_rbs_collection - revision: 28208148c7e64a25e9b86b9723b4c3a2cef14e81 - remote: https://github.com/ruby/gem_rbs_collection.git - repo_dir: gems path: ".gem_rbs_collection" gems: - name: ast @@ -12,7 +6,27 @@ gems: source: type: git name: ruby/gem_rbs_collection - revision: 12631190e75c631eca0908ecc8c03bcfd78c0c99 + revision: 1663d48383d01e0088cf23206ef8662e48212e00 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: fileutils + version: '0' + source: + type: stdlib +- name: parser + version: '3.2' + source: + type: git + name: ruby/gem_rbs_collection + revision: 1663d48383d01e0088cf23206ef8662e48212e00 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: rake + version: '13.0' + source: + type: git + name: ruby/gem_rbs_collection + revision: 1663d48383d01e0088cf23206ef8662e48212e00 remote: https://github.com/ruby/gem_rbs_collection.git repo_dir: gems gemfile_lock_path: Gemfile.lock diff --git a/sample/assembler/else.s b/sample/assembler/else.s new file mode 100644 index 0000000..47334f6 --- /dev/null +++ b/sample/assembler/else.s @@ -0,0 +1,27 @@ + .intel_syntax noprefix + .globl main +main: + push rbp + mov rbp, rsp + sub rsp, 0 + push 0 + pop rax + push rax + cmp rax, 0 + je .Lelse0 + push 1 + pop rax + mov rsp, rbp + pop rbp + ret + jmp .Lend0 +.Lelse0: + push 2 + pop rax + mov rsp, rbp + pop rbp + ret +.Lend0: + mov rsp, rbp + pop rbp + ret diff --git a/sample/assembler/if.s b/sample/assembler/if.s new file mode 100644 index 0000000..754f39f --- /dev/null +++ b/sample/assembler/if.s @@ -0,0 +1,55 @@ + .intel_syntax noprefix + .globl main +main: + push rbp + mov rbp, rsp + sub rsp, 16 + mov rax, rbp + sub rax, 8 + push rax + push 1 + pop rdi + pop rax + mov [rax], rdi + push rdi + pop rax + mov rax, rbp + sub rax, 16 + push rax + push 1 + pop rdi + pop rax + mov [rax], rdi + push rdi + pop rax + mov rax, rbp + sub rax, 8 + push rax + pop rax + mov rax, [rax] + push rax + mov rax, rbp + sub rax, 16 + push rax + pop rax + mov rax, [rax] + push rax + pop rdi + pop rax + cmp rax, rdi + sete al + movzb rax, al + push rax + pop rax + push rax + cmp rax, 0 + je .Lend0 + push 1 + pop rax + mov rsp, rbp + pop rbp + ret +.Lend0: + mov rsp, rbp + pop rbp + ret diff --git a/sample/assembler/plus.s b/sample/assembler/plus.s new file mode 100644 index 0000000..5a725d2 --- /dev/null +++ b/sample/assembler/plus.s @@ -0,0 +1,31 @@ + .intel_syntax noprefix + .globl main +main: + push rbp + mov rbp, rsp + sub rsp, 0 + push 1 + push 2 + pop rdi + pop rax + add rax, rdi + push rax + push 3 + pop rdi + pop rax + imul rax, rdi + push rax + push 5 + push 4 + pop rdi + pop rax + sub rax, rdi + push rax + pop rdi + pop rax + cqo + idiv rdi + push rax + mov rsp, rbp + pop rbp + ret diff --git a/sample/assembler/variable.s b/sample/assembler/variable.s new file mode 100644 index 0000000..7de5ca9 --- /dev/null +++ b/sample/assembler/variable.s @@ -0,0 +1,63 @@ + .intel_syntax noprefix + .globl main +main: + push rbp + mov rbp, rsp + sub rsp, 24 + mov rax, rbp + sub rax, 8 + push rax + push 1 + pop rdi + pop rax + mov [rax], rdi + push rdi + pop rax + mov rax, rbp + sub rax, 16 + push rax + push 2 + pop rdi + pop rax + mov [rax], rdi + push rdi + pop rax + mov rax, rbp + sub rax, 24 + push rax + mov rax, rbp + sub rax, 8 + push rax + pop rax + mov rax, [rax] + push rax + mov rax, rbp + sub rax, 16 + push rax + pop rax + mov rax, [rax] + push rax + pop rdi + pop rax + add rax, rdi + push rax + push 3 + pop rdi + pop rax + cqo + idiv rdi + push rax + pop rdi + pop rax + mov [rax], rdi + push rdi + pop rax + mov rax, rbp + sub rax, 24 + push rax + pop rax + mov rax, [rax] + push rax + mov rsp, rbp + pop rbp + ret diff --git a/sig/vaporware.rbs b/sig/vaporware.rbs index fb62e27..91b07d5 100644 --- a/sig/vaporware.rbs +++ b/sig/vaporware.rbs @@ -1,6 +1,6 @@ module Vaporware VERSION: String # See the writing guide of rbs: https://github.com/ruby/rbs#guides - class Error + class Compiler end end diff --git a/sig/vaporware/compiler.rbs b/sig/vaporware/compiler.rbs index 59e7d9c..4363803 100644 --- a/sig/vaporware/compiler.rbs +++ b/sig/vaporware/compiler.rbs @@ -1,10 +1,17 @@ class Vaporware::Compiler + GCC_LD_PATH: String + LD_PATH: String + DYN_LD_PATH: String + SHARED_LD_PATH: String # class methods - def self.compile: (String, ?compiler: String, ?dest: String, ?debug: bool, ?compiler_options: Array[String], ?shared: bool) -> void - def initialize: (String, ?_precompile: String, ?debug: bool, ?shared: bool) -> void + def self.compile: (String, ?assembler: String, ?linker: String, ?dest: String, ?debug: bool, ?compiler_options: Array[String], ?linker_options: Array[String], ?shared: bool) -> void + def initialize: (input: String, ?output: String, ?assembler: String, ?linker: String, ?debug: bool, ?shared: bool) -> void @generator: Vaporware::Compiler::Generator + @assembler: Vaporware::Compiler::Assembler # instance methods - def compile: (?compiler: String, ?compiler_options: Array[String]) -> void + def assemble: (input: String, ?output: String, ?assembler: String, ?assembler_options: Array[String]?, ?debug: bool) -> String + def link: (input: String, ?output: String, ?linker: String, ?linker_options: Array[String], ?shared: bool) -> void + def compile: (?compiler_options: Array[String]) -> void + def call_command: (Array[String]) -> void end - diff --git a/sig/vaporware/compiler/assembler.rbs b/sig/vaporware/compiler/assembler.rbs new file mode 100644 index 0000000..fac4245 --- /dev/null +++ b/sig/vaporware/compiler/assembler.rbs @@ -0,0 +1,13 @@ +use Vaporware::Compiler::Assembler::ELF +class Vaporware::Compiler::Assembler + @input: String + @output: String + @assembler: String + @elf: ELF + @debug: bool + + def initialize: (input: String, ?output: String, ?type: Symbol, ?debug: bool) -> void + def assemble: (?assembler: String, ?assembler_options: Array[String] | [], ?input: String, ?output: String, ?debug: bool) -> String + def obj_file: () -> String + def to_elf: (?input: String, ?output: String, ?debug: bool) -> void +end diff --git a/sig/vaporware/compiler/assembler/elf.rbs b/sig/vaporware/compiler/assembler/elf.rbs new file mode 100644 index 0000000..78a0346 --- /dev/null +++ b/sig/vaporware/compiler/assembler/elf.rbs @@ -0,0 +1,23 @@ +use Vaporware::Compiler::Assembler::ELF::Header +use Vaporware::Compiler::Assembler::ELF::Section::Text +use Vaporware::Compiler::Assembler::ELF::Sections + +class Vaporware::Compiler::Assembler::ELF + Error: singleton(StandardError) + + @input: String + @output: String + @header: Header + @sections: Sections + + def initialize: (type: Symbol, input: String, output: String, debug: bool) -> void + def build: (input: String, output: String, ?debug: bool) -> void + + private def read!: (?input: String, ?text: Text) -> void + private def init_assemble!: () -> void + private def note!: () -> void + private def text!: () -> void + private def symtab!: () -> void + private def strtab!: () -> void + private def shstrtab!: () -> void +end diff --git a/sig/vaporware/compiler/assembler/elf/header.rbs b/sig/vaporware/compiler/assembler/elf/header.rbs new file mode 100644 index 0000000..6580aef --- /dev/null +++ b/sig/vaporware/compiler/assembler/elf/header.rbs @@ -0,0 +1,30 @@ +class Vaporware::Compiler::Assembler::ELF::Header + IDENT: Array[Integer] + ELF_FILE_TYPE: Hash[Symbol, Integer] + + @e_ident: Array[Integer] + @type: Integer + @arch: Array[Integer] + @version: Array[Integer] + @entry: Array[Integer]? + @phoffset: Array[Integer]? + @shoffset: Array[Integer]? + @flags: Array[Integer] + @ehsize: Array[Integer] + @phsize: Array[Integer] + @ehnum: Array[Integer] + @shentsize: Array[Integer] + @shnum: Array[Integer]? + @shstrndx: Array[Integer]? + + def initialize: (?endian: Symbol, ?type: Symbol, ?arche: Symbol) -> void + def build: () -> String + def set!: (?entry: Integer?, ?phoffset: Integer?, ?shoffset: Integer?, ?shnum: Integer?, ?shstrndx: Integer?) -> void + + private + def check: (Array[Integer] | Integer, Integer) -> bool + def num2bytes: (Integer, Integer) -> Array[Integer] + def bytes: () -> Array[Array[Integer]] + def arch: (String | Symbol) -> Array[Integer] + def elf: (String | Symbol) -> Symbol +end diff --git a/sig/vaporware/compiler/assembler/elf/section.rbs b/sig/vaporware/compiler/assembler/elf/section.rbs new file mode 100644 index 0000000..e3412b5 --- /dev/null +++ b/sig/vaporware/compiler/assembler/elf/section.rbs @@ -0,0 +1,11 @@ +use Vaporware::Compiler::Assembler::ELF::SectionHeader +use Vaporware::Compiler::Assembler::ELF::Section::* + +class Vaporware::Compiler::Assembler::ELF::Section + attr_reader name: String + attr_reader header: SectionHeader + attr_reader body: Text | Null | Data | BSS | Symtab | Shstrtab | Strtab | Note + attr_reader section_name: String + def initialize: (type: (String | Symbol)) -> void + def name=: (String) -> void +end diff --git a/sig/vaporware/compiler/assembler/elf/section/bss.rbs b/sig/vaporware/compiler/assembler/elf/section/bss.rbs new file mode 100644 index 0000000..05b60d6 --- /dev/null +++ b/sig/vaporware/compiler/assembler/elf/section/bss.rbs @@ -0,0 +1,11 @@ +class Vaporware::Compiler::Assembler::ELF::Section::BSS + attr_reader size: Integer + attr_reader offset: Integer + + def build: () -> String + def set!: () -> self + + private + def bytes: () -> Array[Array[Integer?]] + def check: (Integer, Integer) -> bool +end diff --git a/sig/vaporware/compiler/assembler/elf/section/data.rbs b/sig/vaporware/compiler/assembler/elf/section/data.rbs new file mode 100644 index 0000000..3a91458 --- /dev/null +++ b/sig/vaporware/compiler/assembler/elf/section/data.rbs @@ -0,0 +1,11 @@ +class Vaporware::Compiler::Assembler::ELF::Section::Data + PREFIX: Hash[Symbol, Integer] + REGISTER_CODE: Hash[Symbol, Integer] + OPECODE: Hash[Symbol, Integer] + + attr_reader bytes: Array[Integer] + attr_reader size: Integer + attr_reader offset: Integer + + def build: () -> String +end diff --git a/sig/vaporware/compiler/assembler/elf/section/note.rbs b/sig/vaporware/compiler/assembler/elf/section/note.rbs new file mode 100644 index 0000000..fef8d46 --- /dev/null +++ b/sig/vaporware/compiler/assembler/elf/section/note.rbs @@ -0,0 +1,29 @@ +class Vaporware::Compiler::Assembler::ELF::Section::Note + def self.gnu_property: () -> String + + @nsize: Array[Integer]? + @dsize: Array[Integer]? + @type: Array[Integer]? + @name: Array[Integer]? + @desc: Array[Integer]? + + def initialize: (?type: String | Symbol) -> void + + def set!: ( + ?nsize: Integer?, + ?dsize: Integer?, + ?type: Integer?, + ?name: (Integer | Array[Integer] | String)?, + ?desc: (Integer | Array[Integer] | String)? + ) -> self + def gnu_property!: () -> void + def build: () -> String + + private + def name!: ((Integer | Array[Integer] | String)?) -> Array[Integer] + def desc!: ((String | Array[Integer])?) -> Array[Integer] + def bytes: () -> Array[Array[Integer] | nil] + def align!: (Array[Integer], Integer) -> Array[Integer] + def num2bytes: (Integer?, Integer) -> Array[Integer] + def check: ((Integer | Array[Integer])?, Integer) -> bool +end diff --git a/sig/vaporware/compiler/assembler/elf/section/null.rbs b/sig/vaporware/compiler/assembler/elf/section/null.rbs new file mode 100644 index 0000000..0bf63b3 --- /dev/null +++ b/sig/vaporware/compiler/assembler/elf/section/null.rbs @@ -0,0 +1,2 @@ +class Vaporware::Compiler::Assembler::ELF::Section::Null +end diff --git a/sig/vaporware/compiler/assembler/elf/section/shstrtab.rbs b/sig/vaporware/compiler/assembler/elf/section/shstrtab.rbs new file mode 100644 index 0000000..cebdb11 --- /dev/null +++ b/sig/vaporware/compiler/assembler/elf/section/shstrtab.rbs @@ -0,0 +1,9 @@ +class Vaporware::Compiler::Assembler::ELF::Section::Shstrtab + @strtab: Array[Integer] + def initialize: () -> void + def build: () -> String + def set!: (name: String | Array[Integer]) -> self + def set: (name: String | Array[Integer]) -> Array[Integer]? + private def bytes: () -> Array[Array[Integer]?] + private def name!: (String | Array[Integer]) -> Array[Integer]? +end diff --git a/sig/vaporware/compiler/assembler/elf/section/strtab.rbs b/sig/vaporware/compiler/assembler/elf/section/strtab.rbs new file mode 100644 index 0000000..3f40b9c --- /dev/null +++ b/sig/vaporware/compiler/assembler/elf/section/strtab.rbs @@ -0,0 +1,5 @@ +class Vaporware::Compiler::Assembler::ELF::Section::Strtab + @name: String + def initialize: (String) -> void + def build: () -> String +end diff --git a/sig/vaporware/compiler/assembler/elf/section/symtab.rbs b/sig/vaporware/compiler/assembler/elf/section/symtab.rbs new file mode 100644 index 0000000..369ff34 --- /dev/null +++ b/sig/vaporware/compiler/assembler/elf/section/symtab.rbs @@ -0,0 +1,20 @@ +class Vaporware::Compiler::Assembler::ELF::Section::Symtab + @name: Array[Integer]? + @info: Array[Integer] + @other: Array[Integer] + @shndx: Array[Integer] + @value: Array[Integer] + @size: Array[Integer] + + def initialize: () -> void + def set!: (?name: Integer?, ?info: Integer?, ?other: Integer?, ?shndx: Integer?, ?value: Integer?, ?size: Integer?) -> void + def build: () -> String + + private def name!: (String) -> void + private def desc!: (String) -> void + private def bytes: () -> Array[Array[Integer]?] + private def align!: (Array[Integer], Integer) -> void + private def num2bytes: (Integer?, Integer) -> Array[Integer] + private def check: ((String | Integer | Array[Integer])?, Integer) -> bool + private def name2bytes: ((String | Integer | Array[Integer])?, Integer) -> Array[Integer]? +end diff --git a/sig/vaporware/compiler/assembler/elf/section/text.rbs b/sig/vaporware/compiler/assembler/elf/section/text.rbs new file mode 100644 index 0000000..bc41571 --- /dev/null +++ b/sig/vaporware/compiler/assembler/elf/section/text.rbs @@ -0,0 +1,23 @@ +class Vaporware::Compiler::Assembler::ELF::Section::Text + PREFIX: Hash[Symbol, Integer] + REGISTER_CODE: Hash[Symbol, Integer] + OPECODE: Hash[Symbol, Integer] + + @bytes: Array[untyped] + + attr_reader offset: Integer + + def initialize: () -> void + def assemble!: (String) -> void + def align!: (Integer) -> void + def build: () -> String + + private + + def opecode: ((String | Symbol)?, *String) -> Array[Integer] + def mov: ((String | Symbol), Array[String]) -> Array[Integer] + def calc: ((String | Symbol), Array[String]) -> Array[Integer] + def push: (Array[String]) -> Array[Integer] + def pop: (Array[String]) -> Array[Integer] + def reg: ((String | Symbol)) -> Integer +end diff --git a/sig/vaporware/compiler/assembler/elf/section_header.rbs b/sig/vaporware/compiler/assembler/elf/section_header.rbs new file mode 100644 index 0000000..b057212 --- /dev/null +++ b/sig/vaporware/compiler/assembler/elf/section_header.rbs @@ -0,0 +1,24 @@ +use Vaporware::Compiler::Assembler::ELF + +class Vaporware::Compiler::Assembler::ELF::SectionHeader + @name: Array[Integer]? + @type: Array[Integer]? + @flags: Array[Integer]? + @addr: Array[Integer]? + @offset: Array[Integer]? + @size: Array[Integer]? + @link: Array[Integer]? + @info: Array[Integer]? + @addralign: Array[Integer]? + @entsize: Array[Integer]? + + def build: () -> String + def set!: (?name: Integer?, ?type: Integer?, ?flags: Integer?, ?addr: Integer?, ?offset: Integer?, ?size: Integer?, ?link: Integer?, ?info: Integer?, ?addralign: Integer?, ?entsize: Integer?) -> Vaporware::Compiler::Assembler::ELF::SectionHeader + def null!: () -> ELF::SectionHeader + def text!: () -> ELF::SectionHeader + def note!: () -> ELF::SectionHeader + + private def bytes: () -> Array[Array[Integer]?] + private def check: ((Array[Integer] | Integer)?, Integer) -> bool + private def num2bytes: (Integer?, Integer) -> Array[Integer] +end diff --git a/sig/vaporware/compiler/assembler/elf/sections.rbs b/sig/vaporware/compiler/assembler/elf/sections.rbs new file mode 100644 index 0000000..a31365f --- /dev/null +++ b/sig/vaporware/compiler/assembler/elf/sections.rbs @@ -0,0 +1,16 @@ +use Vaporware::Compiler::Assembler::ELF::Section + +class Vaporware::Compiler::Assembler::ELF::Sections + ATTRIBUTES: Array[Symbol] + attr_reader null: Section + attr_reader text: Section + attr_reader data: Section + attr_reader bss: Section + attr_reader note: Section + attr_reader symtab: Section + attr_reader strtab: Section + attr_reader shstrtab: Section + + def initialize: () -> void + def each: (){ (Vaporware::Compiler::Assembler::ELF::Section) -> void } -> void +end diff --git a/sig/vaporware/compiler/assembler/elf/utils.rbs b/sig/vaporware/compiler/assembler/elf/utils.rbs new file mode 100644 index 0000000..eb4bb36 --- /dev/null +++ b/sig/vaporware/compiler/assembler/elf/utils.rbs @@ -0,0 +1,13 @@ +use Vaporware::Compiler::Assembler::ELF::Error + +module Vaporware::Compiler::Assembler::ELF::Utils + def build: () -> String + def size: () -> Integer + def empties: () -> Array[Symbol] + private def align: (Array[Integer], Integer) -> void + private def check: ((Array[Integer] | Integer)?, Integer) -> bool + private def num2bytes: (Integer, Integer) -> Array[Integer] + private def bytes: () -> Array[Array[Integer]?] + private def hexas: (Integer, Integer) -> Array[Integer] + private def must_be_filled_section_fields: () -> Array[Symbol] +end diff --git a/sig/vaporware/compiler/generator.rbs b/sig/vaporware/compiler/generator.rbs index 3af1e8c..82bad73 100644 --- a/sig/vaporware/compiler/generator.rbs +++ b/sig/vaporware/compiler/generator.rbs @@ -1,30 +1,26 @@ class Vaporware::Compiler::Generator REGISTER: Array[String] - # attr_accessor and define instance variables - @main: bool - attr_accessor main (): bool - - # attr_reader for instance variables - attr_reader debug: bool - attr_reader doned: Set[Symbol] - attr_reader defined_methods: Set[Symbol] - attr_reader defined_variables: Set[Symbol] + # attr_reader attr_reader precompile: String - attr_reader seq: Integer - attr_reader shared: bool - # temporarily using untyped types since parser.gem's rbs information is unchecked. - attr_reader ast: RubyVM::AbstractSyntaxTree::Node + @main: bool + @debug: bool + @doned: Set[Symbol] + @defined_methods: Set[Symbol] + @defined_variables: Set[Symbol] + @seq: Integer + @shared: bool + @ast: RubyVM::AbstractSyntaxTree::Node + @source: String # class methods - def initialize: (String, precompile: String, debug: bool, shared: bool) -> void + def initialize: (input: String, ?output: String, ?debug: bool, ?shared: bool) -> void # instance private methods def already_build_methods?: -> bool - def call_compiler: (?output: String, ?compiler: String, ?compiler_options: Array[String]) -> void def call_method: (RubyVM::AbstractSyntaxTree::Node, File, bool) -> void - def comp: (String, File) -> void + def compile: () -> void def compile_shared_option: () -> Array[String] def define_method_prologue: (RubyVM::AbstractSyntaxTree::Node, File) -> void def epilogue: (File) -> void @@ -33,7 +29,7 @@ class Vaporware::Compiler::Generator def method: (Symbol, RubyVM::AbstractSyntaxTree::Node, File) -> void def prologue: (RubyVM::AbstractSyntaxTree::Node, File) -> void def prologue_methods: (File) -> void - def register_var_and_method: (RubyVM::AbstractSyntaxTree::Node) -> void + def register_var_and_method: (RubyVM::AbstractSyntaxTree::Node?) -> void def ret: (File) -> void def to_asm: (RubyVM::AbstractSyntaxTree::Node, File, ?bool) -> void def variable_or_method?: (Symbol) -> bool diff --git a/sig/vaporware/compiler/linker.rbs b/sig/vaporware/compiler/linker.rbs new file mode 100644 index 0000000..2c90bee --- /dev/null +++ b/sig/vaporware/compiler/linker.rbs @@ -0,0 +1,15 @@ +class Vaporware::Compiler::Linker + @input: String + @output: String + @linker: String + @options: Array[String] + @shared: bool + @debug: bool + + def initialize: (input: String, ?output: String, ?linker: String, ?linker_options: Array[String], ?shared: bool, ?debug: bool) -> void + def link: (input: String, ?output: String, ?shared: bool, ?debug: bool) -> void + + def link_command: () -> String + def libpath: () -> String + def gcc_libpath: () -> String +end diff --git a/test/vaporware/compiler/assembler/elf/section/test_note.rb b/test/vaporware/compiler/assembler/elf/section/test_note.rb new file mode 100644 index 0000000..49c249e --- /dev/null +++ b/test/vaporware/compiler/assembler/elf/section/test_note.rb @@ -0,0 +1,18 @@ +require "vaporware" +require "test/unit" + +class Vaporware::Compiler::Assembler::ELF::Section::NoteTest < Test::Unit::TestCase + def reference_binary = "\x04\x00\x00\x00 \x00\x00\x00\x05\x00\x00\x00GNU\x00\x02\x00\x01\xC0\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\xC0\x04\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00".force_encoding("ASCII-8BIT") + def setup = @note = Vaporware::Compiler::Assembler::ELF::Section::Note.new + + def test_gnu_property! + assert_equal( + Vaporware::Compiler::Assembler::ELF::Section::Note.gnu_property, + reference_binary + ) + assert_equal( + @note.gnu_property!.build, + reference_binary + ) + end +end diff --git a/test/vaporware/compiler/assembler/elf/section/test_shstrtab.rb b/test/vaporware/compiler/assembler/elf/section/test_shstrtab.rb new file mode 100644 index 0000000..55c2df9 --- /dev/null +++ b/test/vaporware/compiler/assembler/elf/section/test_shstrtab.rb @@ -0,0 +1,18 @@ +require "vaporware" +require "test/unit" + +class Vaporware::Compiler::Assembler::ELF::Section::TestShstrtab < Test::Unit::TestCase + def setup = @shstrtab = Vaporware::Compiler::Assembler::ELF::Section::Shstrtab.new + def test_set_values + binary = "\x00.text\x00".force_encoding("ASCII-8BIT") + @shstrtab.set!(name: "text") + assert_equal(@shstrtab.build, binary) + binary = "\x00.text\x00.strtab\x00".force_encoding("ASCII-8BIT") + @shstrtab.set!(name: "strtab") + assert_equal(@shstrtab.build, binary) + end + def test_alert_values + assert_raise(Vaporware::Compiler::Assembler::ELF::Error) { @shstrtab.set!(name: :main) } + assert_raise(Vaporware::Compiler::Assembler::ELF::Error) { @shstrtab.set!(name: 123) } + end +end diff --git a/test/vaporware/compiler/assembler/elf/section/test_symtab.rb b/test/vaporware/compiler/assembler/elf/section/test_symtab.rb new file mode 100644 index 0000000..ba296b3 --- /dev/null +++ b/test/vaporware/compiler/assembler/elf/section/test_symtab.rb @@ -0,0 +1,21 @@ +require "vaporware" +require "test/unit" + +class Vaporware::Compiler::Assembler::ELF::Section::TestSymtab < Test::Unit::TestCase + def setup = @symtab = Vaporware::Compiler::Assembler::ELF::Section::Symtab.new + + def test_default_values + binary = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".force_encoding("ASCII-8BIT") + assert_equal(@symtab.build, binary) + end + + def test_main_value + binary = "\x01\x00\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".force_encoding("ASCII-8BIT") + @symtab.set!(name: [1, 0, 2, 1]) + assert_equal(@symtab.build, binary) + @symtab.set!(name: 16908289) + assert_equal(@symtab.build, binary) + @symtab.set!(name: "\x01\x00\x02\x01".force_encoding("ASCII-8BIT")) + assert_equal(@symtab.build, binary) + end +end diff --git a/test/vaporware/compiler/assembler/elf/test_header.rb b/test/vaporware/compiler/assembler/elf/test_header.rb new file mode 100644 index 0000000..2c6affd --- /dev/null +++ b/test/vaporware/compiler/assembler/elf/test_header.rb @@ -0,0 +1,10 @@ +require "vaporware" +require "test/unit" + +class Vaporware::Compiler::Assembler::ELF::HeaderTest < Test::Unit::TestCase + def setup = @elf_header = Vaporware::Compiler::Assembler::ELF::Header.new + + def test_build_elf_header + # TODO: ELF Header Section binary + end +end diff --git a/test/vaporware/compiler/assembler/elf/test_section_header.rb b/test/vaporware/compiler/assembler/elf/test_section_header.rb new file mode 100644 index 0000000..6d282ea --- /dev/null +++ b/test/vaporware/compiler/assembler/elf/test_section_header.rb @@ -0,0 +1,11 @@ +require "vaporware" +require "test/unit" + +class Vaporware::Compiler::Assembler::ELF::SectionHeaderTest < Test::Unit::TestCase + def setup = @section_header = Vaporware::Compiler::Assembler::ELF::SectionHeader.new + def test_null! + assert(@section_header.null!) + assert_equal(@section_header.build.size, 64) + assert_equal(@section_header.build, [[0] * 4, [0] * 4, [0] * 8, [0] * 8, [0] * 8, [0] * 8, [0] * 4, [0] * 4, [0] * 8, [0] * 8].flatten.pack("C*")) + end +end diff --git a/test/vaporware/compiler/test_assembler.rb b/test/vaporware/compiler/test_assembler.rb new file mode 100644 index 0000000..a979a77 --- /dev/null +++ b/test/vaporware/compiler/test_assembler.rb @@ -0,0 +1,61 @@ +require "vaporware" +require "test/unit" +require "tempfile" +require "pathname" + +class Vaporware::Compiler::Assembler::ELFTest < Test::Unit::TestCase + def test_to_elf + input = Pathname.pwd.join('sample', 'assembler', 'plus.s').to_s + + assembler = Vaporware::Compiler::Assembler.new(input:, output: "amd64.o") + header, null, text, data, bss, note, symtab, strtab, shstrtab, *section_headers = assembler.to_elf + sh_null, sh_text, sh_data, sh_bss, sh_note, sh_symtab, sh_strtab, sh_shstrtab = section_headers + r_header, r_null, r_text, r_data, r_bss, r_note, r_symtab, r_strtab, r_shstrtab, *r_section_headers = dumped_references + r_sh_null, r_sh_text, r_sh_data, r_sh_bss, r_sh_note, r_sh_symtab, r_sh_strtab, r_sh_shstrtab = r_section_headers + assert_equal(r_header, header.unpack("C*")) + assert_equal(r_null, null.unpack("C*")) + assert_equal(r_data, data.unpack("C*")) + # remove alignment bytes + assert_equal(r_text, text.unpack("C*")[..-7]) + assert_equal(r_bss, bss.unpack("C*")) + assert_equal(r_note, note.unpack("C*")) + assert_equal(r_symtab, symtab.unpack("C*")) + assert_equal(r_strtab, strtab.unpack("C*")) + ref_shstrtab = r_shstrtab.pack("C*").split("\0").select { |str| str.size > 0 }.sort + assert(ref_shstrtab.zip(shstrtab.split("\0").select { |str| str.size > 0 }.sort).all? { |ref, act| ref =~ /#{act}/ }) + assert_equal(r_sh_null, sh_null.unpack("C*")) + assert_equal(r_sh_text, sh_text.unpack("C*")) + assert_equal(r_sh_data, sh_data.unpack("C*")) + assert_equal(r_sh_bss, sh_bss.unpack("C*")) + assert_equal(r_sh_note, sh_note.unpack("C*")) + assert_equal(r_sh_symtab, sh_symtab.unpack("C*")) + assert_equal(r_sh_strtab, sh_strtab.unpack("C*")) + assert_equal(r_sh_shstrtab, sh_shstrtab.unpack("C*")) + File.delete("amd64.o") + end + + def dumped_references + [ + [127, 69, 76, 70, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 62, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 64, 0, 8, 0, 7, 0], # elf header + [], # null section + [85, 72, 137, 229, 72, 131, 236, 0, 106, 1, 106, 2, 95, 88, 72, 1, 248, 80, 106, 3, 95, 88, 72, 15, 175, 199, 80, 106,5, 106, 4, 95, 88, 72, 41, 248, 80, 95, 88, 72, 153, 72, 247, 255, 80, 72, 137, 236, 93, 195], # text section + [], # data section + [], # bss section + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78, 85, 76, 76, 0, 0, 0, 0],# + # [4, 0, 0, 0, 32, 0, 0, 0, 5, 0, 0, 0, 71, 78, 85, 0, 2, 0, 1, 192, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 192, 4, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], # note section + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 16, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0], # symtab section + [0, 109, 97, 105, 110, 0], # strtab section + [0, 46, 115, 121, 109, 116, 97, 98, 0, 46, 115, 116, 114, 116, 97, 98, 0, 46, 115, 104, 115, 116, 114, 116, 97, 98, 0,46, 116, 101, 120, 116, 0, 46, 100, 97, 116, 97, 0, 46, 98, 115, 115, 0, 46, 110, 111, 116, 101, 46, 103, 110, 117, 46, 112, 114, 111, 112, 101, 114, 116, 121, 0], # shstrtab section + # section headers + [0]*64, # null + [1, 0, 0, 0, 1, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # text + [7, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # data + [13, 0, 0, 0, 8, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # bss + [18, 0, 0, 0, 7, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # note + [24, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 140, 0, 0, 0, 0, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0], # symtab + [32, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 188, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # strtab + [40, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 194, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # shstrtab + [], + ] + end +end diff --git a/test/vaporware/compiler/test_linker.rb b/test/vaporware/compiler/test_linker.rb new file mode 100644 index 0000000..2db39fb --- /dev/null +++ b/test/vaporware/compiler/test_linker.rb @@ -0,0 +1,17 @@ +require "vaporware" +require "test/unit" + +class Vaporware::Compiler::LinkerTest < Test::Unit::TestCase + def test_librarie + linker = Vaporware::Compiler::Linker.new(input: "tmp.o") + assert_false(linker.libpath.empty?, "should not be empty") + assert_false(linker.gcc_libpath.empty?, "should not be empty") + end + + def test_link_command + linker = Vaporware::Compiler::Linker.new(input: "tmp.o", output: "tmp") + assert_match(%r|mold -o tmp -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 /.+/crt1.o /.+/crti.o /.+/crtbegin.o /.+/crtend.o /.+/libc.so /.+/crtn.o tmp.o|, linker.link_command) + linker = Vaporware::Compiler::Linker.new(input: "tmp.o", output: "tmp", shared: true) + assert_match(%r|mold -o tmp -m elf_x86_64 --shared /.+/crti.o /.+/crtbeginS.o /.+/crtendS.o /.+/libc.so /.+/crtn.o tmp.o|, linker.link_command) + end +end diff --git a/test/test_vaporware.rb b/test/vaporware/test_compiler.rb similarity index 65% rename from test/test_vaporware.rb rename to test/vaporware/test_compiler.rb index 0d71327..0173c90 100644 --- a/test/test_vaporware.rb +++ b/test/vaporware/test_compiler.rb @@ -1,12 +1,15 @@ require "vaporware" require "test/unit" -class VaporwareTest < Test::Unit::TestCase - def tear_down = File.delete("tmp") rescue File.delete(@generated) +class Vaporware::CompilerTest < Test::Unit::TestCase + def setup = @generated = ["tmp.s", "tmp.o"] + def teardown + File.delete("tmp") if File.exist?("tmp") + @generated.map { File.delete(_1) if File.exist?(_1) } + end def test_sample_plus @file = "sample/plus.rb" - @vaporware = Vaporware::Compiler.new(@file) - @vaporware.compile + @vaporware = Vaporware::Compiler.compile(@file, assembler: "self") IO.popen("./tmp").close exit_code, handle_code = check_process($?.to_i) assert_equal(9, exit_code) @@ -15,8 +18,7 @@ def test_sample_plus def test_sample_variable @file = "sample/variable.rb" - @vaporware = Vaporware::Compiler.new(@file) - @vaporware.compile + @vaporware = Vaporware::Compiler.compile(@file, assembler: "self") IO.popen("./tmp").close exit_code, handle_code = check_process($?.to_i) assert_equal(1, exit_code) @@ -25,8 +27,7 @@ def test_sample_variable def test_sample_if @file = "sample/if.rb" - @vaporware = Vaporware::Compiler.new(@file) - @vaporware.compile + @vaporware = Vaporware::Compiler.compile(@file, assembler: "self") IO.popen("./tmp").close exit_code, handle_code = check_process($?.to_i) assert_equal(1, exit_code) @@ -35,8 +36,7 @@ def test_sample_if def test_sample_else @file = "sample/else.rb" - @vaporware = Vaporware::Compiler.new(@file) - @vaporware.compile + @vaporware = Vaporware::Compiler.compile(@file, assembler: "self") IO.popen("./tmp").close exit_code, handle_code = check_process($?.to_i) assert_equal(2, exit_code) @@ -45,8 +45,7 @@ def test_sample_else def test_sample_while @file = "sample/while.rb" - @vaporware = Vaporware::Compiler.new(@file) - @vaporware.compile + @vaporware = Vaporware::Compiler.compile(@file) IO.popen("./tmp").close exit_code, handle_code = check_process($?.to_i) assert_equal(55, exit_code) @@ -54,10 +53,9 @@ def test_sample_while end def test_sample_call_method - @generated = "libtmp.so" + @generated = ["libtmp.so", "libtmp.so.o", "libtmp.so.s"] @file = "sample/method.rb" - @vaporware = Vaporware::Compiler.new(@file, shared: true) - @vaporware.compile + @vaporware = Vaporware::Compiler.compile(@file, dest: "./libtmp.so", shared: true) require './sample/fiddle.rb' assert_equal(10, X.aibo) end