Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
1228cbb
change compile method to to_elf method
katsyoshi May 20, 2023
c44ff58
fix missreading comment
katsyoshi May 20, 2023
e496a44
add interface for to_elf
katsyoshi May 22, 2023
513edc9
assemble class for assembling
katsyoshi Aug 7, 2023
b09e3f7
pass only sample/plus.rb
katsyoshi Aug 18, 2023
3e24e1a
refactoring assemble.rb
katsyoshi Aug 24, 2023
cf5fb6d
rename assemble to assembler
katsyoshi Aug 31, 2023
3dba8cf
add rbs for refactoringed compiler.rbs
katsyoshi Sep 1, 2023
343a563
reduction nest
katsyoshi Sep 5, 2023
70202ec
refactoring testing interface
katsyoshi Sep 5, 2023
b943f74
use inner classes for compile
katsyoshi Sep 10, 2023
092e97b
link shared library linking object files
katsyoshi Sep 10, 2023
16f65b3
pass ci
katsyoshi Sep 13, 2023
27a1e42
pass ci
katsyoshi Sep 15, 2023
2fa4b79
replaced class files for elf sections
katsyoshi Sep 18, 2023
11ff264
replace utils for common methods
katsyoshi Sep 23, 2023
f9a1107
fix name to Shstrtab
katsyoshi Oct 3, 2023
f02d48a
fix replace variable name and use text body
katsyoshi Oct 3, 2023
bbc9a92
add setter name for Shstrtab
katsyoshi Oct 3, 2023
4038e7e
fill section headers binary
katsyoshi Oct 8, 2023
cfa5216
use directive for Assembler::ELF
katsyoshi Oct 17, 2023
9fa97ca
reset hex align 2 times for bytes
katsyoshi Oct 17, 2023
c6f2def
set class name for exceptions
katsyoshi Nov 12, 2023
bbb83d2
fix split to scan in hexas method
katsyoshi Nov 14, 2023
e0dcdb9
set null for unused section's section header
katsyoshi Dec 29, 2023
df76f1d
add :BEGIN node for ruby 3.3
katsyoshi Jan 1, 2024
44b1398
check debug log
katsyoshi Jan 3, 2024
6ba6f49
change default assembler gcc to (g)as
katsyoshi Jan 7, 2024
3ed73ec
fix option description using = for args value
katsyoshi Jan 8, 2024
82e6464
notify elf header fields error and add elf header test
katsyoshi Feb 11, 2024
96eda19
add test for note section
katsyoshi Mar 2, 2024
e2e699c
fix note implementation
katsyoshi Mar 2, 2024
9522902
add test for shstrtab and fix implementation
katsyoshi Mar 2, 2024
0157fb4
fix types for implementations
katsyoshi Mar 2, 2024
0fdd912
add shstrtab for twice
katsyoshi Mar 2, 2024
0da8b9a
don't understanding of elf
katsyoshi Mar 9, 2024
26e06ae
add test for null section symtab
katsyoshi Mar 11, 2024
6d64bc2
update rbs collection
katsyoshi May 4, 2024
4d97d9a
set name bytes
katsyoshi May 4, 2024
783bf1c
forget adding & for method calling
katsyoshi Jul 21, 2024
6bbe27d
remove hand assemble sources from each
katsyoshi Jul 22, 2024
26bb0c2
set keyword arguments for section classes
katsyoshi Aug 7, 2024
a597ad2
add integration test
katsyoshi Aug 10, 2024
c2680aa
tmp
katsyoshi Aug 11, 2024
82d3873
check header from integration file
katsyoshi Aug 12, 2024
1213c70
create test assembler file
katsyoshi Aug 19, 2024
72709ff
set section headers
katsyoshi Aug 24, 2024
7ecb647
set paddings
katsyoshi Aug 25, 2024
c054511
fix elf header for section header offset bytes
katsyoshi Aug 25, 2024
f9340e4
remove unnecessary test
katsyoshi Aug 31, 2024
4b58d14
wrong setup and teadown
katsyoshi Aug 31, 2024
28ea0b9
add using assembler option and commented default linker
katsyoshi Sep 12, 2024
6dad8a8
fix error class for rbs
katsyoshi Sep 13, 2024
736e81a
use self made note
katsyoshi Sep 15, 2024
1ed018f
migrate building elf file to ELF class from Assembler class
katsyoshi Sep 15, 2024
fb56b96
add mov operation
katsyoshi Sep 19, 2024
1af7f69
support if condition opecodes
katsyoshi Sep 19, 2024
a14b982
support else jump
katsyoshi Sep 19, 2024
aeec9a3
ignore steep for pattern matching
katsyoshi Mar 12, 2025
2de6bbf
use variable arguments for opecode method
katsyoshi Mar 12, 2025
0ac7f00
support clang for compile
katsyoshi Mar 12, 2025
a67af3a
:lipstick: cosmetic change
katsyoshi Mar 30, 2025
1e81bee
support syscall in assembler, and compiler
katsyoshi May 16, 2025
16b92c6
support over ruby 3.4
katsyoshi May 17, 2025
63aa978
unsupported assembler code
katsyoshi May 17, 2025
35a65aa
pass test
katsyoshi May 17, 2025
97f5590
use immediate method
katsyoshi May 17, 2025
812c752
use mold in gha
katsyoshi May 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ jobs:
strategy:
matrix:
ruby:
- '3.2'
- '3.3'
- '3.4'

steps:
Expand All @@ -25,6 +23,7 @@ jobs:
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- uses: rui314/setup-mold@v1
- name: Install rbs
run: bundle exec rbs collection install
- name: Run type check
Expand Down
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 5 additions & 3 deletions exe/vaporware
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
56 changes: 18 additions & 38 deletions lib/vaporware/compiler.rb
Original file line number Diff line number Diff line change
@@ -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
66 changes: 66 additions & 0 deletions lib/vaporware/compiler/assembler.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# 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
GCC_ASSEMBLERS = ["gcc", "as"]
CLANG_ASSEMBLERS = ["clang", "llvm"]
ASSEMBLERS = GCC_ASSEMBLERS + CLANG_ASSEMBLERS
class Error < StandardError; end

def self.assemble!(input, output = File.basename(input, ".*") + ".o", options = {}) = new(input:, output:, **options).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 ASSEMBLERS.include?(assembler)
IO.popen([command(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:)

def command(asm)
case asm
when "as", "gcc"
gcc_assembler(asm)
when "clang", "llvm"
clang_assembler(asm)
else
raise Error, "Invalid assembler command: #{asm}"
end
end

private
def gcc_assembler(assembler)
case assembler
when "as", "gcc"
"as"
else
raise Error, "Invalid assembler command: #{assembler}"
end
end

def clang_assembler(assembler)
case assembler
when "clang"
"clang"
when "llvm"
"clang -cc1as -triple x86_64-pc-linux-gnu -filetype obj -target-cpu x86-64 -mrelocation-model pic"
else
raise Error, "Invalid assembler command: #{assembler}"
end
end
end
78 changes: 78 additions & 0 deletions lib/vaporware/compiler/assembler/elf.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
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
62 changes: 62 additions & 0 deletions lib/vaporware/compiler/assembler/elf/header.rb
Original file line number Diff line number Diff line change
@@ -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
25 changes: 25 additions & 0 deletions lib/vaporware/compiler/assembler/elf/section.rb
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions lib/vaporware/compiler/assembler/elf/section/bss.rb
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions lib/vaporware/compiler/assembler/elf/section/data.rb
Original file line number Diff line number Diff line change
@@ -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
33 changes: 33 additions & 0 deletions lib/vaporware/compiler/assembler/elf/section/note.rb
Original file line number Diff line number Diff line change
@@ -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
Loading