Skip to content

Commit 4289ba2

Browse files
authored
Merge pull request #11 from katsyoshi/compile-to-elf
Compile to elf
2 parents 94a6bdf + 812c752 commit 4289ba2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1553
-120
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ jobs:
1414
strategy:
1515
matrix:
1616
ruby:
17-
- '3.2'
18-
- '3.3'
1917
- '3.4'
2018

2119
steps:
@@ -25,6 +23,7 @@ jobs:
2523
with:
2624
ruby-version: ${{ matrix.ruby }}
2725
bundler-cache: true
26+
- uses: rui314/setup-mold@v1
2827
- name: Install rbs
2928
run: bundle exec rbs collection install
3029
- name: Run type check

Rakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require "steep/cli"
88
task default: %i[test]
99

1010
Rake::TestTask.new do |t|
11-
t.test_files = FileList['test/test*.rb']
11+
t.test_files = FileList['test/test*.rb', 'test/**/test*.rb']
1212
end
1313

1414
namespace :steep do

exe/vaporware

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ require "vaporware"
44
require "optparse"
55
opt = OptionParser.new
66
options = {}
7-
opt.on("-c", "--compiler [VAL]", "this option is selecting compiler precompiled file, default: gcc") { |v| options[:compiler] = v }
7+
opt.on("-c", "--compiler[=VAL]", "this option is selecting compiler precompiled file, default: \"self\"") { |v| options[:compiler] = v }
8+
opt.on("-a", "--assembler[=VAL]", "this option is selecting assembler assembler file, default: \"as\"") { |v| options[:assembler] = v }
89
opt.on("-D", "--debug") { |v| options[:debug] = v }
9-
opt.on("-o", "--objects [VAL]") { |v| options[:dest] = v }
10+
opt.on("-o", "--objects[=VAL]") { |v| options[:dest] = v }
1011
opt.on("--compiler-options[=VAL]", "compiler options") { |v| options[:compiler_options] = v.split(",") }
11-
opt.on("-s", "--shared") { |v| options[:shared] = v }
12+
opt.on("-s", "--shared-library") { |v| options[:shared] = v }
13+
opt.on("-l", "--linker[=VAL]", "selecting linker: gold, lld, and mold, default: \"gold\".") { |v| options[:linker] = v }
1214

1315
begin
1416
opt.parse!(ARGV)

lib/vaporware/compiler.rb

Lines changed: 18 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,26 @@
11
# frozen_string_literal: true
22

33
require_relative "compiler/generator"
4+
require_relative "compiler/assembler"
5+
require_relative "compiler/linker"
46

5-
module Vaporware
6-
# Your code goes here...
7-
class Compiler
8-
def self.compile(source, compiler: "gcc", dest: "tmp", debug: false, compiler_options: ["-O0"], shared: false)
9-
_precompile = "#{dest}.s"
10-
s = new(source, _precompile: _precompile, debug:, shared:)
11-
s.compile(compiler:, compiler_options:)
12-
end
7+
class Vaporware::Compiler
8+
attr_reader *%i(generator assembler linker)
139

14-
def initialize(source, _precompile: "tmp.s", debug: false, shared: false)
15-
@generator = Vaporware::Compiler::Generator.new(source, precompile: _precompile, debug:, shared:)
16-
end
17-
18-
def compile(compiler: "gcc", compiler_options: ["-O0"])
19-
@generator.register_var_and_method(@generator.ast)
10+
def self.compile(source, assembler: "as", linker: "ld", dest: "tmp", debug: false, compiler_options: ["-O0"], shared: false)
11+
compiler = new(input: source, output: dest, debug:, shared:, linker:, assembler:)
12+
compiler.compile(compiler_options:)
13+
compiler.assemble(input: dest.to_s + ".s", assembler:, debug:)
14+
compiler.link
15+
end
2016

21-
output = File.open(@generator.precompile, "w")
22-
# prologue
23-
output.puts ".intel_syntax noprefix"
24-
if @generator.defined_methods.empty?
25-
@generator.main = true
26-
output.puts ".globl main"
27-
output.puts "main:"
28-
output.puts " push rbp"
29-
output.puts " mov rbp, rsp"
30-
output.puts " sub rsp, #{@generator.defined_variables.size * 8}"
31-
@generator.to_asm(@generator.ast, output)
32-
# epilogue
33-
@generator.epilogue(output)
34-
else
35-
@generator.prologue_methods(output)
36-
output.puts ".globl main" unless @generator.shared
37-
@generator.to_asm(@generator.ast, output)
38-
# epilogue
39-
@generator.epilogue(output)
40-
end
41-
output.close
42-
compiler_options += @generator.compile_shared_option if @generator.shared
43-
@generator.call_compiler(compiler:, compiler_options:)
44-
end
17+
def initialize(input:, output: File.basename(input, ".*"), linker: "ld", assembler: "as", debug: false, shared: false)
18+
@generator = Vaporware::Compiler::Generator.new(input:, output: output + ".s", debug:, shared:)
19+
@assembler = Vaporware::Compiler::Assembler.new(input: @generator.precompile, output: output + ".o", assembler:, debug:)
20+
@linker = Vaporware::Compiler::Linker.new(input: @assembler.obj_file, output:, linker:, debug:, shared:)
4521
end
22+
23+
def assemble(input:, output: File.basename(input, ".*") + ".o", assembler: "as", assembler_options: [], debug: false) = @assembler.assemble(input:, output:, assembler:, assembler_options:, debug:)
24+
def link = @linker.link
25+
def compile(compiler_options: ["-O0"]) = @generator.compile
4626
end
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# frozen_string_literal: true
2+
require_relative "assembler/elf"
3+
require_relative "assembler/elf/utils"
4+
require_relative "assembler/elf/header"
5+
require_relative "assembler/elf/sections"
6+
require_relative "assembler/elf/section_header"
7+
8+
class Vaporware::Compiler::Assembler
9+
GCC_ASSEMBLERS = ["gcc", "as"]
10+
CLANG_ASSEMBLERS = ["clang", "llvm"]
11+
ASSEMBLERS = GCC_ASSEMBLERS + CLANG_ASSEMBLERS
12+
class Error < StandardError; end
13+
14+
def self.assemble!(input, output = File.basename(input, ".*") + ".o", options = {}) = new(input:, output:, **options).assemble
15+
16+
def initialize(input:, output: File.basename(input, ".*") + ".o", assembler: "as", type: :relocatable, debug: false)
17+
@input, @output = input, output
18+
@elf = ELF.new(type:, input:, output:, debug:)
19+
@assembler = assembler
20+
@debug = debug
21+
end
22+
23+
def assemble(assembler: @assembler, assembler_options: [], input: @input, output: @output, debug: false)
24+
if ASSEMBLERS.include?(assembler)
25+
IO.popen([command(assembler), *assembler_options, "-o", output, input].join(" ")).close
26+
else
27+
to_elf(input:, output:, debug:)
28+
end
29+
30+
output
31+
end
32+
def obj_file = @output
33+
def to_elf(input: @input, output: @output, debug: false) = @elf.build(input:, output:, debug:)
34+
35+
def command(asm)
36+
case asm
37+
when "as", "gcc"
38+
gcc_assembler(asm)
39+
when "clang", "llvm"
40+
clang_assembler(asm)
41+
else
42+
raise Error, "Invalid assembler command: #{asm}"
43+
end
44+
end
45+
46+
private
47+
def gcc_assembler(assembler)
48+
case assembler
49+
when "as", "gcc"
50+
"as"
51+
else
52+
raise Error, "Invalid assembler command: #{assembler}"
53+
end
54+
end
55+
56+
def clang_assembler(assembler)
57+
case assembler
58+
when "clang"
59+
"clang"
60+
when "llvm"
61+
"clang -cc1as -triple x86_64-pc-linux-gnu -filetype obj -target-cpu x86-64 -mrelocation-model pic"
62+
else
63+
raise Error, "Invalid assembler command: #{assembler}"
64+
end
65+
end
66+
end
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
class Vaporware::Compiler::Assembler
2+
class ELF
3+
class Error < StandardError; end
4+
class Section; end
5+
class SectionHeader; end
6+
module Utils; end
7+
8+
def initialize(type:, input:, output:, debug:)
9+
@input, @output = input, output
10+
@header = Header.new(type:)
11+
@sections = Sections.new
12+
end
13+
14+
def build(input: @input, output: @output, debug: false)
15+
program_size = 0
16+
read!(input:)
17+
init_assemble!
18+
19+
offset = 0x40
20+
section_headers = []
21+
names = []
22+
bodies = {
23+
null: nil,
24+
text: nil,
25+
data: nil,
26+
bss: nil,
27+
note: nil,
28+
symtab: nil,
29+
strtab: nil,
30+
shstrtab: nil,
31+
}
32+
name_idx = 0
33+
padding = nil
34+
@sections.each do |section|
35+
name = section.name
36+
names << name
37+
section.body.set!(name: names.join) if name == "\0.shstrtab"
38+
bin = section.body.build
39+
size = bin.bytesize
40+
bin << "\0" until (bin.bytesize % 8) == 0 if ["\0.text", "\0.shstrtab"].include?(name)
41+
bin << "\0" until ((bin.bytesize + offset) % 8) == 0 if ["\0.shstrtab"].include?(name)
42+
bodies[section.section_name.to_sym] = bin
43+
header = section.header
44+
if offset > 0x40 && size > 0 && padding&.>(0)
45+
offset += padding
46+
padding = nil
47+
end
48+
padding = bin.size - size if size > 0
49+
header.set!(name: name_idx, offset:, size:) unless name == ""
50+
offset += size
51+
section_headers << header.build
52+
name_idx += name == "" ? 1 : name.size
53+
end
54+
@header.set!(shoffset: offset + padding)
55+
w = File.open(output, "wb")
56+
w.write([@header.build, *bodies.values, *section_headers].join)
57+
w.close
58+
[@header.build, *bodies.values, *section_headers]
59+
end
60+
61+
private
62+
def init_assemble! = (note!; symtab!)
63+
64+
def read!(input: @input, text: @sections.text.body)
65+
read = { main: false }
66+
File.open(input, "r") do |r|
67+
r.each_line do |line|
68+
read[:main] = line.match(/main:/) unless read[:main]
69+
next unless read[:main] && !/main:/.match(line)
70+
next if /\.L.+:/.match(line)
71+
text.assemble!(line)
72+
end
73+
end
74+
end
75+
def note! = @sections.note.body.null!
76+
def symtab! = @sections.symtab.body.set!(entsize: 0x18, name: 1, info: 0x10, other: 0, shndx: 1)
77+
end
78+
end
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
require_relative "../elf"
2+
3+
class Vaporware::Compiler::Assembler::ELF::Header
4+
include Vaporware::Compiler::Assembler::ELF::Utils
5+
IDENT = [0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00].freeze
6+
ELF_FILE_TYPE = { NONE: 0, REL: 1, EXEC: 2, DYN: 3, CORE: 4 }.freeze
7+
8+
def initialize(endian: :littel, type: :rel, arc: :amd64)
9+
@ident = IDENT
10+
@type = num2bytes(ELF_FILE_TYPE[elf(type)], 2)
11+
@arch = arch(arc)
12+
@version = num2bytes(1, 4)
13+
@entry = num2bytes(0x00, 8)
14+
@phoffset = num2bytes(0x00, 8)
15+
@shoffset = num2bytes(0x00, 8)
16+
@flags = num2bytes(0x00, 4)
17+
@ehsize = num2bytes(0x40, 2)
18+
@phsize = num2bytes(0x00, 2)
19+
@phnum = num2bytes(0x00, 2)
20+
@shentsize = num2bytes(0x40, 2)
21+
@shnum = num2bytes(0x08, 2)
22+
@shstrndx = num2bytes(0x07, 2)
23+
end
24+
25+
def build = bytes.flatten.pack("C*")
26+
27+
def set!(entry: nil, phoffset: nil, shoffset: nil, shnum: nil, shstrndx: nil)
28+
@entry = num2bytes(entry, 8) if check(entry, 8)
29+
@phoffset = num2bytes(phoffset, 8) if check(phoffset, 8)
30+
@shoffset = num2bytes(shoffset, 8) if check(shoffset, 8)
31+
@shnum = num2bytes(shnum, 4) if check(shnum, 4)
32+
@shstrndx = num2bytes(shstrndx, 4) if check(shstrndx, 4)
33+
end
34+
35+
private
36+
37+
def bytes = [
38+
@ident, @type, @arch, @version, @entry, @phoffset,
39+
@shoffset, @flags, @ehsize, @phsize, @phnum, @shentsize,
40+
@shnum, @shstrndx
41+
]
42+
43+
def arch(machine)
44+
case machine.to_s
45+
in "amd64" | "x86_64" | "x64"
46+
[0x3e, 0x00]
47+
end
48+
end
49+
50+
def elf(type)
51+
case type.to_s
52+
in "relocatable" | "rel"
53+
:REL
54+
in "exe" | "ex" | "exec"
55+
:EXEC
56+
in "shared" | "share" | "dynamic" | "dyn"
57+
:DYN
58+
else
59+
:NONE
60+
end
61+
end
62+
end
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
require_relative "section/text"
2+
require_relative "section/bss"
3+
require_relative "section/data"
4+
require_relative "section/note"
5+
require_relative "section/null"
6+
require_relative "section/symtab"
7+
require_relative "section/strtab"
8+
require_relative "section/shstrtab"
9+
require_relative "section_header"
10+
11+
class Vaporware::Compiler::Assembler::ELF::Section
12+
attr_reader :header, :body, :name, :section_name
13+
def initialize(type:, options: {})
14+
type_string = type.to_s.capitalize
15+
type_string = type_string.upcase if type_string == "Bss"
16+
@section_name = type_string.downcase
17+
@name = section_name == "null" ? "" : "\0.#{section_name}"
18+
@header = Vaporware::Compiler::Assembler::ELF::SectionHeader.new.send("#{@section_name}!")
19+
@body = Module.const_get("Vaporware::Compiler::Assembler::ELF::Section::#{type_string}").new(**options)
20+
end
21+
22+
def name=(name)
23+
@name = name
24+
end
25+
end
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class Vaporware::Compiler::Assembler::ELF::Section::BSS
2+
include Vaporware::Compiler::Assembler::ELF::Utils
3+
def initialize(**opts) = nil
4+
def build = bytes.flatten.pack("C*")
5+
def set! = self
6+
7+
private
8+
def bytes = []
9+
def check(val, bytes) = false
10+
end
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class Vaporware::Compiler::Assembler::ELF::Section::Data
2+
include Vaporware::Compiler::Assembler::ELF::Utils
3+
def initialize(**opts) = nil
4+
def build = bytes.flatten.pack("C*")
5+
def set! = self
6+
private def bytes = []
7+
end

0 commit comments

Comments
 (0)