@@ -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
20432403void ppu_thread::dump_misc (std::string& ret, std::any& custom_data) const
0 commit comments