See More

#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 &current_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); }