Modules wip: huge refactor + support linking several input files into a single executable memory blob
This commit is contained in:
parent
5c3e59eaf5
commit
a0dd6f9ca6
35 changed files with 775 additions and 398 deletions
|
|
@ -8,8 +8,8 @@
|
|||
#include <pslang/semantic/error.hpp>
|
||||
#include <pslang/ir/compiler.hpp>
|
||||
#include <pslang/ir/print.hpp>
|
||||
#include <pslang/jit/jit.hpp>
|
||||
#include <pslang/jit/executable.hpp>
|
||||
#include <pslang/jit/compiler.hpp>
|
||||
#include <pslang/jit/host_executable.hpp>
|
||||
#include <pslang/jit/foreign.hpp>
|
||||
|
||||
#include <filesystem>
|
||||
|
|
@ -198,8 +198,7 @@ int main(int argc, char ** argv)
|
|||
semantic::validate(module);
|
||||
modules.push_back(std::move(module));
|
||||
|
||||
ir_compiled.emplace_back();
|
||||
ir::compile(ir_compiled.back(), modules.back());
|
||||
ir_compiled.push_back(ir::compile(modules.back()));
|
||||
|
||||
if (dump_ir)
|
||||
{
|
||||
|
|
@ -248,25 +247,18 @@ int main(int argc, char ** argv)
|
|||
|
||||
if (jit)
|
||||
{
|
||||
// TODO: treat all input files as modules combined into a single program
|
||||
std::vector<jit::compiled_module> modules;
|
||||
for (std::size_t i = 0; i < filenames.size(); ++i)
|
||||
modules.push_back(jit::compile(ir_compiled[i], jit::host_abi()));
|
||||
|
||||
auto executable = jit::make_host_executable(modules);
|
||||
|
||||
for (std::size_t i = 0; i < modules.size(); ++i)
|
||||
{
|
||||
jit::program_context pcontext
|
||||
{
|
||||
.abi = jit::host_abi(),
|
||||
};
|
||||
if (!executable.entry_points[i]) continue;
|
||||
|
||||
jit::compile(pcontext, ir_compiled[i]);
|
||||
|
||||
for (auto const & resolve : pcontext.foreign_resolve)
|
||||
{
|
||||
auto fptr = jit::load_foreign(resolve.name);
|
||||
std::copy_n((std::uint8_t const *)(&fptr), 8, pcontext.storage.storage.data() + resolve.offset);
|
||||
}
|
||||
|
||||
auto executable = jit::make_host_executable(pcontext.storage);
|
||||
|
||||
auto entry_point = (void(*)())(executable.get() + pcontext.entry_point);
|
||||
using entry_point_t = void(*)();
|
||||
auto entry_point = entry_point_t(executable.mapping.get() + *executable.entry_points[i]);
|
||||
entry_point();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <pslang/ir/module.hpp>
|
||||
#include <pslang/semantic/module.hpp>
|
||||
|
||||
namespace pslang::ir
|
||||
{
|
||||
|
||||
void compile(compiled_module & result, semantic::module const & module);
|
||||
compiled_module compile(semantic::module const & module);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <pslang/semantic/module.hpp>
|
||||
#include <pslang/ir/node_fwd.hpp>
|
||||
#include <pslang/ast/statement_fwd.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
|
|
@ -9,6 +9,7 @@ namespace pslang::ast
|
|||
{
|
||||
|
||||
struct function_definition;
|
||||
struct variable_declaration;
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -17,20 +18,25 @@ namespace pslang::ir
|
|||
|
||||
struct compiled_module
|
||||
{
|
||||
// Fake root AST node for module entry point
|
||||
ast::statement_ptr root;
|
||||
|
||||
node_list_ptr nodes;
|
||||
|
||||
// Needed for debug IR dump
|
||||
std::unordered_map<node const *, std::string> labels;
|
||||
|
||||
struct function_info
|
||||
struct node_range
|
||||
{
|
||||
node_ref begin;
|
||||
node_ref end;
|
||||
};
|
||||
|
||||
std::unordered_map<ast::function_definition const *, function_info> functions;
|
||||
std::unordered_map<ast::function_definition const *, node_range> functions;
|
||||
std::unordered_map<ast::variable_declaration const *, node_ref> globals;
|
||||
|
||||
node_ref entry_point;
|
||||
// Can be null if the entry point is empty
|
||||
ast::function_definition const * entry_point;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
#include <pslang/types/type.hpp>
|
||||
|
||||
#include <variant>
|
||||
#include <functional>
|
||||
|
||||
namespace pslang::ir
|
||||
{
|
||||
|
|
@ -161,17 +160,3 @@ namespace pslang::ir
|
|||
bool is_value_instruction(instruction const & instruction);
|
||||
|
||||
}
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
template <>
|
||||
struct hash<::pslang::ir::node_ref>
|
||||
{
|
||||
std::size_t operator()(pslang::ir::node_ref const & ref) const
|
||||
{
|
||||
return std::hash<pslang::ir::node const *>()(ref.operator->());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
namespace pslang::ir
|
||||
{
|
||||
|
|
@ -12,3 +13,20 @@ namespace pslang::ir
|
|||
using node_list_ptr = std::shared_ptr<node_list>;
|
||||
|
||||
}
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
template <>
|
||||
struct hash<::pslang::ir::node_ref>
|
||||
{
|
||||
std::size_t operator()(pslang::ir::node_ref const & ref) const
|
||||
{
|
||||
// NON STANDARD!!!
|
||||
// Use the knowledge that list iterator is just a node pointer in
|
||||
// pretty much all implementations
|
||||
return std::hash<void const *>{}(*(void const **)(&ref));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ namespace pslang::ir
|
|||
struct scope
|
||||
{
|
||||
std::string label_prefix;
|
||||
bool single_execution_scope = true;
|
||||
};
|
||||
|
||||
std::vector<scope> scopes;
|
||||
|
|
@ -685,6 +686,19 @@ namespace pslang::ir
|
|||
{
|
||||
if (node.global)
|
||||
{
|
||||
if (lcontext.scopes.back().single_execution_scope)
|
||||
{
|
||||
// Optimization: top-level globals will be initialized unconditionally during module entry point execution
|
||||
// Skip the initialization guard and just initialize it immediately
|
||||
module.nodes->emplace_back(global{.initializer = {}}, node.inferred_type);
|
||||
auto result = last();
|
||||
auto value = apply(*node.initializer);
|
||||
module.nodes->emplace_back(assignment{result, value});
|
||||
lcontext.globals[&node] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Non top-level global: add boolean initialization guard to ensure once-only initialization
|
||||
module.nodes->emplace_back(global{.initializer = {0}}, std::make_unique<types::type>(types::primitive_type{types::bool_type{}}));
|
||||
auto guard = last();
|
||||
module.nodes->emplace_back(global{.initializer = {}}, node.inferred_type);
|
||||
|
|
@ -757,7 +771,7 @@ namespace pslang::ir
|
|||
auto jump_to_end = last();
|
||||
|
||||
lcontext.loop_scopes.emplace_back();
|
||||
lcontext.scopes.emplace_back();
|
||||
lcontext.scopes.emplace_back().single_execution_scope = false;
|
||||
apply(*node.statements);
|
||||
lcontext.scopes.pop_back();
|
||||
|
||||
|
|
@ -824,7 +838,8 @@ namespace pslang::ir
|
|||
module.nodes->emplace_back(label{});
|
||||
auto begin = last();
|
||||
|
||||
lcontext.scopes.emplace_back();
|
||||
bool const is_root = lcontext.scopes.empty();
|
||||
lcontext.scopes.emplace_back().single_execution_scope = is_root;
|
||||
for (std::size_t i = 0; i < node.arguments.size(); ++i)
|
||||
{
|
||||
module.nodes->emplace_back(argument{i}, ast::get_type(*node.arguments[i].type));
|
||||
|
|
@ -896,10 +911,6 @@ namespace pslang::ir
|
|||
label_prefix = lcontext.scopes.back().label_prefix + node.name + ".";
|
||||
lcontext.scopes.emplace_back(std::move(label_prefix));
|
||||
apply(*node.statements);
|
||||
|
||||
// Don't pop_back entry point scope
|
||||
if (lcontext.scopes.size() > 1)
|
||||
lcontext.scopes.pop_back();
|
||||
}
|
||||
|
||||
void apply(ast::foreign_function_declaration const &) {}
|
||||
|
|
@ -911,14 +922,15 @@ namespace pslang::ir
|
|||
|
||||
}
|
||||
|
||||
void compile(compiled_module & module, semantic::module const & module_in)
|
||||
compiled_module compile(semantic::module const & module_in)
|
||||
{
|
||||
if (!module.nodes)
|
||||
module.nodes = std::make_shared<node_list>();
|
||||
compiled_module module;
|
||||
|
||||
module.nodes = std::make_shared<node_list>();
|
||||
|
||||
// Add a fake root AST function node
|
||||
// for module entry point
|
||||
auto root = std::make_shared<ast::statement>(
|
||||
module.root = std::make_shared<ast::statement>(
|
||||
ast::function_definition {
|
||||
{
|
||||
"[entry point]",
|
||||
|
|
@ -937,7 +949,7 @@ namespace pslang::ir
|
|||
|
||||
module.nodes->emplace_back(label{});
|
||||
auto extra_label = std::prev(module.nodes->end());
|
||||
compile_visitor{{}, module, lcontext}.apply(*root);
|
||||
compile_visitor{{}, module, lcontext}.apply(*module.root);
|
||||
module.nodes->erase(extra_label);
|
||||
|
||||
for (auto & function : lcontext.functions)
|
||||
|
|
@ -952,7 +964,12 @@ namespace pslang::ir
|
|||
for (auto const & function : lcontext.functions)
|
||||
module.functions[function.first] = {.begin = function.second.first, .end = function.second.second};
|
||||
|
||||
module.entry_point = lcontext.functions.at(std::get_if<ast::function_definition>(root.get())).first;
|
||||
for (auto const & global : lcontext.globals)
|
||||
module.globals[global.first] = global.second;
|
||||
|
||||
module.entry_point = std::get_if<ast::function_definition>(module.root.get());
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ namespace pslang::jit
|
|||
{
|
||||
jit::isa isa;
|
||||
jit::platform platform;
|
||||
|
||||
friend bool operator == (abi, abi) = default;
|
||||
};
|
||||
|
||||
abi host_abi();
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <pslang/jit/jit.hpp>
|
||||
#include <pslang/jit/compiler.hpp>
|
||||
#include <pslang/ir/module.hpp>
|
||||
|
||||
namespace pslang::jit::linux_x86_64
|
||||
{
|
||||
|
||||
void compile(program_context & pcontext, ir::compiled_module const & module_in);
|
||||
compiled_module compile(ir::compiled_module const & module_in);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -325,30 +325,39 @@ namespace pslang::jit::linux_x86_64
|
|||
|
||||
// Assuming that @opcode refers to the location of a JUMP instruction,
|
||||
// replace its 32-bit jump offset with @offset
|
||||
void jump_inject(std::uint8_t * opcode, std::int32_t offset);
|
||||
static void jump_inject(std::uint8_t * opcode, std::int32_t offset);
|
||||
|
||||
// Assuming that @opcode refers to the location of a JUMP instruction,
|
||||
// replace its 32-bit jump offset with @offset, compensating for the size
|
||||
// of the JUMP instruction itself
|
||||
void jump_inject_prev(std::uint8_t * opcode, std::int32_t offset);
|
||||
static void jump_inject_prev(std::uint8_t * opcode, std::int32_t offset);
|
||||
|
||||
// Assuming that @opcode refers to the location of a conditional JUMP instruction
|
||||
// (JZ or JNZ), replace its 32-bit jump offset with @offset
|
||||
void cjump_inject(std::uint8_t * opcode, std::int32_t offset);
|
||||
static void cjump_inject(std::uint8_t * opcode, std::int32_t offset);
|
||||
|
||||
// Assuming that @opcode refers to the location of a conditional JUMP instruction
|
||||
// (JZ or JNZ), replace its 32-bit jump offset with @offset, compensating for the size
|
||||
// of the JUMP instruction itself
|
||||
void cjump_inject_prev(std::uint8_t * opcode, std::int32_t offset);
|
||||
static void cjump_inject_prev(std::uint8_t * opcode, std::int32_t offset);
|
||||
|
||||
// Assuming that @opcode refers to the location of a RIP-relative MOV instruction,
|
||||
// replace its 32-bit jump offset with @offset
|
||||
static void mov_rip_inject(std::uint8_t * opcode, std::int32_t offset);
|
||||
|
||||
// Assuming that @opcode refers to the location of a RIP-relative MOV instruction,
|
||||
// replace its 32-bit jump offset with @offset, compensating for the size
|
||||
// of the MOV instruction itself
|
||||
static void mov_rip_inject_prev(std::uint8_t * opcode, std::int32_t offset);
|
||||
|
||||
// Assuming that @opcode refers to the location of a RIP-relative LEA instruction,
|
||||
// replace its 32-bit jump offset with @offset
|
||||
void lea_rip_inject(std::uint8_t * opcode, std::int32_t offset);
|
||||
static void lea_rip_inject(std::uint8_t * opcode, std::int32_t offset);
|
||||
|
||||
// Assuming that @opcode refers to the location of a RIP-relative LEA instruction,
|
||||
// replace its 32-bit jump offset with @offset, compensating for the size
|
||||
// of the LEA instruction itself
|
||||
void lea_rip_inject_prev(std::uint8_t * opcode, std::int32_t offset);
|
||||
static void lea_rip_inject_prev(std::uint8_t * opcode, std::int32_t offset);
|
||||
|
||||
// Call to 32-bit signed @offset relative to RIP
|
||||
// NB: RIP holds the address of the _next_ instruction
|
||||
|
|
@ -356,16 +365,19 @@ namespace pslang::jit::linux_x86_64
|
|||
|
||||
// Assuming that @opcode refers to the location of a CALL instruction,
|
||||
// replace its 32-bit call offset with @offset
|
||||
void call_imm_inject(std::uint8_t * opcode, std::int32_t offset);
|
||||
static void call_imm_inject(std::uint8_t * opcode, std::int32_t offset);
|
||||
|
||||
// Assuming that @opcode refers to the location of a CALL instruction,
|
||||
// replace its 32-bit call offset with @offset, compensating for the size
|
||||
// of the CALL instruction itself
|
||||
void call_imm_inject_prev(std::uint8_t * opcode, std::int32_t offset);
|
||||
static void call_imm_inject_prev(std::uint8_t * opcode, std::int32_t offset);
|
||||
|
||||
// Call to address specified in @reg_addr
|
||||
void call_reg(reg reg_addr);
|
||||
|
||||
//
|
||||
static void resolve_offset(std::uint8_t * opcode, std::int32_t offset);
|
||||
|
||||
private:
|
||||
template <typename ... Args>
|
||||
void do_push(Args ... values);
|
||||
|
|
|
|||
10
libs/jit/include/pslang/jit/arch/linux_x86_64/resolver.hpp
Normal file
10
libs/jit/include/pslang/jit/arch/linux_x86_64/resolver.hpp
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <pslang/jit/resolver.hpp>
|
||||
|
||||
namespace pslang::jit::linux_x86_64
|
||||
{
|
||||
|
||||
std::unique_ptr<resolver> make_resolver();
|
||||
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <pslang/jit/jit.hpp>
|
||||
#include <pslang/jit/compiler.hpp>
|
||||
#include <pslang/ir/module.hpp>
|
||||
|
||||
namespace pslang::jit::macos_aarch64
|
||||
{
|
||||
|
||||
void compile(program_context & pcontext, ir::compiled_module const & module_in);
|
||||
compiled_module compile(ir::compiled_module const & module_in);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,6 +104,8 @@ namespace pslang::jit::macos_aarch64
|
|||
// plus a signed 19-bit @offset multiplied by 4, and store it into register @reg_dst
|
||||
void ldr_pc(std::uint8_t reg_dst, std::int32_t offset);
|
||||
|
||||
static void ldr_pc_inject(std::uint8_t * opcode, std::int32_t offset);
|
||||
|
||||
// Add a 12-bit @value to the register @reg_src and store the result in @reg_dst
|
||||
void add_imm(std::uint8_t reg_src, std::uint8_t reg_dst, std::uint16_t value);
|
||||
|
||||
|
|
@ -178,7 +180,7 @@ namespace pslang::jit::macos_aarch64
|
|||
|
||||
// Inject the 19-bit signed @offset into the opcode of a cbz or cbnz instruction
|
||||
// starting at @opcode
|
||||
void cb_inject(std::uint8_t * opcode, std::int32_t offset);
|
||||
static void cb_inject(std::uint8_t * opcode, std::int32_t offset);
|
||||
|
||||
// Unconditionally move the program counter to the value of
|
||||
// 26-bit signed @offset multiplied by 4
|
||||
|
|
@ -198,12 +200,12 @@ namespace pslang::jit::macos_aarch64
|
|||
|
||||
// Inject the 26-bit signed @offset into the opcode of b or bl instruction
|
||||
// starting at @opcode
|
||||
void b_inject(std::uint8_t * opcode, std::int32_t offset);
|
||||
static void b_inject(std::uint8_t * opcode, std::int32_t offset);
|
||||
|
||||
// Load the current program count plus a signed 21-bit offset into register @reg_dst
|
||||
void adr(std::uint8_t reg_dst, std::int32_t offset);
|
||||
|
||||
void adr_inject(std::uint8_t * opcode, std::int32_t offset);
|
||||
static void adr_inject(std::uint8_t * opcode, std::int32_t offset);
|
||||
|
||||
// Load a floating-point value from current program counter plus a
|
||||
// 19-bit signed @offset multiplied by 4, and store it in floating-point
|
||||
|
|
|
|||
10
libs/jit/include/pslang/jit/arch/macos_aarch64/resolver.hpp
Normal file
10
libs/jit/include/pslang/jit/arch/macos_aarch64/resolver.hpp
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <pslang/jit/resolver.hpp>
|
||||
|
||||
namespace pslang::jit::macos_aarch64
|
||||
{
|
||||
|
||||
std::unique_ptr<resolver> make_resolver();
|
||||
|
||||
}
|
||||
12
libs/jit/include/pslang/jit/compiler.hpp
Normal file
12
libs/jit/include/pslang/jit/compiler.hpp
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <pslang/ast/statement_fwd.hpp>
|
||||
#include <pslang/jit/module.hpp>
|
||||
#include <pslang/ir/module.hpp>
|
||||
|
||||
namespace pslang::jit
|
||||
{
|
||||
|
||||
compiled_module compile(ir::compiled_module const & module_in, abi abi);
|
||||
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <pslang/jit/storage.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
|
||||
namespace pslang::jit
|
||||
{
|
||||
|
||||
std::shared_ptr<std::uint8_t> make_host_executable(binary_storage const & storage);
|
||||
|
||||
}
|
||||
25
libs/jit/include/pslang/jit/host_executable.hpp
Normal file
25
libs/jit/include/pslang/jit/host_executable.hpp
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include <pslang/jit/module.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace pslang::jit
|
||||
{
|
||||
|
||||
struct host_executable_program
|
||||
{
|
||||
std::shared_ptr<std::uint8_t> mapping;
|
||||
|
||||
// Offsets from the start of the mapping
|
||||
std::unordered_map<ir::node_ref, std::size_t> functions;
|
||||
std::unordered_map<ir::node_ref, std::size_t> globals;
|
||||
|
||||
std::vector<std::optional<std::size_t>> entry_points;
|
||||
};
|
||||
|
||||
host_executable_program make_host_executable(std::vector<compiled_module> const & modules);
|
||||
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <pslang/ast/statement_fwd.hpp>
|
||||
#include <pslang/jit/program_context.hpp>
|
||||
#include <pslang/ir/module.hpp>
|
||||
|
||||
namespace pslang::jit
|
||||
{
|
||||
|
||||
void compile(program_context & pcontext, ir::compiled_module const & module_in);
|
||||
|
||||
}
|
||||
24
libs/jit/include/pslang/jit/linked_program.hpp
Normal file
24
libs/jit/include/pslang/jit/linked_program.hpp
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
namespace pslang::jit
|
||||
{
|
||||
|
||||
struct linked_program_info
|
||||
{
|
||||
std::uint8_t storage;
|
||||
|
||||
struct section
|
||||
{
|
||||
std::size_t begin;
|
||||
std::size_t size;
|
||||
};
|
||||
|
||||
section code;
|
||||
section data;
|
||||
section relocations;
|
||||
};
|
||||
|
||||
}
|
||||
49
libs/jit/include/pslang/jit/linker.hpp
Normal file
49
libs/jit/include/pslang/jit/linker.hpp
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#pragma once
|
||||
|
||||
#include <pslang/ir/node_fwd.hpp>
|
||||
#include <pslang/jit/module.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace pslang::jit
|
||||
{
|
||||
|
||||
struct link_options
|
||||
{
|
||||
// If JIT-compiling into an in-memory host-executable binary,
|
||||
// code and data sections must be aligned to OS page size.
|
||||
std::size_t section_alignment = 1;
|
||||
|
||||
// If AOT-compiling into an ELF executable, 1 GOT entry must
|
||||
// be reserved to point to _DYNAMIC (the address of .dynamic segment)
|
||||
std::size_t reserved_relocations = 0;
|
||||
};
|
||||
|
||||
struct linked_program_info
|
||||
{
|
||||
struct section
|
||||
{
|
||||
std::size_t begin;
|
||||
std::size_t size;
|
||||
};
|
||||
|
||||
section code;
|
||||
section data;
|
||||
section relocations;
|
||||
|
||||
std::size_t total_size;
|
||||
|
||||
// Offsets from the beginning of combined storage
|
||||
std::unordered_map<ir::node_ref, std::size_t> functions;
|
||||
std::unordered_map<ir::node_ref, std::size_t> globals;
|
||||
std::unordered_map<std::string, std::size_t> relocation_offset;
|
||||
|
||||
// One per each module
|
||||
std::vector<std::optional<std::size_t>> entry_points;
|
||||
};
|
||||
|
||||
linked_program_info prepare_link(std::vector<compiled_module> const & modules, link_options const & options);
|
||||
|
||||
void link(std::vector<compiled_module> const & modules, linked_program_info const & info, std::uint8_t * storage);
|
||||
|
||||
}
|
||||
77
libs/jit/include/pslang/jit/module.hpp
Normal file
77
libs/jit/include/pslang/jit/module.hpp
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#pragma once
|
||||
|
||||
#include <pslang/jit/abi.hpp>
|
||||
#include <pslang/ir/node_fwd.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace pslang::ast
|
||||
{
|
||||
|
||||
struct function_definition;
|
||||
struct foreign_function_declaration;
|
||||
struct variable_declaration;
|
||||
|
||||
}
|
||||
|
||||
namespace pslang::jit
|
||||
{
|
||||
|
||||
struct compiled_module
|
||||
{
|
||||
struct internal_function_resolve_info
|
||||
{
|
||||
// Function whose address is to be resolved
|
||||
ir::node_ref function;
|
||||
// Offset to call/jump/lea/mov instruction that needs to be patched
|
||||
std::int32_t instruction_offset;
|
||||
};
|
||||
|
||||
struct internal_global_resolve_info
|
||||
{
|
||||
// Global variable whose address is to be resolved
|
||||
ir::node_ref global;
|
||||
// Offset to call/jump/lea/mov instruction that needs to be patched
|
||||
std::int32_t instruction_offset;
|
||||
};
|
||||
|
||||
struct external_function_resolve_info
|
||||
{
|
||||
// Name of external (foreign) function whose address needs to be resolved
|
||||
// On all platforms, this uses GOT (Global Offset Table): an array of 8-byte
|
||||
// pointers to functions resolved dynamically before executing the compiled code
|
||||
// It is the address of this table entry that needs to be resolved
|
||||
std::string name;
|
||||
// Offset to call/jump/lea/mov instruction that needs to be patched
|
||||
// (though this should always be mov, as there's no reason to jump/call the GOT entry)
|
||||
std::int32_t instruction_offset;
|
||||
};
|
||||
|
||||
jit::abi abi;
|
||||
|
||||
// Executable code
|
||||
std::vector<std::uint8_t> code;
|
||||
|
||||
// Module entry point
|
||||
// Can be null if the module's entry point is a nop
|
||||
std::optional<ir::node_ref> entry_point;
|
||||
|
||||
// Global variables
|
||||
std::vector<std::uint8_t> data;
|
||||
|
||||
// Offset to function code start in `code` buffer
|
||||
std::unordered_map<ir::node_ref, std::int32_t> functions;
|
||||
|
||||
// Offset to global variable memory start in `data` buffer
|
||||
std::unordered_map<ir::node_ref, std::int32_t> globals;
|
||||
|
||||
std::vector<internal_function_resolve_info> internal_function_resolve;
|
||||
std::vector<internal_global_resolve_info> internal_global_resolve;
|
||||
std::vector<external_function_resolve_info> external_function_resolve;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <pslang/jit/abi.hpp>
|
||||
#include <pslang/jit/storage.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace pslang::ast
|
||||
{
|
||||
|
||||
struct function_definition;
|
||||
|
||||
}
|
||||
|
||||
namespace pslang::jit
|
||||
{
|
||||
|
||||
struct program_context
|
||||
{
|
||||
struct foreign_resolve_info
|
||||
{
|
||||
std::string name;
|
||||
// Offset in bytes to the place in code blob
|
||||
// containing the 64-bit address of the foreign symbol
|
||||
std::int32_t offset;
|
||||
};
|
||||
|
||||
jit::abi abi;
|
||||
|
||||
binary_storage storage = {};
|
||||
|
||||
std::unordered_map<ast::function_definition const *, std::int32_t> symbols = {};
|
||||
std::int32_t entry_point = 0;
|
||||
std::vector<foreign_resolve_info> foreign_resolve = {};
|
||||
};
|
||||
|
||||
}
|
||||
22
libs/jit/include/pslang/jit/resolver.hpp
Normal file
22
libs/jit/include/pslang/jit/resolver.hpp
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include <pslang/jit/abi.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace pslang::jit
|
||||
{
|
||||
|
||||
struct resolver
|
||||
{
|
||||
// Given a jump/call/lea/mov instruction at @opcode that uses an
|
||||
// immediate signed 32-bit offset, add the @offset to the existing offset
|
||||
virtual void resolve(std::uint8_t * opcode, std::int32_t offset) = 0;
|
||||
|
||||
virtual ~resolver() {}
|
||||
};
|
||||
|
||||
std::unique_ptr<resolver> make_resolver(isa isa);
|
||||
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace pslang::jit
|
||||
{
|
||||
|
||||
struct binary_storage
|
||||
{
|
||||
struct range
|
||||
{
|
||||
std::size_t begin;
|
||||
std::size_t end;
|
||||
};
|
||||
|
||||
std::vector<std::uint8_t> storage;
|
||||
std::vector<range> data;
|
||||
std::vector<range> code;
|
||||
|
||||
std::size_t size() const { return storage.size(); }
|
||||
std::size_t align();
|
||||
};
|
||||
|
||||
std::size_t native_page_size();
|
||||
|
||||
}
|
||||
|
|
@ -52,18 +52,21 @@ namespace pslang::jit::linux_x86_64
|
|||
|
||||
struct value_address
|
||||
{
|
||||
// None means RIP-based addressing
|
||||
std::optional<reg> base;
|
||||
std::optional<reg> base = std::nullopt;
|
||||
ir::node_ref global = {};
|
||||
|
||||
std::int32_t offset;
|
||||
};
|
||||
|
||||
bool is_global(ir::node_ref it)
|
||||
{
|
||||
return std::holds_alternative<ir::global>(it->instruction);
|
||||
}
|
||||
|
||||
struct local_context
|
||||
{
|
||||
bool use_frame_pointer = true;
|
||||
|
||||
std::unordered_map<std::string, std::int32_t> extern_symbols;
|
||||
|
||||
std::unordered_map<types::type_ptr, small_struct_data> small_structs;
|
||||
|
||||
std::unordered_map<ir::node_ref, std::int32_t> nodes;
|
||||
|
|
@ -116,7 +119,7 @@ namespace pslang::jit::linux_x86_64
|
|||
|
||||
struct populate_globals_visitor
|
||||
{
|
||||
std::vector<std::uint8_t> & storage;
|
||||
compiled_module & result;
|
||||
local_context & lcontext;
|
||||
|
||||
template <typename Node>
|
||||
|
|
@ -130,36 +133,16 @@ namespace pslang::jit::linux_x86_64
|
|||
throw std::runtime_error("global IR node with initializer larger than type size");
|
||||
|
||||
auto alignment = ast::type_alignment(*type);
|
||||
auto offset = storage.size();
|
||||
auto offset = result.data.size();
|
||||
offset = ((offset + (alignment - 1)) / alignment) * alignment;
|
||||
storage.resize(offset + size);
|
||||
std::copy(node.initializer.begin(), node.initializer.end(), storage.begin() + offset);
|
||||
lcontext.nodes[it] = offset;
|
||||
}
|
||||
};
|
||||
|
||||
struct populate_const_data_visitor
|
||||
{
|
||||
program_context & pcontext;
|
||||
local_context & lcontext;
|
||||
|
||||
template <typename Node>
|
||||
void apply(Node const & node, types::type_ptr const &)
|
||||
{}
|
||||
|
||||
void apply(ir::extern_symbol const & node, types::type_ptr const &)
|
||||
{
|
||||
std::int32_t offset = pcontext.storage.size();
|
||||
lcontext.extern_symbols[node.name] = offset;
|
||||
pcontext.foreign_resolve.push_back({node.name, offset});
|
||||
push_bytes(pcontext.storage.storage, (void *)nullptr);
|
||||
result.data.resize(offset + size);
|
||||
std::copy(node.initializer.begin(), node.initializer.end(), result.data.begin() + offset);
|
||||
result.globals[it] = offset;
|
||||
}
|
||||
};
|
||||
|
||||
struct literal_visitor
|
||||
{
|
||||
program_context & pcontext;
|
||||
local_context & lcontext;
|
||||
instruction_builder & builder;
|
||||
|
||||
void operator()(ast::bool_literal const & node)
|
||||
|
|
@ -205,8 +188,7 @@ namespace pslang::jit::linux_x86_64
|
|||
|
||||
struct compile_visitor
|
||||
{
|
||||
program_context & pcontext;
|
||||
ir::compiled_module const & module;
|
||||
compiled_module & module;
|
||||
local_context & lcontext;
|
||||
instruction_builder & builder;
|
||||
|
||||
|
|
@ -220,7 +202,7 @@ namespace pslang::jit::linux_x86_64
|
|||
|
||||
void apply(ir::node_ref it, ir::literal const & node, types::type_ptr const & type)
|
||||
{
|
||||
std::visit(literal_visitor{pcontext, lcontext, builder}, node.value);
|
||||
std::visit(literal_visitor{builder}, node.value);
|
||||
if (types::is_integer_like_type(*type))
|
||||
store(it, reg::rax);
|
||||
else if (types::is_floating_point_type(*type))
|
||||
|
|
@ -323,9 +305,14 @@ namespace pslang::jit::linux_x86_64
|
|||
{
|
||||
auto const address = node_address(node.arg1);
|
||||
if (address.base)
|
||||
{
|
||||
builder.lea(*address.base, address.offset, reg::rax);
|
||||
}
|
||||
else
|
||||
builder.lea_rip_prev(address.offset, reg::rax);
|
||||
{
|
||||
push_resolve_global(address.global);
|
||||
builder.lea_rip_prev(0, reg::rax);
|
||||
}
|
||||
store(it, reg::rax);
|
||||
}
|
||||
break;
|
||||
|
|
@ -606,11 +593,16 @@ namespace pslang::jit::linux_x86_64
|
|||
{
|
||||
if (types::equal(*array_type->element_type, *pointer_type->referenced_type))
|
||||
{
|
||||
auto arg1_address = node_address(node.arg1);
|
||||
auto const arg1_address = node_address(node.arg1);
|
||||
if (arg1_address.base)
|
||||
{
|
||||
builder.lea(*arg1_address.base, arg1_address.offset, reg::rax);
|
||||
}
|
||||
else
|
||||
builder.lea_rip_prev(arg1_address.offset, reg::rax);
|
||||
{
|
||||
push_resolve_global(arg1_address.global);
|
||||
builder.lea_rip_prev(0, reg::rax);
|
||||
}
|
||||
store(it, reg::rax);
|
||||
return;
|
||||
}
|
||||
|
|
@ -729,14 +721,15 @@ namespace pslang::jit::linux_x86_64
|
|||
|
||||
void apply(ir::node_ref it, ir::instruction_address const & node, types::type_ptr const & type)
|
||||
{
|
||||
lcontext.node_resolve.emplace_back(pcontext.storage.size(), node.target);
|
||||
lcontext.node_resolve.emplace_back(builder.code.size(), node.target);
|
||||
builder.lea_rip(0, reg::rax);
|
||||
store(it, reg::rax);
|
||||
}
|
||||
|
||||
void apply(ir::node_ref it, ir::extern_symbol const & node, types::type_ptr const & type)
|
||||
{
|
||||
builder.mov_read_rip_prev(lcontext.extern_symbols[node.name] - (std::int32_t)pcontext.storage.size(), reg::rax);
|
||||
module.external_function_resolve.push_back({.name = node.name, .instruction_offset = (std::int32_t)builder.code.size()});
|
||||
builder.mov_read_rip_prev(0, reg::rax);
|
||||
store(it, reg::rax);
|
||||
}
|
||||
|
||||
|
|
@ -768,7 +761,7 @@ namespace pslang::jit::linux_x86_64
|
|||
|
||||
void apply(ir::node_ref, ir::jump const & node, types::type_ptr const & type)
|
||||
{
|
||||
lcontext.jump_resolve.push_back({(std::int32_t)pcontext.storage.size(), node.target});
|
||||
lcontext.jump_resolve.push_back({(std::int32_t)builder.code.size(), node.target});
|
||||
builder.jump(0);
|
||||
}
|
||||
|
||||
|
|
@ -777,7 +770,7 @@ namespace pslang::jit::linux_x86_64
|
|||
load(node.condition, reg::rax);
|
||||
reg_extend(reg::rax, reg::rax, *node.condition->inferred_type);
|
||||
builder.test(reg::rax, reg::rax);
|
||||
lcontext.cjump_resolve.push_back({(std::int32_t)pcontext.storage.size(), node.target});
|
||||
lcontext.cjump_resolve.push_back({(std::int32_t)builder.code.size(), node.target});
|
||||
builder.jump_if_zero(0);
|
||||
}
|
||||
|
||||
|
|
@ -786,7 +779,7 @@ namespace pslang::jit::linux_x86_64
|
|||
load(node.condition, reg::rax);
|
||||
reg_extend(reg::rax, reg::rax, *node.condition->inferred_type);
|
||||
builder.test(reg::rax, reg::rax);
|
||||
lcontext.cjump_resolve.push_back({(std::int32_t)pcontext.storage.size(), node.target});
|
||||
lcontext.cjump_resolve.push_back({(std::int32_t)builder.code.size(), node.target});
|
||||
builder.jump_if_nonzero(0);
|
||||
}
|
||||
|
||||
|
|
@ -839,8 +832,6 @@ namespace pslang::jit::linux_x86_64
|
|||
{
|
||||
if (auto small_struct = classify_small_struct(lcontext, argument->inferred_type))
|
||||
{
|
||||
auto address = node_address(argument);
|
||||
|
||||
for (int o : {0, 1})
|
||||
{
|
||||
auto & octet = small_struct->octets[o];
|
||||
|
|
@ -869,8 +860,8 @@ namespace pslang::jit::linux_x86_64
|
|||
|
||||
if (return_value_is_large_struct)
|
||||
{
|
||||
auto address = node_address(it);
|
||||
// Function call node cannot have RIP-relative address
|
||||
auto const address = node_address(it);
|
||||
builder.lea(address.base.value(), address.offset, reg::rdi);
|
||||
}
|
||||
|
||||
|
|
@ -931,8 +922,9 @@ namespace pslang::jit::linux_x86_64
|
|||
|
||||
void apply(ir::node_ref it, ir::call const & node, types::type_ptr const & type)
|
||||
{
|
||||
// TODO: call function from a different module?
|
||||
apply_call(it, node, type, [&]{
|
||||
lcontext.call_resolve.emplace_back(pcontext.storage.size(), node.target);
|
||||
lcontext.call_resolve.emplace_back(builder.code.size(), node.target);
|
||||
builder.call_imm(0);
|
||||
});
|
||||
}
|
||||
|
|
@ -959,8 +951,6 @@ namespace pslang::jit::linux_x86_64
|
|||
{
|
||||
if (auto small_struct = classify_small_struct(lcontext, type))
|
||||
{
|
||||
auto address = node_address(*node.value);
|
||||
|
||||
std::uint8_t reg_index = 0;
|
||||
std::uint8_t fp_reg = 0;
|
||||
for (int o : {0, 1})
|
||||
|
|
@ -976,7 +966,7 @@ namespace pslang::jit::linux_x86_64
|
|||
}
|
||||
else
|
||||
{
|
||||
copy_memory(node_address(*node.value), {reg::rdi, 0}, size);
|
||||
copy_memory(node_address(*node.value), {reg::rdi, {}, 0}, size);
|
||||
}
|
||||
}
|
||||
else if (types::is_integer_like_type(*type))
|
||||
|
|
@ -1019,7 +1009,7 @@ namespace pslang::jit::linux_x86_64
|
|||
{
|
||||
stack_position[it] = argument_position[argument->index];
|
||||
}
|
||||
else if (std::holds_alternative<ir::global>(it->instruction))
|
||||
else if (is_global(it))
|
||||
{
|
||||
// stack position doesn't make sense for globals
|
||||
}
|
||||
|
|
@ -1037,7 +1027,7 @@ namespace pslang::jit::linux_x86_64
|
|||
|
||||
auto it = begin;
|
||||
|
||||
lcontext.nodes[it] = pcontext.storage.size();
|
||||
lcontext.nodes[it] = builder.code.size();
|
||||
|
||||
if (lcontext.use_frame_pointer)
|
||||
{
|
||||
|
|
@ -1116,7 +1106,7 @@ namespace pslang::jit::linux_x86_64
|
|||
// Uncomment to debug per-node instruction generation:
|
||||
builder.nop();
|
||||
|
||||
lcontext.nodes[it] = pcontext.storage.size();
|
||||
lcontext.nodes[it] = builder.code.size();
|
||||
std::visit([&](auto const & instruction){ apply(it, instruction, it->inferred_type); }, it->instruction);
|
||||
}
|
||||
}
|
||||
|
|
@ -1154,10 +1144,11 @@ namespace pslang::jit::linux_x86_64
|
|||
}
|
||||
}
|
||||
|
||||
// Must not be called for global nodes - they use RIP-based addressing with future relocation
|
||||
value_address node_address(ir::node_ref it)
|
||||
{
|
||||
if (std::holds_alternative<ir::global>(it->instruction))
|
||||
return {.base = std::nullopt, .offset = lcontext.nodes.at(it) - static_cast<std::int32_t>(builder.code.size())};
|
||||
if (is_global(it))
|
||||
return {.global = it, .offset = 0};
|
||||
else
|
||||
return {.base = reg::rsp, .offset = stack_size - stack_position.at(it)};
|
||||
}
|
||||
|
|
@ -1166,18 +1157,29 @@ namespace pslang::jit::linux_x86_64
|
|||
{
|
||||
auto const address = node_address(it);
|
||||
if (address.base)
|
||||
{
|
||||
builder.mov_read(*address.base, address.offset + offset, reg_dst);
|
||||
}
|
||||
else
|
||||
builder.mov_read_rip_prev(address.offset + offset, reg_dst);
|
||||
{
|
||||
push_resolve_global(address.global);
|
||||
builder.mov_read_rip_prev(offset, reg_dst);
|
||||
}
|
||||
}
|
||||
|
||||
void store(ir::node_ref it, reg reg_src, std::int32_t offset = 0)
|
||||
{
|
||||
auto const address = node_address(it);
|
||||
if (address.base)
|
||||
{
|
||||
auto const address = node_address(it);
|
||||
builder.mov_write(reg_src, *address.base, address.offset + offset);
|
||||
}
|
||||
else
|
||||
builder.mov_write_rip_prev(reg_src, address.offset + offset);
|
||||
{
|
||||
push_resolve_global(address.global);
|
||||
builder.mov_write_rip_prev(reg_src, offset);
|
||||
}
|
||||
}
|
||||
|
||||
void load_xmm(ir::node_ref it, reg reg_dst, std::uint8_t size, std::int32_t offset = 0)
|
||||
|
|
@ -1188,13 +1190,17 @@ namespace pslang::jit::linux_x86_64
|
|||
auto const address = node_address(it);
|
||||
if (address.base)
|
||||
{
|
||||
auto const address = node_address(it);
|
||||
if (size == 4)
|
||||
builder.mov_read_xmm_32(*address.base, address.offset + offset, reg_dst);
|
||||
else
|
||||
builder.mov_read_xmm(*address.base, address.offset + offset, reg_dst);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO
|
||||
throw std::runtime_error("RIP-relative XMM read is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
void store_xmm(ir::node_ref it, reg reg_src, std::uint8_t size, std::int32_t offset = 0)
|
||||
|
|
@ -1211,12 +1217,15 @@ namespace pslang::jit::linux_x86_64
|
|||
builder.mov_write_xmm(reg_src, *address.base, address.offset + offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO
|
||||
throw std::runtime_error("RIP-relative XMM write is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
void copy_memory(value_address src, value_address dst, std::size_t size)
|
||||
{
|
||||
auto const storage_size_at_start = (std::int32_t)pcontext.storage.size();
|
||||
auto const storage_size_at_start = (std::int32_t)builder.code.size();
|
||||
|
||||
reg reg_src;
|
||||
if (src.base)
|
||||
|
|
@ -1224,6 +1233,7 @@ namespace pslang::jit::linux_x86_64
|
|||
else
|
||||
{
|
||||
reg_src = find_free_reg(reg::r10, dst.base);
|
||||
push_resolve_global(src.global);
|
||||
builder.lea_rip_prev(0, reg_src);
|
||||
}
|
||||
|
||||
|
|
@ -1233,7 +1243,8 @@ namespace pslang::jit::linux_x86_64
|
|||
else
|
||||
{
|
||||
reg_dst = find_free_reg(reg::r10, reg_src);
|
||||
builder.lea_rip_prev(storage_size_at_start - (std::int32_t)pcontext.storage.size(), reg_dst);
|
||||
push_resolve_global(dst.global);
|
||||
builder.lea_rip_prev(0, reg_dst);
|
||||
}
|
||||
|
||||
std::int32_t offset = 0;
|
||||
|
|
@ -1277,66 +1288,52 @@ namespace pslang::jit::linux_x86_64
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void push_resolve_global(ir::node_ref global)
|
||||
{
|
||||
module.internal_global_resolve.push_back({.global = global, .instruction_offset = (std::int32_t)builder.code.size()});
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
void compile(program_context & pcontext, ir::compiled_module const & module_in)
|
||||
compiled_module compile(ir::compiled_module const & module_in)
|
||||
{
|
||||
compiled_module result;
|
||||
|
||||
local_context lcontext;
|
||||
|
||||
auto data_begin = pcontext.storage.align();
|
||||
{
|
||||
populate_globals_visitor visitor{.storage = pcontext.storage.storage, .lcontext = lcontext};
|
||||
populate_globals_visitor visitor{result, lcontext};
|
||||
for (auto it = module_in.nodes->begin(); it != module_in.nodes->end(); ++it)
|
||||
std::visit([&](auto const & instruction){ visitor.apply(it, instruction, it->inferred_type); }, it->instruction);
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Force the data page to be allocated
|
||||
// Helps with debugging in QtCreator (which pokes ~16 bytes before the code
|
||||
// page and can hit unmapped memory)
|
||||
if (data_begin == pcontext.storage.size())
|
||||
pcontext.storage.storage.push_back(0);
|
||||
#endif
|
||||
|
||||
auto data_end = pcontext.storage.align();
|
||||
if (data_begin != data_end)
|
||||
pcontext.storage.data.push_back({data_begin, data_end});
|
||||
|
||||
auto code_begin = data_end;
|
||||
|
||||
{
|
||||
populate_const_data_visitor visitor{pcontext, lcontext};
|
||||
for (auto it = module_in.nodes->begin(); it != module_in.nodes->end(); ++it)
|
||||
std::visit([&](auto const & instruction){ visitor.apply(instruction, it->inferred_type); }, it->instruction);
|
||||
}
|
||||
|
||||
instruction_builder builder{pcontext.storage.storage};
|
||||
instruction_builder builder{result.code};
|
||||
|
||||
for (auto const & function : module_in.functions)
|
||||
{
|
||||
pcontext.symbols[function.first] = pcontext.storage.size();
|
||||
compile_visitor visitor{pcontext, module_in, lcontext, builder};
|
||||
result.functions[function.second.begin] = result.code.size();
|
||||
compile_visitor visitor{result, lcontext, builder};
|
||||
visitor.compile(function.first, function.second.begin, function.second.end);
|
||||
}
|
||||
|
||||
pcontext.entry_point = lcontext.nodes.at(module_in.entry_point);
|
||||
if (module_in.entry_point)
|
||||
result.entry_point = module_in.functions.at(module_in.entry_point).begin;
|
||||
|
||||
for (auto const & resolve : lcontext.jump_resolve)
|
||||
builder.jump_inject_prev(pcontext.storage.storage.data() + resolve.offset, lcontext.nodes.at(resolve.target) - resolve.offset);
|
||||
builder.jump_inject_prev(result.code.data() + resolve.offset, lcontext.nodes.at(resolve.target) - resolve.offset);
|
||||
|
||||
for (auto const & resolve : lcontext.cjump_resolve)
|
||||
builder.cjump_inject_prev(pcontext.storage.storage.data() + resolve.offset, lcontext.nodes.at(resolve.target) - resolve.offset);
|
||||
builder.cjump_inject_prev(result.code.data() + resolve.offset, lcontext.nodes.at(resolve.target) - resolve.offset);
|
||||
|
||||
for (auto const & resolve : lcontext.node_resolve)
|
||||
builder.lea_rip_inject_prev(pcontext.storage.storage.data() + resolve.offset, lcontext.nodes.at(resolve.target) - resolve.offset);
|
||||
builder.lea_rip_inject_prev(result.code.data() + resolve.offset, lcontext.nodes.at(resolve.target) - resolve.offset);
|
||||
|
||||
for (auto const & resolve : lcontext.call_resolve)
|
||||
builder.call_imm_inject_prev(pcontext.storage.storage.data() + resolve.offset, lcontext.nodes.at(resolve.target) - resolve.offset);
|
||||
builder.call_imm_inject_prev(result.code.data() + resolve.offset, lcontext.nodes.at(resolve.target) - resolve.offset);
|
||||
|
||||
auto code_end = pcontext.storage.align();
|
||||
pcontext.storage.code.push_back({code_begin, code_end});
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,11 @@ namespace pslang::jit::linux_x86_64
|
|||
return rex(W, std::uint8_t(R) >> 3, std::uint8_t(X) >> 3, std::uint8_t(B) >> 3);
|
||||
}
|
||||
|
||||
bool is_rex(std::uint8_t value)
|
||||
{
|
||||
return (value & 0xf0) == 0x40;
|
||||
}
|
||||
|
||||
// Memory
|
||||
static constexpr std::uint8_t MOD_MEM = 0;
|
||||
// Memory + 8-bit offset
|
||||
|
|
@ -609,6 +614,17 @@ namespace pslang::jit::linux_x86_64
|
|||
cjump_inject(opcode, offset - 6);
|
||||
}
|
||||
|
||||
void instruction_builder::mov_rip_inject(std::uint8_t * opcode, std::int32_t offset)
|
||||
{
|
||||
auto src = (std::uint8_t const *)(&offset);
|
||||
std::copy(src, src + 4, opcode + 3);
|
||||
}
|
||||
|
||||
void instruction_builder::mov_rip_inject_prev(std::uint8_t * opcode, std::int32_t offset)
|
||||
{
|
||||
mov_rip_inject(opcode, offset - 7);
|
||||
}
|
||||
|
||||
void instruction_builder::lea_rip_inject(std::uint8_t * opcode, std::int32_t offset)
|
||||
{
|
||||
auto src = (std::uint8_t const *)(&offset);
|
||||
|
|
@ -644,6 +660,35 @@ namespace pslang::jit::linux_x86_64
|
|||
do_push(rex(0, {}, {}, reg_addr), 0xff_ub, modrm(MOD_REG, 0b010, reg_addr));
|
||||
}
|
||||
|
||||
void instruction_builder::resolve_offset(std::uint8_t * opcode, std::int32_t offset)
|
||||
{
|
||||
if (is_rex(*opcode)) ++opcode;
|
||||
|
||||
if (*opcode == 0x8d)
|
||||
{
|
||||
// lea rip prev
|
||||
opcode += 2;
|
||||
}
|
||||
else if (*opcode == 0x8b)
|
||||
{
|
||||
// mov read rip prev
|
||||
opcode += 2;
|
||||
}
|
||||
else if (*opcode == 0x89)
|
||||
{
|
||||
// mov write rip prev
|
||||
opcode += 2;
|
||||
}
|
||||
else
|
||||
throw std::runtime_error("Internal error: unknown opcode to patch");
|
||||
|
||||
std::int32_t current_offset;
|
||||
std::copy(opcode, opcode + 4, (std::uint8_t *)¤t_offset);
|
||||
current_offset += offset;
|
||||
auto begin = (std::uint8_t const *)¤t_offset;
|
||||
std::copy(begin, begin + 4, opcode);
|
||||
}
|
||||
|
||||
template <typename ... Args>
|
||||
void instruction_builder::do_push(Args ... values)
|
||||
{
|
||||
|
|
|
|||
26
libs/jit/source/arch/linux_x86_64/resolver.cpp
Normal file
26
libs/jit/source/arch/linux_x86_64/resolver.cpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#include <pslang/jit/arch/linux_x86_64/resolver.hpp>
|
||||
#include <pslang/jit/arch/linux_x86_64/instruction_builder.hpp>
|
||||
|
||||
namespace pslang::jit::linux_x86_64
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct resolver_impl
|
||||
: resolver
|
||||
{
|
||||
void resolve(std::uint8_t * opcode, std::int32_t offset) override
|
||||
{
|
||||
instruction_builder::resolve_offset(opcode, offset);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
std::unique_ptr<resolver> make_resolver()
|
||||
{
|
||||
return std::make_unique<resolver_impl>();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -27,7 +27,6 @@ namespace pslang::jit::macos_aarch64
|
|||
|
||||
std::unordered_map<ast::struct_definition const *, std::optional<hfa_data>> struct_hfa;
|
||||
|
||||
std::unordered_map<std::string, std::int32_t> extern_symbols;
|
||||
std::unordered_map<ir::node_ref, std::int32_t> nodes;
|
||||
|
||||
std::unordered_map<float, std::int32_t> f16_constants;
|
||||
|
|
@ -43,6 +42,8 @@ namespace pslang::jit::macos_aarch64
|
|||
std::vector<resolve_data> branch_resolve;
|
||||
std::vector<resolve_data> cbranch_resolve;
|
||||
std::vector<resolve_data> adr_resolve;
|
||||
|
||||
std::vector<compiled_module::external_function_resolve_info> external_function_resolve;
|
||||
};
|
||||
|
||||
std::uint8_t fp_mode_for(types::type const & type)
|
||||
|
|
@ -115,7 +116,7 @@ namespace pslang::jit::macos_aarch64
|
|||
|
||||
struct populate_globals_visitor
|
||||
{
|
||||
std::vector<std::uint8_t> & storage;
|
||||
compiled_module & result;
|
||||
local_context & lcontext;
|
||||
|
||||
template <typename Node>
|
||||
|
|
@ -129,18 +130,18 @@ namespace pslang::jit::macos_aarch64
|
|||
throw std::runtime_error("global IR node with initializer larger than type size");
|
||||
|
||||
auto alignment = ast::type_alignment(*type);
|
||||
auto offset = storage.size();
|
||||
auto offset = result.data.size();
|
||||
offset = ((offset + (alignment - 1)) / alignment) * alignment;
|
||||
storage.resize(offset + size);
|
||||
std::copy(node.initializer.begin(), node.initializer.end(), storage.begin() + offset);
|
||||
lcontext.nodes[it] = offset;
|
||||
result.data.resize(offset + size);
|
||||
std::copy(node.initializer.begin(), node.initializer.end(), result.data.begin() + offset);
|
||||
result.globals[it] = offset;
|
||||
}
|
||||
};
|
||||
|
||||
struct populate_const_data_visitor
|
||||
{
|
||||
program_context & pcontext;
|
||||
local_context & lcontext;
|
||||
instruction_builder & builder;
|
||||
|
||||
template <typename Node>
|
||||
void apply(Node const & node, types::type_ptr const &)
|
||||
|
|
@ -150,38 +151,30 @@ namespace pslang::jit::macos_aarch64
|
|||
{
|
||||
if (auto f16_literal = std::get_if<ast::f16_literal>(&node.value))
|
||||
{
|
||||
lcontext.f16_constants[f16_literal->value.repr] = pcontext.storage.size();
|
||||
lcontext.f16_constants[f16_literal->value.repr] = builder.code.size();
|
||||
push_bytes(f16_literal->value.repr);
|
||||
// Ensure 4-byte alignment
|
||||
push_bytes(std::uint16_t{0});
|
||||
}
|
||||
else if (auto f32_literal = std::get_if<ast::f32_literal>(&node.value))
|
||||
{
|
||||
lcontext.f32_constants[f32_literal->value] = pcontext.storage.size();
|
||||
lcontext.f32_constants[f32_literal->value] = builder.code.size();
|
||||
push_bytes(f32_literal->value);
|
||||
}
|
||||
else if (auto f64_literal = std::get_if<ast::f64_literal>(&node.value))
|
||||
{
|
||||
lcontext.f32_constants[f64_literal->value] = pcontext.storage.size();
|
||||
lcontext.f32_constants[f64_literal->value] = builder.code.size();
|
||||
push_bytes(f64_literal->value);
|
||||
}
|
||||
}
|
||||
|
||||
void apply(ir::extern_symbol const & node, types::type_ptr const &)
|
||||
{
|
||||
std::int32_t offset = pcontext.storage.size();
|
||||
lcontext.extern_symbols[node.name] = offset;
|
||||
pcontext.foreign_resolve.push_back({node.name, offset});
|
||||
push_bytes<void *>(nullptr);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
void push_bytes(T const & value)
|
||||
{
|
||||
auto begin = (std::uint8_t const *)(&value);
|
||||
auto end = begin + sizeof(value);
|
||||
pcontext.storage.storage.insert(pcontext.storage.storage.end(), begin, end);
|
||||
builder.code.insert(builder.code.end(), begin, end);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -193,7 +186,6 @@ namespace pslang::jit::macos_aarch64
|
|||
|
||||
struct literal_visitor
|
||||
{
|
||||
program_context & pcontext;
|
||||
local_context & lcontext;
|
||||
instruction_builder & builder;
|
||||
|
||||
|
|
@ -236,7 +228,7 @@ namespace pslang::jit::macos_aarch64
|
|||
void operator()(ast::f16_literal const & node)
|
||||
{
|
||||
auto offset = lcontext.f16_constants.at(node.value.repr);
|
||||
std::int32_t current = pcontext.storage.size();
|
||||
std::int32_t current = builder.code.size();
|
||||
builder.adr(0, (offset - current) / 4);
|
||||
builder.ldr_fp(0, 1, 0, 0);
|
||||
}
|
||||
|
|
@ -244,14 +236,14 @@ namespace pslang::jit::macos_aarch64
|
|||
void operator()(ast::f32_literal const & node)
|
||||
{
|
||||
auto offset = lcontext.f32_constants.at(node.value);
|
||||
std::int32_t current = pcontext.storage.size();
|
||||
std::int32_t current = builder.code.size();
|
||||
builder.ldr_fp_pc(0, 0, (offset - current) / 4);
|
||||
}
|
||||
|
||||
void operator()(ast::f64_literal const & node)
|
||||
{
|
||||
auto offset = lcontext.f64_constants.at(node.value);
|
||||
std::int32_t current = pcontext.storage.size();
|
||||
std::int32_t current = builder.code.size();
|
||||
builder.ldr_fp_pc(0, 1, (offset - current) / 4);
|
||||
}
|
||||
};
|
||||
|
|
@ -312,8 +304,6 @@ namespace pslang::jit::macos_aarch64
|
|||
|
||||
struct compile_visitor
|
||||
{
|
||||
program_context & pcontext;
|
||||
ir::compiled_module const & module_in;
|
||||
local_context & lcontext;
|
||||
instruction_builder & builder;
|
||||
|
||||
|
|
@ -327,7 +317,7 @@ namespace pslang::jit::macos_aarch64
|
|||
|
||||
void apply(ir::node_ref it, ir::literal const & node, types::type_ptr const & type)
|
||||
{
|
||||
std::visit(literal_visitor{pcontext, lcontext, builder}, node.value);
|
||||
std::visit(literal_visitor{lcontext, builder}, node.value);
|
||||
if (types::is_integer_like_type(*type))
|
||||
store(it, 0);
|
||||
else if (types::is_floating_point_type(*type))
|
||||
|
|
@ -734,14 +724,15 @@ namespace pslang::jit::macos_aarch64
|
|||
|
||||
void apply(ir::node_ref it, ir::instruction_address const & node, types::type_ptr const &)
|
||||
{
|
||||
lcontext.adr_resolve.emplace_back(pcontext.storage.size(), node.target);
|
||||
lcontext.adr_resolve.emplace_back(builder.code.size(), node.target);
|
||||
builder.adr(0, 0);
|
||||
store(it, 0);
|
||||
}
|
||||
|
||||
void apply(ir::node_ref it, ir::extern_symbol const & node, types::type_ptr const &)
|
||||
{
|
||||
builder.ldr_pc(0, (lcontext.extern_symbols[node.name] - (std::int32_t)pcontext.storage.size()) / 4);
|
||||
lcontext.external_function_resolve.push_back({.name = node.name, .instruction_offset = (std::int32_t)builder.code.size()});
|
||||
builder.ldr_pc(0, 0);
|
||||
store(it, 0);
|
||||
}
|
||||
|
||||
|
|
@ -774,7 +765,7 @@ namespace pslang::jit::macos_aarch64
|
|||
|
||||
void apply(ir::node_ref, ir::jump const & node, types::type_ptr const &)
|
||||
{
|
||||
lcontext.branch_resolve.emplace_back(pcontext.storage.size(), node.target);
|
||||
lcontext.branch_resolve.emplace_back(builder.code.size(), node.target);
|
||||
builder.b(0);
|
||||
}
|
||||
|
||||
|
|
@ -782,7 +773,7 @@ namespace pslang::jit::macos_aarch64
|
|||
{
|
||||
load(node.condition, 0);
|
||||
extend(0, node.condition->inferred_type);
|
||||
lcontext.cbranch_resolve.emplace_back(pcontext.storage.size(), node.target);
|
||||
lcontext.cbranch_resolve.emplace_back(builder.code.size(), node.target);
|
||||
builder.cbz(0, 0);
|
||||
}
|
||||
|
||||
|
|
@ -790,7 +781,7 @@ namespace pslang::jit::macos_aarch64
|
|||
{
|
||||
load(node.condition, 0);
|
||||
extend(0, node.condition->inferred_type);
|
||||
lcontext.cbranch_resolve.emplace_back(pcontext.storage.size(), node.target);
|
||||
lcontext.cbranch_resolve.emplace_back(builder.code.size(), node.target);
|
||||
builder.cbnz(0, 0);
|
||||
}
|
||||
|
||||
|
|
@ -921,7 +912,7 @@ namespace pslang::jit::macos_aarch64
|
|||
void apply(ir::node_ref it, ir::call const & node, types::type_ptr const & type)
|
||||
{
|
||||
apply_call(it, node, type, [&]{
|
||||
lcontext.branch_resolve.emplace_back(pcontext.storage.size(), node.target);
|
||||
lcontext.branch_resolve.emplace_back(builder.code.size(), node.target);
|
||||
builder.bl(0);
|
||||
});
|
||||
}
|
||||
|
|
@ -1039,7 +1030,7 @@ namespace pslang::jit::macos_aarch64
|
|||
|
||||
auto it = begin;
|
||||
|
||||
lcontext.nodes[it] = pcontext.storage.size();
|
||||
lcontext.nodes[it] = builder.code.size();
|
||||
if (stack_size > 0)
|
||||
builder.sub_imm(31, 31, stack_size);
|
||||
if (lcontext.use_frame_pointer)
|
||||
|
|
@ -1109,7 +1100,7 @@ namespace pslang::jit::macos_aarch64
|
|||
// Uncomment to debug per-node instruction generation:
|
||||
// builder.nop();
|
||||
|
||||
lcontext.nodes[it] = pcontext.storage.size();
|
||||
lcontext.nodes[it] = builder.code.size();
|
||||
std::visit([&](auto const & instruction){ apply(it, instruction, it->inferred_type); }, it->instruction);
|
||||
}
|
||||
}
|
||||
|
|
@ -1217,50 +1208,49 @@ namespace pslang::jit::macos_aarch64
|
|||
|
||||
}
|
||||
|
||||
void compile(program_context & pcontext, ir::compiled_module const & module_in)
|
||||
compiled_module compile(ir::compiled_module const & module_in)
|
||||
{
|
||||
compiled_module result;
|
||||
|
||||
local_context lcontext;
|
||||
|
||||
auto data_begin = pcontext.storage.align();
|
||||
{
|
||||
populate_globals_visitor visitor{.storage = pcontext.storage.storage, .lcontext = lcontext};
|
||||
populate_globals_visitor visitor{result, lcontext};
|
||||
for (auto it = module_in.nodes->begin(); it != module_in.nodes->end(); ++it)
|
||||
std::visit([&](auto const & instruction){ visitor.apply(it, instruction, it->inferred_type); }, it->instruction);
|
||||
}
|
||||
auto data_end = pcontext.storage.align();
|
||||
if (data_begin != data_end)
|
||||
pcontext.storage.data.push_back({data_begin, data_end});
|
||||
|
||||
auto code_begin = data_end;
|
||||
instruction_builder builder{result.data};
|
||||
|
||||
{
|
||||
populate_const_data_visitor visitor{pcontext, lcontext};
|
||||
populate_const_data_visitor visitor{lcontext, builder};
|
||||
for (auto it = module_in.nodes->begin(); it != module_in.nodes->end(); ++it)
|
||||
std::visit([&](auto const & instruction){ visitor.apply(instruction, it->inferred_type); }, it->instruction);
|
||||
}
|
||||
|
||||
instruction_builder builder{pcontext.storage.storage};
|
||||
|
||||
for (auto const & function : module_in.functions)
|
||||
{
|
||||
pcontext.symbols[function.first] = pcontext.storage.size();
|
||||
compile_visitor visitor{pcontext, module_in, lcontext, builder};
|
||||
result.functions[function.second.begin] = result.code.size();
|
||||
compile_visitor visitor{lcontext, builder};
|
||||
visitor.compile(function.first, function.second.begin, function.second.end);
|
||||
}
|
||||
|
||||
pcontext.entry_point = lcontext.nodes.at(module_in.entry_point);
|
||||
if (module_in.entry_point)
|
||||
result.entry_point = module_in.functions.at(module_in.entry_point).begin;
|
||||
|
||||
for (auto const & resolve : lcontext.branch_resolve)
|
||||
builder.b_inject(pcontext.storage.storage.data() + resolve.offset, (lcontext.nodes.at(resolve.target) - resolve.offset) / 4);
|
||||
builder.b_inject(result.code.data() + resolve.offset, (lcontext.nodes.at(resolve.target) - resolve.offset) / 4);
|
||||
|
||||
for (auto const & resolve : lcontext.cbranch_resolve)
|
||||
builder.cb_inject(pcontext.storage.storage.data() + resolve.offset, (lcontext.nodes.at(resolve.target) - resolve.offset) / 4);
|
||||
builder.cb_inject(result.code.data() + resolve.offset, (lcontext.nodes.at(resolve.target) - resolve.offset) / 4);
|
||||
|
||||
for (auto const & resolve : lcontext.adr_resolve)
|
||||
builder.adr_inject(pcontext.storage.storage.data() + resolve.offset, lcontext.nodes.at(resolve.target) - resolve.offset);
|
||||
builder.adr_inject(result.code.data() + resolve.offset, lcontext.nodes.at(resolve.target) - resolve.offset);
|
||||
|
||||
auto code_end = pcontext.storage.align();
|
||||
pcontext.storage.code.push_back({code_begin, code_end});
|
||||
result.external_function_resolve = std::move(lcontext.external_function_resolve);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,6 +143,15 @@ namespace pslang::jit::macos_aarch64
|
|||
do_push(0x58000000u | (reg_dst & REG_MASK) | ((((std::uint32_t)offset) & 0x7ffffu) << 5));
|
||||
}
|
||||
|
||||
void instruction_builder::ldr_pc_inject(std::uint8_t * opcode, std::int32_t offset)
|
||||
{
|
||||
check_bits(offset, 19, "Bad ldr_pc_inject offset value");
|
||||
auto offset_val = std::uint32_t(offset) & 0x7ffffu;
|
||||
auto dst = (std::uint32_t *)opcode;
|
||||
*dst &= 0xff00001fu;
|
||||
*dst |= offset_val << 5;
|
||||
}
|
||||
|
||||
void instruction_builder::add_imm(std::uint8_t reg_src, std::uint8_t reg_dst, std::uint16_t value)
|
||||
{
|
||||
check_bits(value, 12, "Bad add_imm value");
|
||||
|
|
|
|||
26
libs/jit/source/arch/macos_aarch64/resolver.cpp
Normal file
26
libs/jit/source/arch/macos_aarch64/resolver.cpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#include <pslang/jit/arch/macos_aarch64/resolver.hpp>
|
||||
#include <pslang/jit/arch/macos_aarch64/instruction_builder.hpp>
|
||||
|
||||
namespace pslang::jit::macos_aarch64
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct resolver_impl
|
||||
: resolver
|
||||
{
|
||||
void resolve(std::uint8_t * opcode, std::int32_t offset) override
|
||||
{
|
||||
instruction_builder::ldr_pc_inject(opcode, offset / 4);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
std::unique_ptr<resolver> make_resolver()
|
||||
{
|
||||
return std::make_unique<resolver_impl>();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#include <pslang/jit/jit.hpp>
|
||||
#include <pslang/jit/compiler.hpp>
|
||||
#include <pslang/jit/arch/macos_aarch64/compiler.hpp>
|
||||
#include <pslang/jit/arch/linux_x86_64/compiler.hpp>
|
||||
|
||||
|
|
@ -7,16 +7,15 @@
|
|||
namespace pslang::jit
|
||||
{
|
||||
|
||||
void compile(program_context & pcontext, ir::compiled_module const & module_in)
|
||||
compiled_module compile(ir::compiled_module const & module_in, abi abi)
|
||||
{
|
||||
switch (pcontext.abi.platform)
|
||||
switch (abi.platform)
|
||||
{
|
||||
case platform::linux:
|
||||
switch (pcontext.abi.isa)
|
||||
switch (abi.isa)
|
||||
{
|
||||
case isa::x86_64:
|
||||
linux_x86_64::compile(pcontext, module_in);
|
||||
break;
|
||||
return linux_x86_64::compile(module_in);
|
||||
case isa::aarch64:
|
||||
throw std::runtime_error("Linux aarch64 JIT compilation not supported");
|
||||
}
|
||||
|
|
@ -25,16 +24,17 @@ namespace pslang::jit
|
|||
case platform::windows:
|
||||
throw std::runtime_error("Windows JIT compilation not supported");
|
||||
case platform::macos:
|
||||
switch (pcontext.abi.isa)
|
||||
switch (abi.isa)
|
||||
{
|
||||
case isa::x86_64:
|
||||
throw std::runtime_error("macOS x86_64 JIT compilation not supported");
|
||||
case isa::aarch64:
|
||||
macos_aarch64::compile(pcontext, module_in);
|
||||
break;
|
||||
return macos_aarch64::compile(module_in);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
throw std::runtime_error("Internal error: unknown isa+platform combination");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
#include <pslang/jit/executable.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <system_error>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
namespace pslang::jit
|
||||
{
|
||||
|
||||
std::shared_ptr<std::uint8_t> make_host_executable(binary_storage const & storage)
|
||||
{
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
auto const page_size = native_page_size();
|
||||
|
||||
for (auto const & range : storage.data)
|
||||
{
|
||||
if ((range.begin % page_size) != 0)
|
||||
throw std::runtime_error("Data range start is not aligned to page boundary");
|
||||
|
||||
if ((range.end % page_size) != 0)
|
||||
throw std::runtime_error("Data range end is not aligned to page boundary");
|
||||
}
|
||||
|
||||
for (auto const & range : storage.code)
|
||||
{
|
||||
if ((range.begin % page_size) != 0)
|
||||
throw std::runtime_error("Code range start is not aligned to page boundary");
|
||||
|
||||
if ((range.end % page_size) != 0)
|
||||
throw std::runtime_error("Code range end is not aligned to page boundary");
|
||||
}
|
||||
|
||||
auto total_size = storage.storage.size();
|
||||
auto ptr = (std::uint8_t *)mmap(nullptr, total_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
|
||||
if (ptr == MAP_FAILED)
|
||||
throw std::system_error(errno, std::generic_category());
|
||||
|
||||
std::copy(storage.storage.begin(), storage.storage.end(), ptr);
|
||||
|
||||
for (auto const & range : storage.code)
|
||||
if (mprotect(ptr + range.begin, range.end - range.begin, PROT_READ | PROT_EXEC) != 0)
|
||||
throw std::system_error(errno, std::generic_category());
|
||||
|
||||
return std::shared_ptr<std::uint8_t>(ptr, [total_size](void * ptr){ munmap(ptr, total_size); });
|
||||
#else
|
||||
throw std::runtime_error("Host-executable modules are not supported for this platform");
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
71
libs/jit/source/host_executable.cpp
Normal file
71
libs/jit/source/host_executable.cpp
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#include <pslang/jit/host_executable.hpp>
|
||||
#include <pslang/jit/linker.hpp>
|
||||
#include <pslang/jit/foreign.hpp>
|
||||
|
||||
#include <system_error>
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace pslang::jit
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::size_t native_page_size()
|
||||
{
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
return getpagesize();
|
||||
#else
|
||||
throw std::runtime_error("Native page size is not supported for this platform");
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
host_executable_program make_host_executable(std::vector<compiled_module> const & modules)
|
||||
{
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
auto const page_size = native_page_size();
|
||||
auto const abi = host_abi();
|
||||
|
||||
for (auto const & module : modules)
|
||||
if (module.abi != abi)
|
||||
throw std::runtime_error("Internal error: wrong compiled module ABI for host-executable binary");
|
||||
|
||||
auto link_info = prepare_link(modules, {.section_alignment = page_size});
|
||||
|
||||
auto storage = (std::uint8_t *)mmap(nullptr, link_info.total_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
|
||||
if (storage == MAP_FAILED)
|
||||
throw std::system_error(errno, std::generic_category());
|
||||
|
||||
host_executable_program result;
|
||||
result.mapping.reset(storage, [total_size = link_info.total_size](void * ptr){ munmap(ptr, total_size); });
|
||||
|
||||
link(modules, link_info, storage);
|
||||
|
||||
for (auto const & relocation : link_info.relocation_offset)
|
||||
*(void **)(storage + relocation.second) = load_foreign(relocation.first);
|
||||
|
||||
if (mprotect(storage + link_info.code.begin, link_info.code.size, PROT_READ | PROT_EXEC) != 0)
|
||||
throw std::system_error(errno, std::generic_category());
|
||||
|
||||
if (mprotect(storage + link_info.relocations.begin, link_info.relocations.size, PROT_READ) != 0)
|
||||
throw std::system_error(errno, std::generic_category());
|
||||
|
||||
result.functions = std::move(link_info.functions);
|
||||
result.globals = std::move(link_info.globals);
|
||||
result.entry_points = std::move(link_info.entry_points);
|
||||
|
||||
return result;
|
||||
#else
|
||||
throw std::runtime_error("Host-executable modules are not supported for this platform");
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
110
libs/jit/source/linker.cpp
Normal file
110
libs/jit/source/linker.cpp
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
#include <pslang/jit/linker.hpp>
|
||||
#include <pslang/jit/resolver.hpp>
|
||||
|
||||
namespace pslang::jit
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::size_t align(std::size_t value, std::size_t alignment)
|
||||
{
|
||||
return ((value + alignment - 1) / alignment) * alignment;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
linked_program_info prepare_link(std::vector<compiled_module> const & modules, link_options const & options)
|
||||
{
|
||||
linked_program_info result;
|
||||
|
||||
std::size_t total_code_size = 0;
|
||||
std::size_t total_data_size = 0;
|
||||
std::size_t total_relo_size = options.reserved_relocations * 8;
|
||||
|
||||
for (auto const & module : modules)
|
||||
{
|
||||
for (auto const & function : module.functions)
|
||||
result.functions[function.first] = total_code_size + function.second;
|
||||
|
||||
for (auto const & global : module.globals)
|
||||
result.globals[global.first] = total_data_size + global.second;
|
||||
|
||||
for (auto const & relocation : module.external_function_resolve)
|
||||
if (!result.relocation_offset.contains(relocation.name))
|
||||
{
|
||||
result.relocation_offset[relocation.name] = total_relo_size;
|
||||
total_relo_size += 8;
|
||||
}
|
||||
|
||||
if (module.entry_point)
|
||||
result.entry_points.push_back(total_code_size + module.functions.at(*module.entry_point));
|
||||
else
|
||||
result.entry_points.push_back(std::nullopt);
|
||||
|
||||
total_code_size += module.code.size();
|
||||
total_data_size += module.data.size();
|
||||
}
|
||||
|
||||
total_code_size = align(total_code_size, options.section_alignment);
|
||||
total_data_size = align(total_data_size, options.section_alignment);
|
||||
total_relo_size = align(total_relo_size, options.section_alignment);
|
||||
|
||||
std::size_t const data_offset = total_code_size;
|
||||
std::size_t const relo_offset = total_code_size + total_data_size;
|
||||
result.total_size = total_code_size + total_data_size + total_relo_size;
|
||||
|
||||
for (auto & global : result.globals)
|
||||
global.second += data_offset;
|
||||
|
||||
for (auto & relocation : result.relocation_offset)
|
||||
relocation.second += relo_offset;
|
||||
|
||||
result.code = {0, total_code_size};
|
||||
result.data = {total_code_size, total_data_size};
|
||||
result.relocations = {total_code_size + total_data_size, total_relo_size};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void link(std::vector<compiled_module> const & modules, linked_program_info const & info, std::uint8_t * storage)
|
||||
{
|
||||
// TODO: resolve internal symbols (across different modules)
|
||||
|
||||
if (modules.empty())
|
||||
return;
|
||||
|
||||
auto resolver = make_resolver(modules.front().abi.isa);
|
||||
|
||||
{
|
||||
auto dst = storage + info.code.begin;
|
||||
|
||||
for (auto const & module : modules)
|
||||
{
|
||||
auto end = std::copy(module.code.data(), module.code.data() + module.code.size(), dst);
|
||||
|
||||
for (auto const & resolve : module.internal_global_resolve)
|
||||
{
|
||||
std::int32_t offset = info.globals.at(resolve.global) - (dst - storage + resolve.instruction_offset);
|
||||
resolver->resolve(dst + resolve.instruction_offset, offset);
|
||||
}
|
||||
|
||||
for (auto const & resolve : module.external_function_resolve)
|
||||
{
|
||||
std::int32_t offset = info.relocation_offset.at(resolve.name) - (dst - storage + resolve.instruction_offset);
|
||||
resolver->resolve(dst + resolve.instruction_offset, offset);
|
||||
}
|
||||
|
||||
dst = end;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto dst = storage + info.data.begin;
|
||||
|
||||
for (auto const & module : modules)
|
||||
dst = std::copy(module.data.data(), module.data.data() + module.data.size(), dst);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
21
libs/jit/source/resolver.cpp
Normal file
21
libs/jit/source/resolver.cpp
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#include <pslang/jit/resolver.hpp>
|
||||
#include <pslang/jit/arch/linux_x86_64/resolver.hpp>
|
||||
#include <pslang/jit/arch/macos_aarch64/resolver.hpp>
|
||||
|
||||
namespace pslang::jit
|
||||
{
|
||||
|
||||
std::unique_ptr<resolver> make_resolver(isa isa)
|
||||
{
|
||||
switch (isa)
|
||||
{
|
||||
case isa::x86_64:
|
||||
return linux_x86_64::make_resolver();
|
||||
case isa::aarch64:
|
||||
return macos_aarch64::make_resolver();
|
||||
}
|
||||
|
||||
throw std::runtime_error("Unknown ISA for resolver");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
#include <pslang/jit/storage.hpp>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace pslang::jit
|
||||
{
|
||||
|
||||
std::size_t binary_storage::align()
|
||||
{
|
||||
auto const page_size = native_page_size();
|
||||
auto aligned_size = ((storage.size() + (page_size - 1)) / page_size) * page_size;
|
||||
storage.resize(aligned_size);
|
||||
return storage.size();
|
||||
}
|
||||
|
||||
std::size_t native_page_size()
|
||||
{
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
return getpagesize();
|
||||
#else
|
||||
throw std::runtime_error("Native page size is not supported for this platform");
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
Add table
Reference in a new issue