rexglue-skate3/src/codegen/phase_gapfill.cpp
2026-05-18 22:41:13 +01:00

235 lines
7.1 KiB
C++

/**
* @file codegen/phase_gapfill.cpp
* @brief GapFill phase: find uncovered code regions and register them as functions
*
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
* All rights reserved.
*
* @license BSD 3-Clause License
* See LICENSE file in the project root for full license text.
*/
#include "ppc/instruction.h"
#include <unordered_set>
#include <rex/codegen/phases.h>
#include "phase_helpers.h"
#include <rex/logging.h>
#include "codegen_logging.h"
#include <rex/memory/utils.h>
#include <ppc.h>
using rex::codegen::ppc::decode_instruction;
using rex::codegen::ppc::Opcode;
using rex::memory::load_and_swap;
namespace rex::codegen {
namespace {
//=============================================================================
// GapFill to register uncovered code regions
//=============================================================================
// Split a code region into function segments based on terminators (blr, tail calls).
std::vector<CodeRegion> splitRegionOnTerminators(
const CodeRegion& region, const BinaryView& binary,
const std::unordered_set<uint32_t>& knownCallables) {
std::vector<CodeRegion> segments;
uint32_t segmentStart = region.start;
for (uint32_t addr = region.start; addr < region.end; addr += 4) {
const uint8_t* data = binary.translate(addr);
if (!data)
break;
uint32_t raw = load_and_swap<uint32_t>(data);
auto decoded = decode_instruction(addr, raw);
bool shouldSplit = false;
const char* reason = nullptr;
// Check for terminators
if (decoded.is_return()) {
shouldSplit = true;
reason = "blr";
} else if (decoded.opcode == Opcode::b && decoded.branch_target.has_value()) {
uint32_t target = decoded.branch_target.value();
// Don't split on tail recursion (branch to own segment start)
if (target != segmentStart && knownCallables.contains(target)) {
shouldSplit = true;
reason = "tail call";
}
}
if (shouldSplit) {
uint32_t segmentEnd = addr + 4;
if (segmentEnd > segmentStart) {
segments.push_back({segmentStart, segmentEnd});
REXCODEGEN_TRACE("GapFill: split segment 0x{:08X}-0x{:08X} ({} at 0x{:08X})", segmentStart,
segmentEnd, reason, addr);
}
segmentStart = segmentEnd;
}
}
// Handle remaining code after last terminator
if (segmentStart < region.end) {
segments.push_back({segmentStart, region.end});
}
return segments;
}
// Check if address looks like exception handler data (handler ptr + rdata ptr)
bool looksLikeExceptionData(const BinaryView& binary, const FunctionGraph& graph, uint32_t addr) {
const uint8_t* data = binary.translate(addr);
if (!data)
return false;
// Exception handler data pattern:
// [addr+0]: pointer to __C_specific_handler (entry point)
// [addr+4]: pointer to scope table in .rdata
uint32_t firstDword = load_and_swap<uint32_t>(data);
uint32_t secondDword = load_and_swap<uint32_t>(data + 4);
// Check if first dword is a known entry point (like __C_specific_handler)
if (!graph.isEntryPoint(firstDword)) {
return false;
}
// Check if second dword points to .rdata section
auto* rdataSection = binary.findSectionByName(".rdata");
if (!rdataSection)
return false;
uint32_t rdataStart = rdataSection->baseAddress;
uint32_t rdataEnd = rdataStart + rdataSection->size;
if (secondDword >= rdataStart && secondDword < rdataEnd) {
REXCODEGEN_TRACE(
"GapFill: 0x{:08X} looks like exception data (handler=0x{:08X}, scope=0x{:08X}), skipping",
addr, firstDword, secondDword);
return true;
}
return false;
}
void gapFillCodeRegions(CodegenContext& ctx) {
REXCODEGEN_TRACE("Analyze: checking for uncovered code regions...");
auto& graph = ctx.graph;
auto& binary = ctx.binary();
auto& scan = ctx.scan;
// Build set of known callables for tail call detection
std::unordered_set<uint32_t> knownCallables;
for (const auto& [addr, node] : graph.functions()) {
knownCallables.insert(addr);
}
size_t gapsFound = 0;
size_t segmentsCreated = 0;
for (const auto& region : scan.codeRegions) {
// Split region on terminators (blr, tail calls), then check each segment
auto segments = splitRegionOnTerminators(region, binary, knownCallables);
for (const auto& segment : segments) {
// Skip if this segment's start is already a registered function entry
if (graph.isEntryPoint(segment.start))
continue;
// Skip if this segment's start is inside another function
if (auto* containingFunc = graph.getFunctionContaining(segment.start)) {
continue;
}
// Skip if this looks like exception handler data (handler ptr + rdata ptr)
if (looksLikeExceptionData(binary, graph, segment.start))
continue;
uint32_t segmentSize = segment.size();
graph.addFunction(segment.start, segmentSize, FunctionAuthority::GAP_FILL, false);
REXCODEGEN_TRACE("GapFill: registered sub_{:08X} (0x{:08X}-0x{:08X}, {} bytes)",
segment.start, segment.start, segment.end, segmentSize);
segmentsCreated++;
}
gapsFound++;
}
if (segmentsCreated > 0) {
REXCODEGEN_TRACE("Analyze: registered {} gap functions from {} regions", segmentsCreated,
gapsFound);
} else {
REXCODEGEN_TRACE("Analyze: no uncovered regions found");
}
}
//=============================================================================
// Cleanup absorbed GAP_FILL functions
//=============================================================================
void cleanupAbsorbedGapFills(CodegenContext& ctx) {
auto& graph = ctx.graph;
std::vector<uint32_t> toRemove;
for (const auto& [addr, node] : graph.functions()) {
if (node->authority() != FunctionAuthority::GAP_FILL)
continue;
for (const auto& [otherAddr, otherNode] : graph.functions()) {
if (otherAddr == addr)
continue;
if (!otherNode->containsAddress(addr))
continue;
// This GAP_FILL is inside another function's blocks
if (otherNode->authority() != FunctionAuthority::GAP_FILL) {
// Absorbed by higher authority - remove
toRemove.push_back(addr);
break;
} else if (otherAddr < addr) {
// Both GAP_FILL, other has lower address - it survives
toRemove.push_back(addr);
break;
}
}
}
for (uint32_t addr : toRemove) {
graph.removeFunction(addr);
}
if (!toRemove.empty()) {
REXCODEGEN_TRACE("Analyze: removed {} absorbed GAP_FILL functions", toRemove.size());
}
}
} // anonymous namespace
namespace phases {
VoidResult GapFill(CodegenContext& ctx, ProgressReporter* reporter) {
(void)reporter;
gapFillCodeRegions(ctx);
// Discover blocks for gap-filled functions
auto known = buildKnownFunctions(ctx.graph, /*excludeGapFill=*/true);
size_t discovered = discoverPendingFunctions(ctx, known);
REXCODEGEN_TRACE("Analyze: discovered blocks for {} gap-filled functions", discovered);
cleanupAbsorbedGapFills(ctx);
return Ok();
}
} // namespace phases
} // namespace rex::codegen