diff --git a/lib/vaporware/compiler.rb b/lib/vaporware/compiler.rb index 497147b..095ad45 100644 --- a/lib/vaporware/compiler.rb +++ b/lib/vaporware/compiler.rb @@ -6,59 +6,42 @@ class Vaporware::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:) + s = new(input: source, output: _precompile, debug:, shared:) + s.compile(compiler_options:) obj_file = s.assemble(input: _precompile, assembler: "as", debug:) - s.link(obj_file) - File.delete(_precompile) if debug + output = File.basename(obj_file, ".o") + output = "lib#{output}.so" if shared + s.link(input: obj_file, output:, shared:) + File.delete(obj_file) unless debug + #File.delete(_precompile) unless debug end - def initialize(source, _precompile: "tmp.s", debug: false, shared: false) - @generator = Vaporware::Compiler::Generator.new(source, precompile: _precompile, debug:, shared:) - @assembler = Vaporware::Compiler::Assembler.new(@generator.precompile, debug:) + def initialize(input:, output: File.basename(input, ".*") + ".s", debug: false, shared: false) + @generator = Vaporware::Compiler::Generator.new(input:, output:, debug:, shared:) + @assembler = Vaporware::Compiler::Assembler.new(input: @generator.precompile, debug:,) end - def assemble(input:, output: File.basename(input, ".*") + ".o", assembler: "gcc", assembler_options: [], debug: false) + def assemble(input:, output: File.basename(input, ".*") + ".o", assembler: "as", assembler_options: [], debug: false) if ["gcc", "as"].include?(assembler) - assemble_commands = [assembler, *assembler_options, "-o", output, input].compact - call_commands(assemble_commands) + assemble = [assembler, *assembler_options, "-o", output, input].compact + call_command(assemble) else - @assembler.assemble(input, output) + @assembler.assemble(input:, output:) end output end - def link(input, output = File.basename(input, ".*"), linker: "mold", linker_options: ["-m", "elf_x86_64", "-dynamic-linker", "/lib64/ld-linux-x86-64.so.2", "/lib64/libc.so.6", "/usr/lib64/crt1.o", input]) - linker_commands = [linker, *linker_options, "-o", output].compact - call_commands(linker_commands) + def link(input:, output: File.basename(input, ".*"), linker: "mold", linker_options: [], dyn_ld_path: ["-dynamic-linker", "/lib64/ld-linux-x86-64.so.2"], ld_path: ["/lib64/libc.so.6", "/usr/lib64/crt1.o"], shared: false) + if shared + dyn_ld_path = [] + ld_path = ["/usr/lib64/crti.o", "/usr/lib/gcc/x86_64-pc-linux-gnu/*/crtbeginS.o"] + linker_options = ["-shared"] + end + linker_commands = [linker, *linker_options, *dyn_ld_path, "-o", output, *ld_path, input].compact + call_command(linker_commands) end - def call_commands(commands) = IO.popen(commands).close - - def compile(compiler: "gcc", compiler_options: ["-O0"]) - @generator.register_var_and_method(@generator.ast) + def call_command(commands) = IO.popen(commands.join(" ")).close - 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 - end + def compile(compiler_options: ["-O0"]) = @generator.compile end diff --git a/lib/vaporware/compiler/assembler.rb b/lib/vaporware/compiler/assembler.rb index c93d2a7..dacc3d6 100644 --- a/lib/vaporware/compiler/assembler.rb +++ b/lib/vaporware/compiler/assembler.rb @@ -2,225 +2,56 @@ require_relative "assembler/elf" require_relative "assembler/elf/header" require_relative "assembler/elf/section" -require_relative "assembler/elf/section/note" require_relative "assembler/elf/section/text" +require_relative "assembler/elf/section/bss" +require_relative "assembler/elf/section/data" +require_relative "assembler/elf/section/note" require_relative "assembler/elf/section/symtab" +require_relative "assembler/elf/section/strtab" require_relative "assembler/elf/section/shsymtab" +require_relative "assembler/elf/section/shstrtab" require_relative "assembler/elf/section_header" -module Vaporware - class Compiler - class Assembler - SYMTAB_SECTION = %w( - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 01 00 00 00 10 00 01 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - ).map { _1.to_i(16) }.pack("C*") - - SHSYMTAB_SECTION = %w( - 00 2e 73 79 6d 74 61 62 00 2e 73 74 72 74 61 62 - 00 2e 73 68 73 74 72 74 61 62 00 2e 74 65 78 74 - 00 2e 64 61 74 61 00 2e 62 73 73 00 2e 6e 6f 74 - 65 2e 67 6e 75 2e 70 72 6f 70 65 72 74 79 00 - ).map { _1.to_i(16) }.pack("C*") - - def self.assemble!(input, output = File.basename(input, ".*") + ".o") = new(input, output).assemble - - def initialize(input, output = File.basename(input, ".*") + ".o", type: :relocator, debug: false) - @input, @output = input, output - @target_file = File.open(output, "wb") - @elf_header = ELF::Header.new(type:) - @sections = { - null: { body: nil, header: ELF::SectionHeader.new.null! }, - text: { body: ELF::Section::Text.new, header: ELF::SectionHeader.new.text! }, - note: { body: ELF::Section::Note.new.gnu_property!, header: ELF::SectionHeader.new.note! }, - symtab: { body: ELF::Section::Symtab.new, header: ELF::SectionHeader.new.symtab! }, - strtab: { body: ELF::Section::Strtab.new, header: ELF::SectionHeader.new.strtab! }, - shsymtab: { body: ELF::Section::Shsymtab.new, header: ELF::SectionHeader.new.shsymtab! }, - } - @debug = debug - end - - def assemble(assemble_command: "as", assemble_options: [], input: @input, f: @target_file) - read = { main: false } - program_size = 0 - text = @section[:text][:body] - File.open(input, "r") do |r| - r.each_line do |line| - read[:main] = /main:/.match(line) unless read[:main] - next unless read[:main] && !/main:/.match(line) - text.assemble!(line) - end - end - f.write(@elf_header.build!) - bins = [] - section_headers = [] - @sections.values.map do |section| - bins << section[:body].build - section_headers << section[:header].build - end - - f.close - f.path - end - - private - - def section_header(str_section_names:) - [ - NOTE_GNU_PROPERTY_SECTION, - SYMTAB_SECTION, - start_table_section(str_section_names), - SHSYMTAB_SECTION, - ] - end - - def start_table_section(main = "main") - b = main.bytes << 0 - b.unshift 0 until b.size % 2 ==0 - b.pack("C*") - end - - def null_section_header = section_headers - - def text_section_header(name: [0x1b, *[0] * 3], offset: [0x40, *[0]* 7], size:) - type = [1, *[0] * 3] - flags = [6, *[0] * 7] - addralign = [1, *[0] * 7] - section_headers(name:, type:, flags:, size:, offset:, addralign:) - end - - def data_section_header(offset: [0x72, *[0]*7]) - name = [0x21, *[0] * 3] - type = [1, *[0] * 3] - flags = [3, *[0] * 7] - addralign = [1, *[0] * 7] - section_headers(name:, type:, flags:, offset:, addralign:) - end - - def bss_section_header(offset: [0x72, *[0]*7]) - name = [0x27, *[0] * 3] - type = [8, *[0] * 3] - flags = [3, *[0] * 7] - addralign = [1, *[0]* 7] - section_headers(name:, type:, flags:, offset:, addralign:) - end - - def note_section_header(offset: [0x78, *[0]*7]) - name = [0x2c, *[0] * 3] - type = [0x07, *[0] * 3] - flags = [0x02, *[0] * 7] - size = [0x30, *[0] * 7] - addralign = [0x08, *[0] * 7] - section_headers(name:, type:, flags:, offset:, size:, addralign:) - end - - def symtab_section_header(name: [0x01, *[0] * 3], offset: [0xa8, *[0] * 7], size: [0x30, *[0] * 7]) - type = [0x02, *[0]* 3] - link = [0x06, *[0] * 3] - info = [0x01, *[0] * 3] - addralign = [0x08, *[0] * 7] - entsize = [0x18, *[0] * 7] - section_headers(name:, type:, offset:, size:, link:, info:, addralign:, entsize:) - end - - def strtab_section_header(name: [0x09, *[0] * 3], offset: [0xd8, *[0] * 7], size:) - type = [0x03, *[0] * 3] - addralign = [0x01, *[0]*7] - section_headers(name:, type:, offset:, size:, addralign:) - end - - def shstrtab_section_header(name: [0x11, *[0] * 3], offset: [0xde, *[0] * 7], size: [0x3f, *[0] * 7]) - type = [0x03, *[0] * 3] - addralign = [0x01, *[0] * 7] - section_headers(name:, type:, offset:, size:, addralign:) - end - - def section_headers( - name: [0]*4, type: [0]*4, flags: [0]*8, addr: [0]*8, - offset: [0]*8, size: [0]*8, link: [0]*4, info: [0]*4, - addralign: [0]*8, entsize: [0]*8) = - [name, type, flags, addr, offset, size, link, info, addralign, entsize].flatten.pack("C*") - - def opecode(op, args) - case op - when "push" - push(args) - when "mov" - [PREFIX[:REX_W], *mov(op, *args)] - when "sub", "add", "imul", "cqo", "idiv" - [PREFIX[:REX_W], *calc(op, *args)] - when "pop" - pop(args) - when "ret" - [0xc3] - end - end - - def mov(op, *arguments) - reg = case arguments - in ["rbp", "rsp"] - [0xe5] - in ["rsp", "rbp"] - [0xec] - else - arguments&.map { reg(_1) } - end - [OPECODE[op.upcase.to_sym], *reg] - end - - def calc(op, *arguments) - ope_code = OPECODE[op.upcase.to_sym] - case [op, *arguments] - 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 ["cqo"] - [0x99] - end - end - - def push(args) - case args - in ["rbp"] | ["rdi"] - [0x55] - in ["rax"] - [0x50] - else - [0x6a, *args.map { reg(_1) }] - end - end - - def pop(args) - case args - in ["rax"] | ["rdi"] - [0x58 + REGISTER_CODE[args.first.upcase.to_sym]] - in ["rbp"] - [0x5d] - end - end +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", type: :relocator, debug: false) + @input, @output = input, output + @elf_header = ELF::Header.new(type:) + @sections = { + null: { body: nil, header: ELF::SectionHeader.new.null! }, + text: { body: ELF::Section::Text.new, header: ELF::SectionHeader.new.text! }, + data: { body: ELF::Section::Data.new, header: ELF::SectionHeader.new.data! }, + bss: { body: ELF::Section::BSS.new, header: ELF::SectionHeader.new.bss! }, + note: { body: ELF::Section::Note.new.gnu_property!, header: ELF::SectionHeader.new.note! }, + symtab: { body: ELF::Section::Symtab.new, header: ELF::SectionHeader.new.symtab! }, + strtab: { body: ELF::Section::Strtab.new, header: ELF::SectionHeader.new.strtab! }, + shsymtab: { body: ELF::Section::Shsymtab.new, header: ELF::SectionHeader.new.shsymtab! }, + } + @debug = debug + end - def reg(r) - case r - in "rsp" - 0xec - in "rbp" - 0x5e - in "rax" - 0x29 - in "rdi" - 0xf8 - in /\d+/ - ("%02x" % r).to_i(16) - end + def assemble(assemble_command: "as", assemble_options: [], input: @input, output: @output) + f = File.open(output, "wb") + read = { main: false } + program_size = 0 + text = @sections[:text][:body] + File.open(input, "r") do |r| + r.each_line do |line| + read[:main] = /main:/.match(line) unless read[:main] + next unless read[:main] && !/main:/.match(line) + text.assemble!(line) end end + f.write(@elf_header.build) + bins = [] + section_headers = [] + @sections.values.map do |section| + bins << section[:body].build + section_headers << section[:header].build + end + + f.close + f.path end end diff --git a/lib/vaporware/compiler/generator.rb b/lib/vaporware/compiler/generator.rb index e432d86..3526dfd 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,7 +52,7 @@ def register_var_and_method(node) nil end - def already_build_methods? = defined_methods.sort == @doned.to_a.sort + def already_build_methods? = @defined_methods.sort == @doned.to_a.sort def epilogue(output) output.puts " mov rsp, rbp" @@ -38,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 @@ -75,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| @@ -89,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 @@ -184,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/sig/vaporware/compiler.rbs b/sig/vaporware/compiler.rbs index 1ab0dbc..39ca913 100644 --- a/sig/vaporware/compiler.rbs +++ b/sig/vaporware/compiler.rbs @@ -1,11 +1,13 @@ class Vaporware::Compiler # 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 initialize: (input: String, ?output: String, ?debug: bool, ?shared: bool) -> void @generator: Vaporware::Compiler::Generator + @assembler: Vaporware::Compiler::Assembler # instance methods - def assemble: (?output: String, ?compiler: String, ?compiler_options: Array[String]) -> void - def link: (?output: String, ?linker: String, ?linker_options: Array[String]) -> void - 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 index d28d2ac..82a784b 100644 --- a/sig/vaporware/compiler/assembler.rbs +++ b/sig/vaporware/compiler/assembler.rbs @@ -1,8 +1,9 @@ class Vaporware::Compiler::Assembler @input: String @output: String - @target_file: File @elf_header: Vaporware::Compiler::Assembler::ELF::Header - def initialize: (String, ?String, type: Symbol, debug: bool) -> void - def assemble: (?asemble_command: String, ?assemble_options: Array[String], input: String, f: File) -> void + @debug: bool + + def initialize: (input: String, ?output: String, ?type: Symbol, ?debug: bool) -> void + def assemble: (?assemble_command: String, ?assemble_options: Array[String], ?input: String, ?output: String) -> void end diff --git a/sig/vaporware/compiler/generator.rbs b/sig/vaporware/compiler/generator.rbs index 954a00d..82bad73 100644 --- a/sig/vaporware/compiler/generator.rbs +++ b/sig/vaporware/compiler/generator.rbs @@ -1,29 +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_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 @@ -32,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