compiler: compile_call

This commit is contained in:
ProgramSnail 2025-01-22 19:44:26 +03:00
parent 1b26d0eaae
commit 02e22ff99b

View file

@ -42,14 +42,52 @@ std::vector<U> transform(std::vector<T> v, const std::function<U(T &&)> &f) {
return result; return result;
} }
template <typename T> void insert(std::vector<T> &x, std::vector<T> &&y) {
x.insert(x.end(), std::move_iterator(y.begin()), std::move_iterator(y.end()));
}
template <typename T> std::vector<T> reverse(std::vector<T> &&x) {
std::reverse(x.begin(), x.end());
return std::move(x);
}
template <typename T> std::vector<T> concat(std::vector<T> &&x) { template <typename T> std::vector<T> concat(std::vector<T> &&x) {
return std::move(x); return std::move(x);
} }
template <typename T, typename... Args> // --> declarations
std::vector<T> concat(std::vector<T> &&x, std::vector<T> &&y, Args &&...args) { template <typename T, typename U, typename... Args>
x.insert(x.end(), std::move_iterator(y.begin()), std::move_iterator(y.end())); requires std::is_same_v<U, T>
return concat(std::move(x), std::forward<Args>(args)...); std::vector<T> concat(std::vector<T> &&x, U &&y, Args &&...args);
template <typename T, typename U, typename... Args>
requires std::is_same_v<U, std::optional<T>>
std::vector<T> concat(std::vector<T> &&x, U &&y, Args &&...args);
template <typename T, typename U, typename... Args>
requires std::is_same_v<U, std::vector<T>>
std::vector<T> concat(std::vector<T> &&x, U &&y, Args &&...args);
// <--
template <typename T, typename U, typename... Args>
requires std::is_same_v<U, std::vector<T>>
std::vector<T> concat(std::vector<T> &&x, U &&y, Args &&...args) {
insert(x, std::move(y));
return concat<T, Args...>(std::move(x), std::forward<Args>(args)...);
}
template <typename T, typename U, typename... Args>
requires std::is_same_v<U, T>
std::vector<T> concat(std::vector<T> &&x, U &&y, Args &&...args) {
x.push_back(std::move(y));
return concat<T, Args...>(std::move(x), std::forward<Args>(args)...);
}
template <typename T, typename U, typename... Args>
requires std::is_same_v<U, std::optional<T>>
std::vector<T> concat(std::vector<T> &&x, U &&y, Args &&...args) {
if (y) {
x.push_back(std::move(*y));
}
return concat<T, Args...>(std::move(x), std::forward<Args>(args)...);
} }
// template <typename T, typename... Args> // template <typename T, typename... Args>
@ -167,6 +205,7 @@ const auto r12 = Register::from_number(12);
const auto r13 = Register::from_number(13); const auto r13 = Register::from_number(13);
const auto r14 = Register::from_number(14); const auto r14 = Register::from_number(14);
const auto r15 = Register::from_number(15); const auto r15 = Register::from_number(15);
const std::vector<Register::T> argument_registers = {rdi, rsi, rdx, const std::vector<Register::T> argument_registers = {rdi, rsi, rdx,
rcx, r8, r9}; rcx, r8, r9};
const std::vector<Register::T> extra_caller_saved_registers = {r10, r11, r12, const std::vector<Register::T> extra_caller_saved_registers = {r10, r11, r12,
@ -276,6 +315,8 @@ using M = Opnd::M;
using R = Opnd::R; using R = Opnd::R;
using S = Opnd::S; using S = Opnd::S;
/* Value that could be used to fill unused stack locations.
Garbage is not allowed as it will affect GC. */
struct ArgumentLocation { struct ArgumentLocation {
struct Register { struct Register {
Opnd opnd; Opnd opnd;
@ -466,13 +507,13 @@ struct ValT {
std::string s; std::string s;
}; };
struct Local { struct Local {
int n; size_t n;
}; };
struct Arg { struct Arg {
int n; size_t n;
}; };
struct Access { struct Access {
int n; size_t n;
}; };
struct Fun { struct Fun {
std::string s; std::string s;
@ -550,6 +591,10 @@ template <typename U> struct AbstractSymbolicStack {
const W &operator*() const { return val; } const W &operator*() const { return val; }
const W &operator->() const { return val; } const W &operator->() const { return val; }
template <typename S> bool is() const {
return std::holds_alternative<S>(val);
}
}; };
using Stack = SymbolicLocation::Stack; using Stack = SymbolicLocation::Stack;
using Register = SymbolicLocation::Register; using Register = SymbolicLocation::Register;
@ -665,8 +710,9 @@ struct SymbolicStack {
using R = AbSS::StackState::R; using R = AbSS::StackState::R;
using E = AbSS::StackState::E; using E = AbSS::StackState::E;
using Stack = AbSS::SymbolicLocation::Stack; using SymbolicLocation = AbSS::SymbolicLocation;
using Register = AbSS::SymbolicLocation::Register; using Stack = SymbolicLocation::Stack;
using Register = SymbolicLocation::Register;
// type t // type t
@ -694,7 +740,7 @@ struct SymbolicStack {
}; };
} }
static Opnd opnd_from_loc(const T &v, const AbSS::SymbolicLocation &loc) { static Opnd opnd_from_loc(const T &v, const SymbolicLocation &loc) {
return std::visit( return std::visit(
utils::multifunc{ utils::multifunc{
[](const Register &x) -> Opnd { return {Opnd::R{x.r}}; }, [](const Register &x) -> Opnd { return {Opnd::R{x.r}}; },
@ -900,7 +946,8 @@ public:
/* is rdx register in use */ /* is rdx register in use */
bool rdx_in_use() const { return nargs > 2; } bool rdx_in_use() const { return nargs > 2; }
std::vector<Opnd> arguments_locations(size_t n) { std::pair<std::vector<SymbolicStack::SymbolicLocation>, size_t>
arguments_locations(size_t n) {
// TODO // TODO
// if n < argument_registers_size then // if n < argument_registers_size then
// ( Array.to_list (Array.sub argument_registers 0 n) // ( Array.to_list (Array.sub argument_registers 0 n)
@ -1421,7 +1468,7 @@ std::vector<Instr> compile_binop(Env<Prg, mode_> &env, Opr op) {
stack. As we do not have control where does the C compiler locate them in the stack. As we do not have control where does the C compiler locate them in the
moment of GC, we have to explicitly locate them on the stack. And to the moment of GC, we have to explicitly locate them on the stack. And to the
runtime function we are passing a reference to their location. */ runtime function we are passing a reference to their location. */
const std::vector<std::string> safepoint_functions = { const std::unordered_set<std::string> safepoint_functions = {
utils::labeled("s__Infix_58"), utils::labeled("substring"), utils::labeled("s__Infix_58"), utils::labeled("substring"),
utils::labeled("clone"), utils::labeled_builtin("string"), utils::labeled("clone"), utils::labeled_builtin("string"),
utils::labeled("stringcat"), utils::labeled("string"), utils::labeled("stringcat"), utils::labeled("string"),
@ -1434,16 +1481,261 @@ const std::vector<std::string> safepoint_functions = {
/* Lsprintf, or Bsprintf is an extra dirty hack that probably works */ /* Lsprintf, or Bsprintf is an extra dirty hack that probably works */
}; };
const std::vector<std::pair<std::string, size_t>> vararg_functions = { const std::unordered_map<std::string, size_t> vararg_functions = {
{utils::labeled("printf"), 1}, {utils::labeled("printf"), 1},
{utils::labeled("fprintf"), 2}, {utils::labeled("fprintf"), 2},
{utils::labeled("sprintf"), 1}, {utils::labeled("sprintf"), 1},
{utils::labeled("failure"), 1}, {utils::labeled("failure"), 1},
}; };
namespace utils::call_compilation::tail {
// NOTE: all comands in result are in inversed order
void push_args_rec_inv(Env<Prg, mode_> &env, std::vector<Instr> &acc,
size_t n) {
if (n == 0) {
return;
}
const auto x = env.pop();
utils::insert(acc, utils::reverse(mov(x, env.loc(ValT::Arg{n - 1}))));
push_args_rec_inv(env, acc, n - 1);
}
std::vector<Instr> push_args(Env<Prg, mode_> &env, size_t n) {
std::vector<Instr> acc;
push_args_rec_inv(env, acc, n);
std::reverse(acc.begin(), acc.end());
return acc;
}
} // namespace utils::call_compilation::tail
std::vector<Instr> compile_tail_call(Env<Prg, mode_> &env,
const std::optional<std::string> &fname,
size_t nargs) {
using namespace utils::call_compilation::tail;
std::vector<Instr> pushs = push_args(env, nargs);
std::optional<Instr> setup_closure;
if (!fname) {
const auto closure = env.pop();
setup_closure = Mov{closure, r15};
}
Instr add_argc_counter = Mov{L{static_cast<int>(nargs)}, r11};
Instr jump = fname ? Instr{Jmp{*fname}} : Instr{JmpI{r15}};
env.allocate();
return utils::concat(std::move(pushs), Instr{Mov{rbp, rsp}}, Instr{Pop{rbp}},
std::move(setup_closure), std::move(add_argc_counter),
std::move(jump));
}
namespace utils::call_compilation {
std::vector<Opnd> pop_arguments(Env<Prg, mode_> &env, size_t n) {
std::vector<Opnd> result;
result.reserve(n);
for (size_t i = 0; i < n; ++i) {
const auto x = env.pop();
result.push_back(x);
}
std::reverse(result.begin(), result.end());
return result;
};
namespace common {
std::pair<size_t, std::vector<Instr>> setup_arguments(Env<Prg, mode_> &env,
size_t nargs) {
const auto move_arguments =
[](std::vector<Opnd> &&args,
std::vector<SymbolicStack::SymbolicLocation> &&arg_locs) {
using Register = SymbolicStack::Register;
using Stack = SymbolicStack::Stack;
assert(args.size() == arg_locs.size());
std::vector<Instr> result;
result.reserve(args.size());
// NOTE: direction should be (fold left)
for (size_t i = 0; i < args.size(); ++i) {
result.push_back(
arg_locs[i].is<Register>()
? Instr{Mov{args[i], std::get<Register>(*arg_locs[i]).r}}
: /*Stack*/ Push{args[i]});
}
std::reverse(result.begin(), result.end());
return result;
};
auto args = pop_arguments(env, nargs);
auto [arg_locs, stack_slots] = env.arguments_locations(args.size());
auto setup_args_code = move_arguments(std::move(args), std::move(arg_locs));
return {stack_slots, std::move(setup_args_code)};
}
std::optional<Instr> setup_closure(Env<Prg, mode_> &env,
const std::optional<std::string> &fname) {
if (!fname) {
return {};
}
const auto closure = env.pop();
return Mov{closure, r15};
}
Instr call(const std::optional<std::string> &fname) {
return fname ? Instr{Call{*fname}} : Instr{CallI{r15}};
}
Instr add_argc_counter(const std::optional<std::string> &fname, size_t nargs) {
const auto it =
fname ? vararg_functions.find(*fname) : vararg_functions.end();
size_t argc = it == vararg_functions.end() ? 0 : it->second;
return Mov{L{static_cast<int>(nargs - argc)}, r11};
}
} // namespace common
std::pair<std::vector<Instr>, std::vector<Instr>>
protect_registers(Env<Prg, mode_> &env) {
std::vector<Instr> pushr;
std::vector<Instr> popr;
if (env.has_closure) {
pushr.push_back(Push{r15});
popr.push_back(Pop{r15});
}
pushr = utils::concat(
std::move(pushr),
utils::transform<Opnd, Instr>(env.live_registers(),
[](const auto &r) { return Push{r}; }));
popr = utils::concat(
std::move(popr),
utils::transform<Opnd, Instr>(
env.live_registers(), [](const auto &r) -> Instr { return Pop{r}; }));
return {pushr, popr};
}
std::pair<std::optional<Instr>, std::optional<Instr>>
align_stack(size_t saved_registers, size_t stack_arguments) {
const bool aligned = (saved_registers + stack_arguments) % 2 == 0;
if (aligned && stack_arguments == 0) {
return {{}, {}};
}
if (aligned) {
return {{},
{Binop{Opr::ADD, L{static_cast<int>(word_size * stack_arguments)},
rsp}}};
}
return {Push{filler},
{Binop{Opr::ADD,
L{static_cast<int>(word_size * (1 + stack_arguments))}, rsp}}};
}
Instr move_result(Env<Prg, mode_> &env) {
const auto y = env.allocate();
return Mov{rax, y};
}
} // namespace utils::call_compilation
std::vector<Instr> compile_common_call(Env<Prg, mode_> &env,
const std::optional<std::string> &fname,
size_t nargs) {
using namespace utils::call_compilation::common;
using namespace utils::call_compilation;
auto add_argc_counter_code = add_argc_counter(fname, nargs);
auto [stack_slots, setup_args_code] = setup_arguments(env, nargs);
auto [push_registers, pop_registers] = protect_registers(env);
auto [align_prologue, align_epilogue] =
align_stack(push_registers.size(), stack_slots);
auto setup_closure_code = setup_closure(env, fname);
auto call_code = call(fname);
auto move_result_code = move_result(env);
return utils::concat(
std::move(push_registers), std::move(align_prologue),
std::move(setup_args_code), std::move(setup_closure_code),
std::move(add_argc_counter_code), std::move(call_code),
std::move(align_epilogue), utils::reverse(std::move(pop_registers)),
std::move(move_result_code));
}
namespace utils::call_compilation::safepoint {
std::pair<size_t, std::vector<Instr>>
setup_arguments(Env<Prg, mode_> &env, const std::optional<std::string> &fname,
size_t nargs) {
auto args = pop_arguments(env, nargs);
auto [arg_locs, stack_slots] = env.arguments_locations(args.size());
auto setup_args_code =
utils::transform<Opnd, Instr>(utils::reverse(std::move(args)),
[](const auto &arg) { return Push{arg}; });
setup_args_code.push_back(Mov{rsp, rdi});
if (*fname == utils::labeled_builtin("closure")) {
setup_args_code.push_back(Mov{L{box(nargs - 1)}, rsi});
} else if (*fname == utils::labeled_builtin("sexp") ||
*fname == utils::labeled_builtin("array")) {
setup_args_code.push_back(Mov{L{box(nargs)}, rsi});
}
return {nargs, std::move(setup_args_code)};
}
Instr call(const std::optional<std::string> &fname) { return Call{*fname}; }
} // namespace utils::call_compilation::safepoint
std::vector<Instr>
compile_safepoint_call(Env<Prg, mode_> &env,
const std::optional<std::string> &fname, size_t nargs) {
using namespace utils::call_compilation::safepoint;
using namespace utils::call_compilation;
auto [stack_slots, setup_args_code] = setup_arguments(env, fname, nargs);
auto [push_registers, pop_registers] = protect_registers(env);
auto [align_prologue, align_epilogue] =
align_stack(push_registers.size(), stack_slots);
auto call_code = call(fname);
auto move_result_code = move_result(env);
return utils::concat(std::move(push_registers), std::move(align_prologue),
std::move(setup_args_code), std::move(call_code),
std::move(align_epilogue),
utils::reverse(std::move(pop_registers)),
std::move(move_result_code));
}
std::vector<Instr> compile_call(Env<Prg, mode_> &env, std::vector<Instr> compile_call(Env<Prg, mode_> &env,
std::optional<std::string_view> fname, std::optional<std::string_view> fname_in,
size_t nargs, bool tail) {} size_t nargs, bool tail) {
std::optional<std::string> fname;
if (fname_in) {
fname = (*fname_in)[0] == '.' ? utils::labeled_builtin(fname->substr(1))
: std::string{*fname_in};
}
bool safepoint_call = false;
bool allowed_function = true;
if (fname) {
safepoint_call = (safepoint_functions.count(*fname) != 0);
const bool is_vararg = (vararg_functions.count(*fname) != 0);
const bool is_internal = ((*fname)[0] == 'B');
allowed_function = not is_internal && not is_vararg;
}
const bool same_arguments_count = env.nargs == nargs;
const bool tail_call_optimization_applicable =
tail && allowed_function && same_arguments_count;
if (safepoint_call) {
return compile_safepoint_call(env, fname, nargs);
}
if (tail_call_optimization_applicable) {
return compile_tail_call(env, fname, nargs);
}
return compile_common_call(env, fname, nargs);
}
enum class Patt { enum class Patt {
BOXED, BOXED,
@ -1860,4 +2152,10 @@ std::vector<Instr> compile(cmd, Env<Prg, mode_> &env,
std::vector<Instr> compile(cmd, Env<Prg, mode_> &env, std::vector<Instr> compile(cmd, Env<Prg, mode_> &env,
const std::vector<std::string> &imports, const std::vector<std::string> &imports,
const std::vector<SMInstr> &code) {} const std::vector<SMInstr> &code) {
std::vector<Instr> result;
for (const auto &instr : code) {
result =
utils::concat(std::move(result), compile(cmd, env, imports, instr));
}
}