mirror of
https://github.com/jessicanataliagta/PSPRecomp
synced 2026-09-26 08:41:08 -04:00
fix build memory blow-up and boot crash by removing AOT register caches
This commit is contained in:
+41
-322
@@ -843,11 +843,11 @@ std::string direct_unit_chain_expression(
|
||||
if (found != direct_entry_ids->end() && found->second != 0u) {
|
||||
return "rt.invoke_chained_direct<&" + generated_unit_cpp_entry_name(unit) + ", " +
|
||||
std::to_string(unit) + "u, " + std::to_string(found->second) + "u, " +
|
||||
psprecomp::hex32(target) + "u>(ctx, &aot_mem, &hot_regs)";
|
||||
psprecomp::hex32(target) + "u>(ctx, &aot_mem)";
|
||||
}
|
||||
}
|
||||
return "rt.invoke_chained_direct<&" + generated_unit_cpp_name(unit) + ", " +
|
||||
std::to_string(unit) + "u>(ctx, &aot_mem, &hot_regs)";
|
||||
std::to_string(unit) + "u>(ctx, &aot_mem)";
|
||||
}
|
||||
|
||||
void emit_target(std::ostringstream &body, std::uint32_t target,
|
||||
@@ -882,7 +882,7 @@ void emit_target(std::ostringstream &body, std::uint32_t target,
|
||||
<< "; return;\n";
|
||||
} else {
|
||||
body << indent << "ctx.pc = " << psprecomp::hex32(target)
|
||||
<< "u; (void)rt.invoke_chained_call(ctx, &aot_mem, &hot_regs); return;\n";
|
||||
<< "u; (void)rt.invoke_chained_call(ctx, &aot_mem); return;\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -933,7 +933,7 @@ std::string emit_function_source(const GeneratedFunctionInput &function,
|
||||
}
|
||||
body << "\n};\n";
|
||||
|
||||
body << "void " << cpp_name << "_entry(Runtime &rt, AllegrexContext &ctx, std::uint16_t direct_entry_id, GuestMemory::AotFastView &aot_mem, AotHotRegisterCache & PSPRECOMP_RESTRICT hot_regs) {\n"
|
||||
body << "void " << cpp_name << "_entry(Runtime &rt, AllegrexContext &ctx, std::uint16_t direct_entry_id, GuestMemory::AotFastView &aot_mem) {\n"
|
||||
<< " std::uint32_t jump_target = 0u;\n"
|
||||
<< " std::uint32_t local_transfers = 0u;\n"
|
||||
<< " std::uint32_t local_pc = ctx.pc;\n"
|
||||
@@ -952,11 +952,19 @@ std::string emit_function_source(const GeneratedFunctionInput &function,
|
||||
}
|
||||
body << " default:\n"
|
||||
<< " if (local_transfers == 0u) rt.unsupported(ctx.pc, 0u, \"invalid internal function entry\");\n"
|
||||
// Local transfers advance local_pc without touching AllegrexContext.
|
||||
// Leaving the unit through this default arm hands control back to the
|
||||
// outer dispatcher, for which ctx.pc is the only statement of where to
|
||||
// resume -- so it must be materialized here. Without this store the
|
||||
// dispatcher re-enters at the unit's original entry PC and executes
|
||||
// the wrong code. local_transfers == 0u needs no store because no
|
||||
// local jump has happened yet and ctx.pc is still current.
|
||||
<< " else ctx.pc = local_pc;\n"
|
||||
<< " return;\n"
|
||||
<< " }\n"
|
||||
<< " }\n";
|
||||
} else {
|
||||
body << "void " << cpp_name << "_entry(Runtime &rt, AllegrexContext &ctx, std::uint16_t direct_entry_id, GuestMemory::AotFastView &aot_mem, AotHotRegisterCache & PSPRECOMP_RESTRICT hot_regs) {\n"
|
||||
body << "void " << cpp_name << "_entry(Runtime &rt, AllegrexContext &ctx, std::uint16_t direct_entry_id, GuestMemory::AotFastView &aot_mem) {\n"
|
||||
<< " (void)direct_entry_id;\n"
|
||||
<< " std::uint32_t jump_target = 0u;\n"
|
||||
<< " std::uint32_t local_transfers = 0u;\n"
|
||||
@@ -968,6 +976,14 @@ std::string emit_function_source(const GeneratedFunctionInput &function,
|
||||
}
|
||||
body << " default:\n"
|
||||
<< " if (local_transfers == 0u) rt.unsupported(ctx.pc, 0u, \"invalid internal function entry\");\n"
|
||||
// Local transfers advance local_pc without touching AllegrexContext.
|
||||
// Leaving the unit through this default arm hands control back to the
|
||||
// outer dispatcher, for which ctx.pc is the only statement of where to
|
||||
// resume -- so it must be materialized here. Without this store the
|
||||
// dispatcher re-enters at the unit's original entry PC and executes
|
||||
// the wrong code. local_transfers == 0u needs no store because no
|
||||
// local jump has happened yet and ctx.pc is still current.
|
||||
<< " else ctx.pc = local_pc;\n"
|
||||
<< " return;\n"
|
||||
<< " }\n";
|
||||
}
|
||||
@@ -1055,14 +1071,14 @@ std::string emit_function_source(const GeneratedFunctionInput &function,
|
||||
if (direct_unit)
|
||||
body << direct_unit_chain_expression(target_unit, target, function.direct_entry_ids);
|
||||
else
|
||||
body << "rt.invoke_chained_call(ctx, &aot_mem, &hot_regs)";
|
||||
body << "rt.invoke_chained_call(ctx, &aot_mem)";
|
||||
body << " && ctx.pc == " << psprecomp::hex32(return_pc)
|
||||
<< "u) goto L_" << psprecomp::hex32(return_pc).substr(2) << ";\n";
|
||||
} else {
|
||||
if (direct_unit)
|
||||
body << " (void)" << direct_unit_chain_expression(target_unit, target, function.direct_entry_ids) << ";\n";
|
||||
else
|
||||
body << " (void)rt.invoke_chained_call(ctx, &aot_mem, &hot_regs);\n";
|
||||
body << " (void)rt.invoke_chained_call(ctx, &aot_mem);\n";
|
||||
}
|
||||
body << " return;\n";
|
||||
}
|
||||
@@ -1080,11 +1096,11 @@ std::string emit_function_source(const GeneratedFunctionInput &function,
|
||||
const std::uint32_t return_pc = pc + 8u;
|
||||
body << " ctx.pc = jump_target;\n";
|
||||
if (function.entry_labels.contains(return_pc)) {
|
||||
body << " if (rt.invoke_chained_call(ctx, &aot_mem, &hot_regs) && ctx.pc == "
|
||||
body << " if (rt.invoke_chained_call(ctx, &aot_mem) && ctx.pc == "
|
||||
<< psprecomp::hex32(return_pc) << "u) goto L_"
|
||||
<< psprecomp::hex32(return_pc).substr(2) << ";\n";
|
||||
} else {
|
||||
body << " (void)rt.invoke_chained_call(ctx, &aot_mem, &hot_regs);\n";
|
||||
body << " (void)rt.invoke_chained_call(ctx, &aot_mem);\n";
|
||||
}
|
||||
body << " return;\n";
|
||||
} else {
|
||||
@@ -1125,9 +1141,7 @@ std::string emit_function_source(const GeneratedFunctionInput &function,
|
||||
body << "}\n\n";
|
||||
body << "void " << cpp_name << "(Runtime &rt, AllegrexContext &ctx) {\n"
|
||||
<< " auto aot_mem = rt.memory().aot_fast_view();\n"
|
||||
<< " AotHotRegisterCache hot_regs(ctx);\n"
|
||||
<< " " << cpp_name << "_entry(rt, ctx, 0u, aot_mem, hot_regs);\n"
|
||||
<< " hot_regs.flush_to(ctx);\n}\n\n";
|
||||
<< " " << cpp_name << "_entry(rt, ctx, 0u, aot_mem);\n}\n\n";
|
||||
return body.str();
|
||||
}
|
||||
|
||||
@@ -1265,307 +1279,6 @@ std::string lower_constant_gpr_writes(std::string text) {
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// cross-unit hot GPR cache. reduced register traffic
|
||||
// inside individual guest labels, but every label boundary and every native
|
||||
// unit boundary still materialized the hottest architectural registers through
|
||||
// AllegrexContext. The generated entry ABI now carries AotHotRegisterCache by
|
||||
// reference across direct/dynamic generated-unit chains. Replace the seven
|
||||
// dominant GPR/FPR array references with that shared native cache. They represent
|
||||
// a large share of remaining architectural register traffic in common AOT workloads.
|
||||
//
|
||||
// Runtime/HLE visibility is preserved by Runtime::invoke_chained_* and the
|
||||
// generated outer wrapper, which materialize/reload the cache at scheduler,
|
||||
// host/HLE and outer-dispatch boundaries. Signed ADD/SUB are the only generated
|
||||
// AllegrexContext helpers that read/write GPRs internally, so synchronize around
|
||||
// those rare helpers explicitly.
|
||||
std::string lower_cross_unit_hot_regs_cache(std::string text) {
|
||||
static constexpr std::array<std::uint32_t, 7> kHotRegs{2u, 4u, 5u, 6u, 7u, 29u, 31u};
|
||||
for (const auto reg_index : kHotRegs) {
|
||||
const std::string from = "ctx.gpr[" + std::to_string(reg_index) + "]";
|
||||
const std::string to = "hot_regs.g" + std::to_string(reg_index);
|
||||
std::size_t pos = 0u;
|
||||
while ((pos = text.find(from, pos)) != std::string::npos) {
|
||||
text.replace(pos, from.size(), to);
|
||||
pos += to.size();
|
||||
}
|
||||
}
|
||||
static constexpr std::array<std::uint32_t, 6> kHotFprs{12u, 13u, 14u, 15u, 20u, 22u};
|
||||
for (const auto reg_index : kHotFprs) {
|
||||
const std::string from = "ctx.fpr[" + std::to_string(reg_index) + "]";
|
||||
const std::string to = "hot_regs.f" + std::to_string(reg_index);
|
||||
std::size_t pos = 0u;
|
||||
while ((pos = text.find(from, pos)) != std::string::npos) {
|
||||
text.replace(pos, from.size(), to);
|
||||
pos += to.size();
|
||||
}
|
||||
}
|
||||
|
||||
// These helpers directly access AllegrexContext::gpr. The hot cache must be
|
||||
// visible to them and must observe their destination write before execution
|
||||
// continues. emit_regular() intentionally places execute_signed_add/sub in
|
||||
// a standalone `signed_ok` statement so the cache can be reloaded BEFORE
|
||||
// the following overflow-return branch. This also makes the lowering pass
|
||||
// idempotent when generated source is transformed more than once.
|
||||
std::istringstream input(text);
|
||||
std::ostringstream output;
|
||||
std::string line;
|
||||
bool previous_was_flush = false;
|
||||
bool previous_was_reload = false;
|
||||
while (std::getline(input, line)) {
|
||||
const bool is_flush = line.find("hot_regs.flush_to(ctx);") != std::string::npos;
|
||||
const bool is_reload = line.find("hot_regs.reload_from(ctx);") != std::string::npos;
|
||||
const bool signed_gpr_helper = line.find("const bool signed_ok = ctx.execute_signed_add(") != std::string::npos ||
|
||||
line.find("const bool signed_ok = ctx.execute_signed_sub(") != std::string::npos;
|
||||
if (is_flush && previous_was_flush) continue;
|
||||
// Collapse accidental duplicate syncs left by older transformer revisions
|
||||
// transformer runs.
|
||||
if (is_reload && previous_was_reload) continue;
|
||||
if (signed_gpr_helper && !previous_was_flush) {
|
||||
const std::size_t indent_len = line.find_first_not_of(" \t");
|
||||
const std::string indent = indent_len == std::string::npos ? std::string{} : line.substr(0u, indent_len);
|
||||
output << indent << "hot_regs.flush_to(ctx);\n";
|
||||
output << line << '\n';
|
||||
output << indent << "hot_regs.reload_from(ctx);\n";
|
||||
previous_was_flush = false;
|
||||
previous_was_reload = true;
|
||||
continue;
|
||||
}
|
||||
output << line << '\n';
|
||||
previous_was_flush = is_flush;
|
||||
previous_was_reload = is_reload;
|
||||
}
|
||||
return output.str();
|
||||
}
|
||||
|
||||
// conservative host-register cache for hot automatic AOT basic
|
||||
// blocks. Generated C++ previously addressed ctx.gpr[] more than 1.2 million
|
||||
// times statically. Even with /GL, calls and the enormous control-flow graph
|
||||
// make MSVC spill/reload those array elements aggressively. For guest labels
|
||||
// that contain no Runtime call and no execute_* helper, cache any architectural
|
||||
// GPR referenced at least three times in a C++ local and commit dirty values
|
||||
// before control leaves the label. Each label gets its own scope, so arbitrary
|
||||
// generated gotos never jump across a local initialization.
|
||||
//
|
||||
// This intentionally stops at Runtime/execute_* boundaries: those paths can
|
||||
// inspect or mutate the complete Allegrex context and are synchronization
|
||||
// points for the register cache. AotFastView/FPR/VFPU helpers do not mutate GPRs
|
||||
// behind the emitted code and remain eligible.
|
||||
std::string lower_basic_block_gpr_cache(std::string text) {
|
||||
static const std::regex label_pattern(R"((^|\n)(L_[0-9A-F]+:\n))");
|
||||
static const std::regex gpr_pattern(R"(ctx\.gpr\[([1-9]|[12][0-9]|3[01])\])");
|
||||
std::vector<std::pair<std::size_t, std::size_t>> labels;
|
||||
for (std::sregex_iterator it(text.begin(), text.end(), label_pattern), end; it != end; ++it) {
|
||||
const std::size_t prefix = (*it)[1].length();
|
||||
const std::size_t begin = static_cast<std::size_t>((*it).position()) + prefix;
|
||||
labels.emplace_back(begin, begin + static_cast<std::size_t>((*it)[2].length()));
|
||||
}
|
||||
if (labels.empty()) return text;
|
||||
|
||||
std::string output;
|
||||
output.reserve(text.size() + text.size() / 20u);
|
||||
std::size_t last = 0u;
|
||||
for (std::size_t li = 0; li < labels.size(); ++li) {
|
||||
const std::size_t label_begin = labels[li].first;
|
||||
const std::size_t body_begin = labels[li].second;
|
||||
std::size_t body_end = li + 1u < labels.size() ? labels[li + 1u].first : text.find("\n}\n\nvoid ", body_begin);
|
||||
if (body_end == std::string::npos) body_end = text.size();
|
||||
output.append(text, last, label_begin - last);
|
||||
const std::string label = text.substr(label_begin, body_begin - label_begin);
|
||||
std::string block = text.substr(body_begin, body_end - body_begin);
|
||||
|
||||
if (block.find("rt.") != std::string::npos || block.find("ctx.execute_") != std::string::npos) {
|
||||
output += label;
|
||||
output += block;
|
||||
last = body_end;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 32> counts{};
|
||||
for (std::sregex_iterator it(block.begin(), block.end(), gpr_pattern), end; it != end; ++it) {
|
||||
const auto reg_index = static_cast<std::uint32_t>(std::strtoul((*it)[1].str().c_str(), nullptr, 10));
|
||||
if (reg_index < counts.size()) ++counts[reg_index];
|
||||
}
|
||||
std::vector<std::uint32_t> selected;
|
||||
std::array<bool, 32> dirty{};
|
||||
for (std::uint32_t reg_index = 1u; reg_index < 32u; ++reg_index) {
|
||||
if (counts[reg_index] < 3u) continue;
|
||||
selected.push_back(reg_index);
|
||||
const std::string lhs = "ctx.gpr[" + std::to_string(reg_index) + "]";
|
||||
std::size_t pos = 0u;
|
||||
while ((pos = block.find(lhs, pos)) != std::string::npos) {
|
||||
std::size_t cursor = pos + lhs.size();
|
||||
while (cursor < block.size() && std::isspace(static_cast<unsigned char>(block[cursor]))) ++cursor;
|
||||
if (cursor < block.size() && block[cursor] == '=' &&
|
||||
(cursor + 1u >= block.size() || block[cursor + 1u] != '=')) {
|
||||
dirty[reg_index] = true;
|
||||
break;
|
||||
}
|
||||
pos = cursor;
|
||||
}
|
||||
}
|
||||
if (selected.empty()) {
|
||||
output += label;
|
||||
output += block;
|
||||
last = body_end;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto reg_index : selected) {
|
||||
const std::string from = "ctx.gpr[" + std::to_string(reg_index) + "]";
|
||||
const std::string to = "g" + std::to_string(reg_index);
|
||||
std::size_t pos = 0u;
|
||||
while ((pos = block.find(from, pos)) != std::string::npos) {
|
||||
block.replace(pos, from.size(), to);
|
||||
pos += to.size();
|
||||
}
|
||||
}
|
||||
|
||||
output += label;
|
||||
output += "{\n";
|
||||
for (const auto reg_index : selected)
|
||||
output += " std::uint32_t g" + std::to_string(reg_index) + " = ctx.gpr[" + std::to_string(reg_index) + "];\n";
|
||||
|
||||
std::array<bool, 32> active_dirty{};
|
||||
auto flush = [&]() {
|
||||
for (const auto reg_index : selected) {
|
||||
if (!active_dirty[reg_index]) continue;
|
||||
output += " ctx.gpr[" + std::to_string(reg_index) + "] = g" + std::to_string(reg_index) + ";\n";
|
||||
active_dirty[reg_index] = false;
|
||||
}
|
||||
};
|
||||
|
||||
std::istringstream lines(block);
|
||||
std::string line;
|
||||
while (std::getline(lines, line)) {
|
||||
line += '\n';
|
||||
if (line.find("if (branch_taken)") != std::string::npos) flush();
|
||||
for (const auto reg_index : selected) {
|
||||
if (!dirty[reg_index]) continue;
|
||||
const std::string token = "g" + std::to_string(reg_index);
|
||||
std::size_t pos = line.find(token);
|
||||
while (pos != std::string::npos) {
|
||||
const bool left_ok = pos == 0u || !(std::isalnum(static_cast<unsigned char>(line[pos - 1u])) || line[pos - 1u] == '_');
|
||||
std::size_t cursor = pos + token.size();
|
||||
const bool right_ok = cursor >= line.size() || !(std::isalnum(static_cast<unsigned char>(line[cursor])) || line[cursor] == '_');
|
||||
if (left_ok && right_ok) {
|
||||
while (cursor < line.size() && std::isspace(static_cast<unsigned char>(line[cursor]))) ++cursor;
|
||||
if (cursor < line.size() && line[cursor] == '=' &&
|
||||
(cursor + 1u >= line.size() || line[cursor + 1u] != '=')) {
|
||||
active_dirty[reg_index] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pos = line.find(token, pos + token.size());
|
||||
}
|
||||
}
|
||||
if ((line.find("goto ") != std::string::npos || line.find("return;") != std::string::npos)) flush();
|
||||
output += line;
|
||||
}
|
||||
flush();
|
||||
output += "}\n";
|
||||
last = body_end;
|
||||
}
|
||||
output.append(text, last, std::string::npos);
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
// companion FPR cache. Constant FPR helper accesses have already
|
||||
// been lowered to ctx.fpr[N], so blocks without Runtime/context-execute calls
|
||||
// can keep frequently reused scalar floats in host locals and commit dirty
|
||||
// values on control-flow exits just like the GPR cache above.
|
||||
std::string lower_basic_block_fpr_cache(std::string text) {
|
||||
static const std::regex label_pattern(R"((^|\n)(L_[0-9A-F]+:\n))");
|
||||
static const std::regex fpr_pattern(R"(ctx\.fpr\[([0-9]|[12][0-9]|3[01])\])");
|
||||
std::vector<std::pair<std::size_t, std::size_t>> labels;
|
||||
for (std::sregex_iterator it(text.begin(), text.end(), label_pattern), end; it != end; ++it) {
|
||||
const std::size_t prefix = (*it)[1].length();
|
||||
const std::size_t begin = static_cast<std::size_t>((*it).position()) + prefix;
|
||||
labels.emplace_back(begin, begin + static_cast<std::size_t>((*it)[2].length()));
|
||||
}
|
||||
if (labels.empty()) return text;
|
||||
std::string output;
|
||||
output.reserve(text.size() + text.size() / 30u);
|
||||
std::size_t last = 0u;
|
||||
for (std::size_t li = 0; li < labels.size(); ++li) {
|
||||
const std::size_t label_begin = labels[li].first;
|
||||
const std::size_t body_begin = labels[li].second;
|
||||
std::size_t body_end = li + 1u < labels.size() ? labels[li + 1u].first : text.find("\n}\n\nvoid ", body_begin);
|
||||
if (body_end == std::string::npos) body_end = text.size();
|
||||
output.append(text, last, label_begin - last);
|
||||
const std::string label = text.substr(label_begin, body_begin - label_begin);
|
||||
std::string block = text.substr(body_begin, body_end - body_begin);
|
||||
if (block.find("rt.") != std::string::npos || block.find("ctx.execute_") != std::string::npos ||
|
||||
block.find("ctx.set_fpr_bits") != std::string::npos || block.find("ctx.fpr_bits(") != std::string::npos) {
|
||||
output += label; output += block; last = body_end; continue;
|
||||
}
|
||||
std::array<std::uint32_t, 32> counts{};
|
||||
for (std::sregex_iterator it(block.begin(), block.end(), fpr_pattern), end; it != end; ++it) {
|
||||
const auto reg_index = static_cast<std::uint32_t>(std::strtoul((*it)[1].str().c_str(), nullptr, 10));
|
||||
if (reg_index < counts.size()) ++counts[reg_index];
|
||||
}
|
||||
std::vector<std::uint32_t> selected;
|
||||
std::array<bool, 32> dirty{};
|
||||
for (std::uint32_t reg_index = 0u; reg_index < 32u; ++reg_index) {
|
||||
if (counts[reg_index] < 3u) continue;
|
||||
selected.push_back(reg_index);
|
||||
const std::string lhs = "ctx.fpr[" + std::to_string(reg_index) + "]";
|
||||
std::size_t pos = 0u;
|
||||
while ((pos = block.find(lhs, pos)) != std::string::npos) {
|
||||
std::size_t cursor = pos + lhs.size();
|
||||
while (cursor < block.size() && std::isspace(static_cast<unsigned char>(block[cursor]))) ++cursor;
|
||||
if (cursor < block.size() && block[cursor] == '=' &&
|
||||
(cursor + 1u >= block.size() || block[cursor + 1u] != '=')) { dirty[reg_index] = true; break; }
|
||||
pos = cursor;
|
||||
}
|
||||
}
|
||||
if (selected.empty()) { output += label; output += block; last = body_end; continue; }
|
||||
for (const auto reg_index : selected) {
|
||||
const std::string from = "ctx.fpr[" + std::to_string(reg_index) + "]";
|
||||
const std::string to = "f" + std::to_string(reg_index);
|
||||
std::size_t pos = 0u;
|
||||
while ((pos = block.find(from, pos)) != std::string::npos) { block.replace(pos, from.size(), to); pos += to.size(); }
|
||||
}
|
||||
output += label; output += "{\n";
|
||||
for (const auto reg_index : selected)
|
||||
output += " float f" + std::to_string(reg_index) + " = ctx.fpr[" + std::to_string(reg_index) + "];\n";
|
||||
std::array<bool, 32> active_dirty{};
|
||||
auto flush = [&]() {
|
||||
for (const auto reg_index : selected) if (active_dirty[reg_index]) {
|
||||
output += " ctx.fpr[" + std::to_string(reg_index) + "] = f" + std::to_string(reg_index) + ";\n";
|
||||
active_dirty[reg_index] = false;
|
||||
}
|
||||
};
|
||||
std::istringstream lines(block); std::string line;
|
||||
while (std::getline(lines, line)) {
|
||||
line += '\n';
|
||||
if (line.find("if (branch_taken)") != std::string::npos) flush();
|
||||
for (const auto reg_index : selected) if (dirty[reg_index]) {
|
||||
const std::string token = "f" + std::to_string(reg_index);
|
||||
std::size_t pos = line.find(token);
|
||||
while (pos != std::string::npos) {
|
||||
const bool left_ok = pos == 0u || !(std::isalnum(static_cast<unsigned char>(line[pos - 1u])) || line[pos - 1u] == '_');
|
||||
std::size_t cursor = pos + token.size();
|
||||
const bool right_ok = cursor >= line.size() || !(std::isalnum(static_cast<unsigned char>(line[cursor])) || line[cursor] == '_');
|
||||
if (left_ok && right_ok) {
|
||||
while (cursor < line.size() && std::isspace(static_cast<unsigned char>(line[cursor]))) ++cursor;
|
||||
if (cursor < line.size() && line[cursor] == '=' &&
|
||||
(cursor + 1u >= line.size() || line[cursor + 1u] != '=')) { active_dirty[reg_index] = true; break; }
|
||||
}
|
||||
pos = line.find(token, pos + token.size());
|
||||
}
|
||||
}
|
||||
if (line.find("goto ") != std::string::npos || line.find("return;") != std::string::npos) flush();
|
||||
output += line;
|
||||
}
|
||||
flush(); output += "}\n"; last = body_end;
|
||||
}
|
||||
output.append(text, last, std::string::npos);
|
||||
return output;
|
||||
}
|
||||
|
||||
std::string lower_aot_memory_accesses(std::string text) {
|
||||
// the outer generated-unit wrapper creates one AotFastView and
|
||||
// compile-time direct chains pass it by reference across unit boundaries.
|
||||
@@ -1787,12 +1500,12 @@ int generate_auto(const std::filesystem::path &elf_path,
|
||||
// instead of forcing every known edge through a function-pointer branch.
|
||||
const auto units_header_path = output_dir / "generated_units.hpp";
|
||||
std::ostringstream units_header;
|
||||
units_header << "#pragma once\n\n#include <cstdint>\n#include \"psprecomp/guest_memory.hpp\"\n\nnamespace psprecomp {\nclass Runtime;\nstruct AllegrexContext;\nstruct AotHotRegisterCache;\n";
|
||||
units_header << "#pragma once\n\n#include <cstdint>\n#include \"psprecomp/guest_memory.hpp\"\n\nnamespace psprecomp {\nclass Runtime;\nstruct AllegrexContext;\n";
|
||||
for (const auto &unit : units) {
|
||||
units_header << "void " << generated_unit_cpp_name(unit.bucket)
|
||||
<< "(Runtime &, AllegrexContext &);\n";
|
||||
units_header << "void " << generated_unit_cpp_entry_name(unit.bucket)
|
||||
<< "(Runtime &, AllegrexContext &, std::uint16_t, GuestMemory::AotFastView &, AotHotRegisterCache &);\n";
|
||||
<< "(Runtime &, AllegrexContext &, std::uint16_t, GuestMemory::AotFastView &);\n";
|
||||
}
|
||||
units_header << "} // namespace psprecomp\n";
|
||||
(void)write_text_if_changed(units_header_path, units_header.str());
|
||||
@@ -1820,13 +1533,19 @@ int generate_auto(const std::filesystem::path &elf_path,
|
||||
|
||||
std::ostringstream out;
|
||||
out << "#include \"psprecomp/runtime.hpp\"\n#include \"generated_units.hpp\"\n#include <bit>\n#include <cmath>\n#include <cstdint>\n#include <limits>\n\nnamespace psprecomp {\n";
|
||||
out << lower_cross_unit_hot_regs_cache(
|
||||
lower_basic_block_fpr_cache(
|
||||
lower_basic_block_gpr_cache(
|
||||
lower_constant_vfpu_accesses(
|
||||
lower_aot_memory_accesses(
|
||||
lower_constant_fpr_accesses(
|
||||
lower_constant_gpr_writes(emit_function_source(generated_unit, memory, generated_unit.name))))))));
|
||||
// The register-cache lowering passes (per-basic-block GPR/FPR caches and
|
||||
// the cross-unit hot-register cache) are deliberately absent. They kept
|
||||
// large numbers of guest registers live in C++ locals and in a second
|
||||
// long-lived object alongside AllegrexContext; inside the ~10,000-line
|
||||
// single functions emitted here, that drove MSVC's optimizer past the
|
||||
// point where it converges. Affected units never finished compiling and
|
||||
// each grew past 2 GB, exhausting system memory during a normal build.
|
||||
// This pipeline matches the last configuration observed booting on
|
||||
// hardware.
|
||||
out << lower_constant_vfpu_accesses(
|
||||
lower_aot_memory_accesses(
|
||||
lower_constant_fpr_accesses(
|
||||
lower_constant_gpr_writes(emit_function_source(generated_unit, memory, generated_unit.name)))));
|
||||
out << "void register_generated_unit_" << unit.bucket << "(Runtime &runtime) {\n";
|
||||
out << " runtime.register_generated_unit(" << unit.bucket << "u, "
|
||||
<< psprecomp::hex32(generated_unit.address) << "u, " << unit_span_bytes
|
||||
|
||||
Reference in New Issue
Block a user