Skip to content

Commit

Permalink
Ensure instruction set does not need to depend on architectures.
Browse files Browse the repository at this point in the history
asoffer committed Feb 2, 2024
1 parent 5c30ec3 commit b4c622a
Showing 7 changed files with 258 additions and 70 deletions.
15 changes: 12 additions & 3 deletions examples/brainfuck/BUILD
Original file line number Diff line number Diff line change
@@ -26,8 +26,6 @@ cc_library(
hdrs = ["instructions.h"],
deps = [
"//jasmin/core:instruction",
"//jasmin/compile/x64:code_generator",
"//jasmin/compile/x64:location_map",
],
)

@@ -50,9 +48,20 @@ cc_binary(
":build",
":file",
":instructions",
":x64_code_generator",
"//jasmin/core:function",
"//jasmin/ssa",
"//jasmin/compile/x64:code_generator",
"//jasmin/compile/x64:function_emitter",
"@nth_cc//nth/dynamic:jit_function",
],
)

cc_library(
name = "x64_code_generator",
hdrs = ["x64_code_generator.h"],
deps = [
"//jasmin/core:instruction",
"//jasmin/compile/x64:function_emitter",
"//jasmin/compile/x64:location_map",
],
)
60 changes: 0 additions & 60 deletions examples/brainfuck/instructions.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
#ifndef EXAMPLES_BRAINFUCK_INSTRUCTIONS_H
#define EXAMPLES_BRAINFUCK_INSTRUCTIONS_H

#include "jasmin/compile/x64/code_generator.h"
#include "jasmin/compile/x64/location_map.h"
#include "jasmin/core/instruction.h"
#include "nth/debug/debug.h"

@@ -19,19 +17,6 @@ struct Initialize : jasmin::Instruction<Initialize> {
jasmin::Output<uint8_t *> out) {
out.set(state.buffer);
}

static void generate_code(jasmin::x64::CodeGenerator &gen,
jasmin::LocationMap const &) {
gen.write({0x48, 0x81, 0xec, 0x30, 0x75, 0x00, 0x00}); // sub rsp, 0x7530
gen.mov(jasmin::x64::Register::rsi, jasmin::x64::Register::rsp);
gen.write({
0xba, 0x01, 0x00, 0x00, 0x00, // mov edx, 0x1
0x48, 0x89, 0xe7, // mov rdi, rsp
0x31, 0xc0, // xor eax eax
0x48, 0xc7, 0xc1, 0x30, 0x75, 0x00, 0x00, // mov rcx, 0x1000
0xf3, 0xaa, // rep stos BYTE PTR es:[rdi],al
});
}
};

struct Increment : jasmin::Instruction<Increment> {
@@ -40,11 +25,6 @@ struct Increment : jasmin::Instruction<Increment> {
jasmin::Output<>) {
++*in.get<0>();
}

static void generate_code(jasmin::x64::CodeGenerator &gen,
jasmin::LocationMap const &) {
gen.write({0x80, 0x06, 0x01}); // add BYTE PTR [rsi], 0x1
}
};

struct Decrement : jasmin::Instruction<Decrement> {
@@ -53,11 +33,6 @@ struct Decrement : jasmin::Instruction<Decrement> {
jasmin::Output<>) {
--*in.get<0>();
}

static void generate_code(jasmin::x64::CodeGenerator &gen,
jasmin::LocationMap const &) {
gen.write({0x80, 0x2e, 0x01}); // sub BYTE PTR [rsi], 0x1
}
};

struct Left : jasmin::Instruction<Left> {
@@ -68,11 +43,6 @@ struct Left : jasmin::Instruction<Left> {
NTH_REQUIRE(ptr != state.buffer);
out.set(ptr - 1);
}

static void generate_code(jasmin::x64::CodeGenerator &gen,
jasmin::LocationMap const &) {
gen.write({0x48, 0x8d, 0x76, 0xff}); // lea rsi, [rsi - 1]
}
};

struct Right : jasmin::Instruction<Right> {
@@ -83,11 +53,6 @@ struct Right : jasmin::Instruction<Right> {
NTH_REQUIRE(ptr < state.buffer + state.buffer_size);
out.set(ptr + 1);
}

static void generate_code(jasmin::x64::CodeGenerator &gen,
jasmin::LocationMap const &) {
gen.write({0x48, 0x8d, 0x76, 0x01}); // lea rsi, [rsi + 1]
}
};

struct Input : jasmin::Instruction<Input> {
@@ -97,16 +62,6 @@ struct Input : jasmin::Instruction<Input> {
auto [ptr] = in;
*ptr = static_cast<uint8_t>(std::getchar());
}

static void generate_code(jasmin::x64::CodeGenerator &gen,
jasmin::LocationMap const &) {
gen.write({
0x48, 0xc7, 0xc0, 0x00, 0x00, 0x00, 0x00, // mov rax, 0x0
0x48, 0xc7, 0xc7, 0x00, 0x00, 0x00, 0x00, // mov rdi, 0x0
0x48, 0xc7, 0xc2, 0x01, 0x00, 0x00, 0x00, // mov rdx, 0x1
});
gen.syscall();
}
};

struct Output : jasmin::Instruction<Output> {
@@ -115,16 +70,6 @@ struct Output : jasmin::Instruction<Output> {
jasmin::Output<>) {
std::putchar(static_cast<char>(*in.get<0>()));
}

static void generate_code(jasmin::x64::CodeGenerator &gen,
jasmin::LocationMap const &) {
gen.write({
0x48, 0xc7, 0xc0, 0x01, 0x00, 0x00, 0x00, // mov rax, 0x1
0x48, 0xc7, 0xc7, 0x01, 0x00, 0x00, 0x00, // mov rdi, 0x1
0x48, 0xc7, 0xc2, 0x01, 0x00, 0x00, 0x00, // mov rdx, 0x1
});
gen.syscall();
}
};

struct Zero : jasmin::Instruction<Zero> {
@@ -133,11 +78,6 @@ struct Zero : jasmin::Instruction<Zero> {
jasmin::Output<bool> out) {
out.set(*in.get<0>() == 0);
}

static void generate_code(jasmin::x64::CodeGenerator &gen,
jasmin::LocationMap const &) {
gen.write({0x8a, 0x06}); // mov al, BYTE PTR [rsi]
}
};

using Instructions =
11 changes: 7 additions & 4 deletions examples/brainfuck/jit.cc
Original file line number Diff line number Diff line change
@@ -3,7 +3,8 @@
#include "examples/brainfuck/build.h"
#include "examples/brainfuck/file.h"
#include "examples/brainfuck/instructions.h"
#include "jasmin/compile/x64/code_generator.h"
#include "examples/brainfuck/x64_code_generator.h"
#include "jasmin/compile/compiled_function.h"
#include "jasmin/ssa/ssa.h"
#include "nth/dynamic/jit_function.h"

@@ -23,10 +24,12 @@ int main(int argc, char* argv[]) {

auto& f = std::get<jasmin::Function<bf::Instructions>>(fn_or_parse_error);

// Generate executable code for the function, storing it in `code`.
jasmin::CompiledFunction code;
jasmin::x64::CodeGenerator gen(nth::type<bf::Instructions>);
gen.function(jasmin::SsaFunction(f), code);
{ // Generate executable code for the function, storing it in `code`.
bf::X64CodeGenerator gen;
jasmin::x64::FunctionEmitter emitter(nth::type<bf::Instructions>, gen);
emitter.emit(jasmin::SsaFunction(f), code);
}

// Construct a JIT-compiled function from the code.
nth::jit_function<void()> jitted_fn(code);
76 changes: 76 additions & 0 deletions examples/brainfuck/x64_code_generator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#ifndef EXAMPLES_BRAINFUCK_X64_CODE_GENERATOR_H
#define EXAMPLES_BRAINFUCK_X64_CODE_GENERATOR_H

#include "jasmin/compile/x64/function_emitter.h"
#include "jasmin/compile/x64/location_map.h"
#include "nth/meta/type.h"

namespace bf {

struct X64CodeGenerator {
void operator()(decltype(nth::type<Initialize>),
jasmin::x64::FunctionEmitter &gen,
jasmin::LocationMap const &) {
gen.write({0x48, 0x81, 0xec, 0x30, 0x75, 0x00, 0x00}); // sub rsp, 0x7530
gen.mov(jasmin::x64::Register::rsi, jasmin::x64::Register::rsp);
gen.write({
0xba, 0x01, 0x00, 0x00, 0x00, // mov edx, 0x1
0x48, 0x89, 0xe7, // mov rdi, rsp
0x31, 0xc0, // xor eax eax
0x48, 0xc7, 0xc1, 0x30, 0x75, 0x00, 0x00, // mov rcx, 0x1000
0xf3, 0xaa, // rep stos BYTE PTR es:[rdi],al
});
}

void operator()(decltype(nth::type<Increment>),
jasmin::x64::FunctionEmitter &gen,
jasmin::LocationMap const &) {
gen.write({0x80, 0x06, 0x01}); // add BYTE PTR [rsi], 0x1
}

void operator()(decltype(nth::type<Decrement>),
jasmin::x64::FunctionEmitter &gen,
jasmin::LocationMap const &) {
gen.write({0x80, 0x2e, 0x01}); // sub BYTE PTR [rsi], 0x1
}

void operator()(decltype(nth::type<Left>), jasmin::x64::FunctionEmitter &gen,
jasmin::LocationMap const &) {
gen.write({0x48, 0x8d, 0x76, 0xff}); // lea rsi, [rsi - 1]
}

void operator()(decltype(nth::type<Right>), jasmin::x64::FunctionEmitter &gen,
jasmin::LocationMap const &) {
gen.write({0x48, 0x8d, 0x76, 0x01}); // lea rsi, [rsi + 1]
}

void operator()(decltype(nth::type<Output>),
jasmin::x64::FunctionEmitter &gen,
jasmin::LocationMap const &) {
gen.write({
0x48, 0xc7, 0xc0, 0x01, 0x00, 0x00, 0x00, // mov rax, 0x1
0x48, 0xc7, 0xc7, 0x01, 0x00, 0x00, 0x00, // mov rdi, 0x1
0x48, 0xc7, 0xc2, 0x01, 0x00, 0x00, 0x00, // mov rdx, 0x1
});
gen.syscall();
}

void operator()(decltype(nth::type<Input>), jasmin::x64::FunctionEmitter &gen,
jasmin::LocationMap const &) {
gen.write({
0x48, 0xc7, 0xc0, 0x00, 0x00, 0x00, 0x00, // mov rax, 0x0
0x48, 0xc7, 0xc7, 0x00, 0x00, 0x00, 0x00, // mov rdi, 0x0
0x48, 0xc7, 0xc2, 0x01, 0x00, 0x00, 0x00, // mov rdx, 0x1
});
gen.syscall();
}

void operator()(decltype(nth::type<Zero>), jasmin::x64::FunctionEmitter &gen,
jasmin::LocationMap const &) {
gen.write({0x8a, 0x06}); // mov al, BYTE PTR [rsi]
}
};

} // namespace bf

#endif // EXAMPLES_BRAINFUCK_X64_CODE_GENERATOR_H
6 changes: 3 additions & 3 deletions jasmin/compile/x64/BUILD
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package(default_visibility = ["//visibility:private"])

cc_library(
name = "code_generator",
hdrs = ["code_generator.h"],
srcs = ["code_generator.cc"],
name = "function_emitter",
hdrs = ["function_emitter.h"],
srcs = ["function_emitter.cc"],
visibility = ["//visibility:public"],
deps = [
":location_map",
74 changes: 74 additions & 0 deletions jasmin/compile/x64/function_emitter.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#include "jasmin/compile/x64/function_emitter.h"

namespace jasmin::x64 {

void FunctionEmitter::write(std::initializer_list<uint8_t> instructions) {
fn_->write(instructions);
}

void FunctionEmitter::push(Register reg) {
write({static_cast<uint8_t>(static_cast<uint8_t>(reg) + 0x50)});
}

void FunctionEmitter::pop(Register reg) {
write({static_cast<uint8_t>(static_cast<uint8_t>(reg) + 0x58)});
}

void FunctionEmitter::mov(Register destination, Register source) {
write({0x48, 0x89,
static_cast<uint8_t>(0xc0 + static_cast<uint8_t>(destination) +
8 * static_cast<uint8_t>(source))});
}

void FunctionEmitter::syscall() { write({0x0f, 0x05}); }

void FunctionEmitter::ret() { write({0xc3}); }

void FunctionEmitter::emit(SsaFunction const &fn, CompiledFunction &c) {
fn_ = &c;
push(Register::rbp);
mov(Register::rbp, Register::rsp);

block_starts_.reserve(fn.blocks().size());
LocationMap loc_map;
for (auto const &block : fn.blocks()) {
block_starts_.push_back(fn_->size());
for (auto const &inst : block.instructions()) {
generators_[metadata_.opcode(inst.op_code())](generator_, *this, loc_map);
}
switch (block.branch().kind()) {
case SsaBranchKind::Return:
mov(Register::rsp, Register::rbp);
pop(Register::rbp);
ret();
break;
case SsaBranchKind::Conditional: {
auto const &c = block.branch().AsConditional();
// TODO: Support arbirtary register choices here.
// TODO: Prefer fallthroughs when we can make that happen.
write({
0x84, 0xc0, // test al, al
0x0f, 0x84, 0x00, 0x00, 0x00, 0x00, // jz __
});
block_jumps_.emplace(fn_->size(), c.true_block);
write({
0xe9, 0x00, 0x00, 0x00, 0x00, // jmp ___
});
block_jumps_.emplace(fn_->size(), c.false_block);
} break;
case SsaBranchKind::Unconditional: {
NTH_UNIMPLEMENTED();
} break;
case SsaBranchKind::Unreachable: break;
}
}

for (auto const &[offset, block_number] : block_jumps_) {
fn_->write_at(offset - 4,
static_cast<uint32_t>(block_starts_[block_number] - offset));
}

fn_ = nullptr;
}

} // namespace jasmin::x64
86 changes: 86 additions & 0 deletions jasmin/compile/x64/function_emitter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#ifndef JASMIN_COMPILE_X64_FUNCTION_EMITTER_H
#define JASMIN_COMPILE_X64_FUNCTION_EMITTER_H

#include <span>
#include <string>
#include <vector>

#include "absl/container/flat_hash_map.h"
#include "jasmin/compile/compiled_function.h"
#include "jasmin/compile/x64/location_map.h"
#include "jasmin/core/instruction.h"
#include "jasmin/ssa/ssa.h"
#include "nth/meta/type.h"

namespace jasmin::x64 {

enum class Register : uint8_t {
rax = 0,
rcx = 1,
rdx = 2,
rbx = 3,
rsp = 4,
rbp = 5,
rsi = 6,
rdi = 7,
};

struct FunctionEmitter {
FunctionEmitter(nth::Type auto instruction_set, auto &generator)
: metadata_(Metadata<nth::type_t<instruction_set>>()),
generator_(&generator_) {
using generator_type = std::remove_reference_t<decltype(generator)>;
nth::type_t<instruction_set>::instructions.reduce([this](auto... ts) {
generators_ = {Generate<generator_type>(ts)...};
});
}

void emit(SsaFunction const &fn, CompiledFunction &f);

void write(std::initializer_list<uint8_t> instructions);

void push(Register reg);
void pop(Register reg);
void mov(Register destination, Register source);
void ret();
void syscall();

private:
template <typename Generator>
static auto Generate(nth::Type auto t)
-> void (*)(void *, FunctionEmitter &, LocationMap const &);

CompiledFunction *fn_ = nullptr;
std::vector<size_t> block_starts_;
absl::flat_hash_map<size_t, size_t> block_jumps_;
InstructionSetMetadata const &metadata_;
void *generator_;
std::vector<void (*)(void *, FunctionEmitter &, LocationMap const &)>
generators_;
};

// Implementation

template <typename Generator>
auto FunctionEmitter::Generate(nth::Type auto t)
-> void (*)(void *, FunctionEmitter &, LocationMap const &) {
if constexpr (t == nth::type<Call>) {
return nullptr;
} else if constexpr (t == nth::type<Jump>) {
return nullptr;
} else if constexpr (t == nth::type<JumpIf>) {
return nullptr;
} else if constexpr (t == nth::type<JumpIfNot>) {
return nullptr;
} else if constexpr (t == nth::type<Return>) {
return nullptr;
} else {
return +[](void *gen, FunctionEmitter &cg, LocationMap const &map) {
(*reinterpret_cast<Generator *>(gen))(decltype(t){}, cg, map);
};
}
}

} // namespace jasmin::x64

#endif // JASMIN_COMPILE_X64_FUNCTION_EMITTER_H

0 comments on commit b4c622a

Please sign in to comment.