Skip to content

Commit 6e089a3

Browse files
authored
Merge branch 'master' into twistedmetal
2 parents 26af116 + 03d03b2 commit 6e089a3

1 file changed

Lines changed: 362 additions & 2 deletions

File tree

rpcs3/Emu/Cell/PPUThread.cpp

Lines changed: 362 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1980,7 +1980,7 @@ std::vector<std::pair<u32, u32>> ppu_thread::dump_callstack_list() const
19801980

19811981
if (res.maybe_leaf && !res.non_leaf)
19821982
{
1983-
const u32 result = res.maybe_use_reg0_instead_of_lr ? static_cast<u32>(gpr0) : static_cast<u32>(_lr);
1983+
const u32 result = res.maybe_use_reg0_instead_of_lr && !is_invalid(static_cast<u32>(gpr0)) ? static_cast<u32>(gpr0) : static_cast<u32>(_lr);
19841984

19851985
// Same stack as far as we know
19861986
call_stack_list.emplace_back(result, static_cast<u32>(sp));
@@ -2037,7 +2037,367 @@ std::vector<std::pair<u32, u32>> ppu_thread::dump_callstack_list() const
20372037
is_first = false;
20382038
}
20392039

2040-
return call_stack_list;
2040+
// Experimental: include tail calls in callstack
2041+
// Debuggers are often so naive to calling optimizations
2042+
// Try to have higher standards
2043+
std::vector<std::pair<u32, u32>> call_stack_list_expanded_with_tail_calls;
2044+
2045+
if (!call_stack_list.empty())
2046+
{
2047+
for (usz i = 0; i < call_stack_list.size(); i++)
2048+
{
2049+
const auto [func_call_before, stack_frame_addr_prev] = call_stack_list[i];
2050+
const auto [func_call_next, stack_frame_addr_next] = i == 0 ? std::make_pair(_cia, stack_ptr) : call_stack_list[i - 1];
2051+
2052+
ensure(func_call_before > 4);
2053+
2054+
be_t<u32> opcode;
2055+
if (!vm::try_access(func_call_before - 4, &opcode, sizeof(opcode), false))
2056+
{
2057+
// Fail
2058+
call_stack_list_expanded_with_tail_calls.emplace_back(call_stack_list[i]);
2059+
continue;
2060+
}
2061+
2062+
const ppu_opcode_t op{opcode};
2063+
2064+
const auto results = op_branch_targets(func_call_before - 4, op);
2065+
2066+
bool proceeded = false;
2067+
u32 func_call_before_target = umax;
2068+
2069+
for (usz res_i = 0; res_i < results.size(); res_i++)
2070+
{
2071+
const u32 route_pc = results[res_i];
2072+
2073+
if (route_pc == umax || route_pc == func_call_before)
2074+
{
2075+
continue;
2076+
}
2077+
2078+
if (vm::check_addr(route_pc, vm::page_executable))
2079+
{
2080+
ensure(!proceeded);
2081+
2082+
// Next PC
2083+
func_call_before_target = route_pc;
2084+
proceeded = true;
2085+
break;
2086+
}
2087+
}
2088+
2089+
if (!proceeded)
2090+
{
2091+
// Fail
2092+
call_stack_list_expanded_with_tail_calls.emplace_back(call_stack_list[i]);
2093+
continue;
2094+
}
2095+
2096+
struct context_t
2097+
{
2098+
u32 start_point;
2099+
bool modified_stack = false;
2100+
bool restored_stack = false;
2101+
2102+
bool can_be_tail_call() const
2103+
{
2104+
// Either stack was not touched or was saved and restored
2105+
// Either way, stack address did not change in the end
2106+
return modified_stack == restored_stack;
2107+
}
2108+
};
2109+
2110+
std::deque<context_t> workload{context_t{func_call_before_target}};
2111+
2112+
std::vector<be_t<u32>> inst_pos;
2113+
2114+
auto get_inst = [&](u32 pos, u32 low_bound) -> be_t<u32>&
2115+
{
2116+
static be_t<u32> s_inst_empty{};
2117+
2118+
if (pos < low_bound)
2119+
{
2120+
return s_inst_empty;
2121+
}
2122+
2123+
const u32 pos_dist = (pos - low_bound) / 4;
2124+
2125+
if (pos_dist >= inst_pos.size())
2126+
{
2127+
const u32 inst_bound = utils::align<u32>(pos, 256);
2128+
2129+
const usz old_size = inst_pos.size();
2130+
const usz new_size = pos_dist + (inst_bound - pos) / 4 + 1;
2131+
2132+
if (new_size >= 0x2000)
2133+
{
2134+
// Let's not analyse a function this far at the moment
2135+
// Functions using tail calls are very short
2136+
return s_inst_empty;
2137+
}
2138+
2139+
inst_pos.resize(new_size);
2140+
2141+
if (!vm::try_access(pos, &inst_pos[old_size], ::narrow<u32>((new_size - old_size) * sizeof(be_t<u32>)), false))
2142+
{
2143+
// Failure (this would be detected as failure by zeroes)
2144+
}
2145+
}
2146+
2147+
return inst_pos[pos_dist];
2148+
};
2149+
2150+
// Tail call CIAs
2151+
std::vector<u32> tail_calls_found;
2152+
2153+
// Highest target address, but must be lower than func_call_before_target
2154+
// Which would be the only option to be the parent function
2155+
u32 highest_CIA_target_of_tail_call = 0;
2156+
2157+
for (usz wi = 0; wi < workload.size(); wi++)
2158+
{
2159+
auto& [work_pc, modified_stack, restored_stack] = workload[wi];
2160+
2161+
for (usz inst_pc = work_pc;;)
2162+
{
2163+
be_t<u32>& opcode = get_inst(inst_pc, func_call_before_target);
2164+
2165+
if (!opcode)
2166+
{
2167+
// Already passed or failure of reading
2168+
break;
2169+
}
2170+
2171+
const ppu_opcode_t op{opcode};
2172+
2173+
// Mark as passed through
2174+
opcode = 0;
2175+
2176+
const auto type = g_ppu_itype.decode(op.opcode);
2177+
2178+
if ((type & ppu_itype::branch) && op.lk)
2179+
{
2180+
if (!modified_stack || restored_stack)
2181+
{
2182+
// Cannot be a valid call, abort
2183+
highest_CIA_target_of_tail_call = 0;
2184+
tail_calls_found.clear();
2185+
break;
2186+
}
2187+
2188+
// We do not care about the target here
2189+
inst_pc += 4;
2190+
continue;
2191+
}
2192+
2193+
if (type == ppu_itype::B || type == ppu_itype::BC)
2194+
{
2195+
const u32 target = ((op.aa ? 0 : inst_pc) + (type == ppu_itype::B ? +op.bt24 : +op.bt14));
2196+
2197+
if (modified_stack == restored_stack)
2198+
{
2199+
// Can be a tail call
2200+
2201+
if (target && highest_CIA_target_of_tail_call < target && target < func_call_before_target)
2202+
{
2203+
// All the previous list is discarded as being irrelevant
2204+
tail_calls_found.clear();
2205+
highest_CIA_target_of_tail_call = target;
2206+
}
2207+
2208+
if (target && highest_CIA_target_of_tail_call == target)
2209+
{
2210+
tail_calls_found.emplace_back(inst_pc);
2211+
}
2212+
}
2213+
}
2214+
2215+
if (type == ppu_itype::STDU && op.rs == 1u && op.ra == 1u)
2216+
{
2217+
if (modified_stack)
2218+
{
2219+
highest_CIA_target_of_tail_call = 0;
2220+
tail_calls_found.clear();
2221+
break;
2222+
}
2223+
2224+
modified_stack = true;
2225+
}
2226+
else if (type == ppu_itype::ADDI && op.ra == 1u && op.rd == 1u)
2227+
{
2228+
if (!modified_stack || restored_stack)
2229+
{
2230+
highest_CIA_target_of_tail_call = 0;
2231+
tail_calls_found.clear();
2232+
break;
2233+
}
2234+
2235+
restored_stack = true;
2236+
}
2237+
2238+
// Even if BCLR is conditional, it still counts because LR value is ready for return
2239+
if (type == ppu_itype::BCLR || type == ppu_itype::BCCTR)
2240+
{
2241+
// This is more complex than that but let's treat it as function return for now
2242+
// Jump table is not to be supported in this short and humble function analyzer
2243+
break;
2244+
}
2245+
2246+
const auto results = op_branch_targets(inst_pc, op);
2247+
2248+
bool proceeded = false;
2249+
2250+
for (usz res_i = 0; res_i < results.size(); res_i++)
2251+
{
2252+
const u32 route_pc = results[res_i];
2253+
2254+
if (route_pc == umax)
2255+
{
2256+
continue;
2257+
}
2258+
2259+
if (vm::check_addr(route_pc, vm::page_executable) && get_inst(route_pc, func_call_before_target))
2260+
{
2261+
if (proceeded)
2262+
{
2263+
// Remember next route start point
2264+
workload.push_back(context_t{route_pc, modified_stack, restored_stack});
2265+
}
2266+
else
2267+
{
2268+
// Next PC
2269+
inst_pc = route_pc;
2270+
proceeded = true;
2271+
}
2272+
}
2273+
}
2274+
2275+
if (!proceeded)
2276+
{
2277+
break;
2278+
}
2279+
}
2280+
}
2281+
2282+
if (tail_calls_found.empty() || !highest_CIA_target_of_tail_call)
2283+
{
2284+
// Fail
2285+
call_stack_list_expanded_with_tail_calls.emplace_back(call_stack_list[i]);
2286+
continue;
2287+
}
2288+
2289+
if (tail_calls_found.size() >= 2)
2290+
{
2291+
// Ambiguity: it is impossible to handle
2292+
// There is more than one tail call that targets the function
2293+
call_stack_list_expanded_with_tail_calls.emplace_back(call_stack_list[i]);
2294+
continue;
2295+
}
2296+
2297+
const u32 tail_call_cia = ::at32(tail_calls_found, 0);
2298+
2299+
// Now we check if highest_CIA_target_of_tail_call is actually the function that hosts the program counter
2300+
2301+
workload.clear();
2302+
inst_pos.clear();
2303+
workload.push_back(context_t{highest_CIA_target_of_tail_call});
2304+
2305+
bool path_to_current_frame_found = false;
2306+
2307+
for (usz wi = 0; !path_to_current_frame_found && wi < workload.size(); wi++)
2308+
{
2309+
auto& [work_pc, modified_stack, restored_stack] = workload[wi];
2310+
2311+
for (usz inst_pc = work_pc;;)
2312+
{
2313+
if (inst_pc == func_call_next)
2314+
{
2315+
// Match found!
2316+
path_to_current_frame_found = true;
2317+
break;
2318+
}
2319+
2320+
be_t<u32>& opcode = get_inst(inst_pc, highest_CIA_target_of_tail_call);
2321+
2322+
if (!opcode)
2323+
{
2324+
// Already passed or failure of reading
2325+
break;
2326+
}
2327+
2328+
const ppu_opcode_t op{opcode};
2329+
2330+
// Mark as passed through
2331+
opcode = 0;
2332+
2333+
const auto type = g_ppu_itype.decode(op.opcode);
2334+
2335+
if ((type & ppu_itype::branch) && op.lk)
2336+
{
2337+
// We do not care about the target here
2338+
inst_pc += 4;
2339+
continue;
2340+
}
2341+
2342+
// Even if BCLR is conditional, it still counts because LR value is ready for return
2343+
if (type == ppu_itype::BCLR || type == ppu_itype::BCCTR)
2344+
{
2345+
// This is more complex than that but let's treat it as function return for now
2346+
// Jump table is not to be supported in this short and humble function analyzer
2347+
break;
2348+
}
2349+
2350+
const auto results = op_branch_targets(inst_pc, op);
2351+
2352+
bool proceeded = false;
2353+
2354+
for (usz res_i = 0; res_i < results.size(); res_i++)
2355+
{
2356+
const u32 route_pc = results[res_i];
2357+
2358+
if (route_pc == umax)
2359+
{
2360+
continue;
2361+
}
2362+
2363+
if (vm::check_addr(route_pc, vm::page_executable) && get_inst(route_pc, highest_CIA_target_of_tail_call))
2364+
{
2365+
if (proceeded)
2366+
{
2367+
// Remember next route start point
2368+
workload.push_back(context_t{route_pc, modified_stack, restored_stack});
2369+
}
2370+
else
2371+
{
2372+
// Next PC
2373+
inst_pc = route_pc;
2374+
proceeded = true;
2375+
}
2376+
}
2377+
}
2378+
2379+
if (!proceeded)
2380+
{
2381+
break;
2382+
}
2383+
}
2384+
}
2385+
2386+
if (!path_to_current_frame_found)
2387+
{
2388+
// Fail
2389+
call_stack_list_expanded_with_tail_calls.emplace_back(call_stack_list[i]);
2390+
continue;
2391+
}
2392+
2393+
// Tail call found!
2394+
call_stack_list_expanded_with_tail_calls.emplace_back(tail_call_cia, stack_frame_addr_next); // TODO: Check stack frame, maybe it is stack_frame_addr_prev
2395+
call_stack_list_expanded_with_tail_calls.emplace_back(call_stack_list[i]);
2396+
}
2397+
}
2398+
2399+
ensure(call_stack_list_expanded_with_tail_calls.size() >= call_stack_list.size());
2400+
return call_stack_list_expanded_with_tail_calls;
20412401
}
20422402

20432403
void ppu_thread::dump_misc(std::string& ret, std::any& custom_data) const

0 commit comments

Comments
 (0)