#include "Bytecode.hpp"
#include "ast/AST.hpp"
#include "executable/FunctionBlock.hpp"
#include "instructions/Instructions.hpp"
#include "interpreter/Interpreter.hpp"
#include "runtime/BaseException.hpp"
#include "runtime/PyFrame.hpp"
#include "runtime/PyModule.hpp"
#include "runtime/PyTraceback.hpp"
#include "serialization/deserialize.hpp"
#include "serialization/serialize.hpp"
#include
#include
using namespace py;
Bytecode::Bytecode(size_t register_count,
size_t locals_count,
size_t stack_size,
std::string function_name,
InstructionVector instructions,
std::vector instruction_locations,
std::shared_ptr program)
: Function(register_count,
locals_count,
stack_size,
function_name,
FunctionExecutionBackend::BYTECODE,
std::move(program)),
m_instructions(std::move(instructions)),
m_instruction_locations(std::move(instruction_locations))
{}
std::optional Bytecode::location_for(size_t instruction_index) const
{
if (m_instruction_locations.empty()) { return std::nullopt; }
// Find the last entry whose instruction_index is <= the query.
const auto it = std::upper_bound(m_instruction_locations.begin(),
m_instruction_locations.end(),
instruction_index,
[](size_t idx, const InstructionSourceLocation &entry) {
return idx < entry.instruction_index;
});
if (it == m_instruction_locations.begin()) { return std::nullopt; }
return *std::prev(it);
}
std::string Bytecode::to_string() const
{
std::ostringstream os;
for (const auto &ins : m_instructions) {
os << fmt::format(" {} {}", (void *)ins.get(), ins->to_string()) << '\n';
}
return os.str();
}
std::vector Bytecode::serialize() const
{
std::vector result;
py::serialize(m_register_count, result);
py::serialize(m_locals_count, result);
py::serialize(m_stack_size, result);
py::serialize(m_function_name, result);
py::serialize(static_cast(m_backend), result);
const size_t instruction_count = m_instructions.size();
py::serialize(instruction_count, result);
for (const auto &ins : m_instructions) {
std::cout << ins->to_string() << std::endl;
auto serialized_instruction = ins->serialize();
result.insert(result.end(), serialized_instruction.begin(), serialized_instruction.end());
}
return result;
}
std::unique_ptr Bytecode::deserialize(std::span &buffer,
std::shared_ptr program)
{
const auto register_count = py::deserialize(buffer);
const auto locals_count = py::deserialize(buffer);
const auto stack_size = py::deserialize(buffer);
const auto function_name = py::deserialize<:string>(buffer);
const auto backend = static_cast(py::deserialize(buffer));
(void)backend;
InstructionVector instructions;
const auto instruction_count = py::deserialize(buffer);
for (size_t i = 0; i < instruction_count; ++i) {
auto instruction = ::deserialize(buffer);
if (!instruction) {
for (const auto &ins : instructions) { std::cout << ins->to_string() << '\n'; }
std::abort();
}
instructions.push_back(std::move(instruction));
}
return std::make_unique(register_count,
locals_count,
stack_size,
function_name,
std::move(instructions),
std::vector{},
std::move(program));
}
PyResult Bytecode::call(VirtualMachine &vm, Interpreter &interpreter) const
{
// create main stack frame
[[maybe_unused]] auto main_frame = [&vm, this]() -> std::unique_ptr {
if (vm.stack().empty()) {
return vm.setup_call_stack(m_register_count, m_locals_count, m_stack_size);
}
return nullptr;
}();
vm.set_instruction_pointer(begin());
return eval_loop(vm, interpreter);
}
PyResult Bytecode::call_without_setup(VirtualMachine &vm, Interpreter &interpreter) const
{
// create main stack frame
ASSERT(!vm.stack().empty());
constexpr auto sentinel = decltype(vm.stack().back().get().last_instruction_pointer)();
if (vm.stack().back().get().last_instruction_pointer == sentinel) {
// first time calling with the stack frame, so we don't have a last instruction pointer yet
vm.set_instruction_pointer(begin());
} else {
// otherwise resume execution, by starting execution from the instruction after the last run
// instruction
vm.set_instruction_pointer(vm.stack().back().get().last_instruction_pointer + 1);
}
return eval_loop(vm, interpreter);
}
py::PyResult<:value> Bytecode::eval_loop(VirtualMachine &vm, Interpreter &interpreter) const
{
std::optional value;
const auto stack_depth = vm.stack().size();
const auto initial_ip = vm.instruction_pointer();
const auto end_instruction_it = end();
for (; vm.instruction_pointer() != end_instruction_it;
vm.set_instruction_pointer(std::next(vm.instruction_pointer()))) {
ASSERT((*vm.instruction_pointer()).get());
const auto ¤t_ip = vm.instruction_pointer();
const auto &instruction = *current_ip;
// spdlog::debug("{} {}", (void *)instruction.get(), instruction->to_string());
// std::cout << std::format("{} {}", (void *)instruction.get(), instruction->to_string())
// << std::endl;
auto result = instruction->execute(vm, vm.interpreter());
// we left the current stack frame in the previous instruction
if (vm.stack().size() != stack_depth) {
ASSERT(result.is_ok());
return result;
}
// vm.dump();
if (result.is_err()) {
auto *exception = result.unwrap_err();
const size_t tb_lasti = std::distance(initial_ip, current_ip);
const size_t tb_lineno =
location_for(tb_lasti).value_or(InstructionSourceLocation{ 0, 0, 0 }).line;
PyTraceback *tb_next = exception->traceback();
auto traceback =
PyTraceback::create(interpreter.execution_frame(), tb_lasti, tb_lineno, tb_next);
ASSERT(traceback.is_ok());
exception->set_traceback(traceback.unwrap());
interpreter.raise_exception(exception);
ASSERT(vm.state().cleanup.size() > 0);
if (!vm.state().cleanup.top()) {
ASSERT(vm.state().cleanup.size() == 1);
// No handler in this frame: the exception propagates to the caller,
// whose eval loop re-pushes it. Pop the entry we just pushed so it
// does not linger on the frame-shared exception stack. Otherwise
// internally-consumed exceptions (e.g. a generator's completion
// StopIteration, swallowed by the FOR_ITER that resumed it) accumulate,
// and a later bare `raise` or implicit __context__ lookup observes that
// stale exception instead of seeing an empty stack.
interpreter.execution_frame()->pop_exception();
// when a function returns without handling the exception do not copy the value
// to the callers the return register
vm.pop_frame(false);
return result;
} else {
auto [exit_cleanup_type, exit_ins] = *vm.state().cleanup.top();
vm.leave_cleanup_handling();
vm.set_instruction_pointer(exit_ins);
}
} else {
value = result.unwrap();
}
}
ASSERT(value.has_value());
return Ok(*value);
}