From 86b33fbd40bb91be343ed562f7ee3d4bb94c8431 Mon Sep 17 00:00:00 2001 From: Zoltan Somogyi Date: Thu, 15 Jan 2026 10:45:35 +1100 Subject: [PATCH] Gather statistics about switch search depth results. compiler/switch_detection.m: Add conditionally-enabled code to gather statistics about what happens when a switch candidate has disjuncts left over, and we test whether these leftovers form a switch on another variable. (The results show that after 99.91% of switch candidates, there are *no* disjuncts left over.) compiler/mercury_compile_main.m: Invoke the predicate that writes out any statistics we gathered. tools/switch_depth: This new summarizes the results. --- compiler/mercury_compile_main.m | 5 +- compiler/switch_detection.m | 90 +++++++++++++++++++++++++++++++-- tools/switch_depth | 36 +++++++++++++ 3 files changed, 127 insertions(+), 4 deletions(-) create mode 100755 tools/switch_depth diff --git a/compiler/mercury_compile_main.m b/compiler/mercury_compile_main.m index 7728376b3..1085af41a 100644 --- a/compiler/mercury_compile_main.m +++ b/compiler/mercury_compile_main.m @@ -2,7 +2,7 @@ % vim: ts=4 sw=4 et ft=mercury %---------------------------------------------------------------------------% % Copyright (C) 1994-2012 The University of Melbourne. -% Copyright (C) 2017-2025 The Mercury Team. +% Copyright (C) 2017-2026 The Mercury Team. % This file may only be copied under the terms of the GNU General % Public License - see the file COPYING in the Mercury distribution. %---------------------------------------------------------------------------% @@ -42,6 +42,8 @@ :- import_module backend_libs. :- import_module backend_libs.compile_target_code. :- import_module backend_libs.link_target_code. +:- import_module check_hlds. +:- import_module check_hlds.switch_detection. :- import_module hlds. :- import_module hlds.instmap. :- import_module libs.check_libgrades. @@ -130,6 +132,7 @@ real_main(!IO) :- record_make_prereqs_cache_stats(!IO), record_module_ext_cache_stats(!IO), record_instmap_delta_restrict_stats(!IO), + record_switch_search_depth_results(!IO), close_any_specific_compiler_streams(!IO). %---------------------------------------------------------------------------% diff --git a/compiler/switch_detection.m b/compiler/switch_detection.m index dd30fff7b..884d4ee3d 100644 --- a/compiler/switch_detection.m +++ b/compiler/switch_detection.m @@ -37,6 +37,8 @@ :- pred detect_switches_in_proc(switch_detect_info::in, proc_info::in, proc_info::out) is det. +:- pred record_switch_search_depth_results(io::di, io::uo) is det. + %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% @@ -87,6 +89,7 @@ :- import_module string. :- import_module term. :- import_module term_context. +:- import_module uint. :- import_module unit. :- import_module varset. @@ -190,13 +193,13 @@ detect_switches_in_proc(Info, !ProcInfo) :- Requant0 = do_not_need_to_requantify, BodyDeletedCallCallees0 = set.init, LocalInfo0 = local_switch_detect_info(VarTable, DisjunctionInfoMap, - ModuleInfo, Requant0, BodyDeletedCallCallees0), + 0u, ModuleInfo, Requant0, BodyDeletedCallCallees0), detect_switches_in_goal(InstMap0, nrsv, Goal0, Goal, LocalInfo0, LocalInfo), proc_info_set_goal(Goal, !ProcInfo), LocalInfo = local_switch_detect_info(_VarTable, _DisjunctionInfoMap, - _ModuleInfo, Requant, BodyDeletedCallCallees), + _, _ModuleInfo, Requant, BodyDeletedCallCallees), ( Requant = need_to_requantify, requantify_proc_general(ord_nl_maybe_lambda, !ProcInfo) @@ -217,7 +220,12 @@ detect_switches_in_proc(Info, !ProcInfo) :- lsdi_disjunction_info_map :: disjunction_info_map, % These fields are read-write. - % + + % The depth matters only in detect_switches_in_disj predicate. + % It specifies the number of recursive calls to that predicate + % above the current invocation. + lsdi_depth :: uint, + % We update module_info when we enter a switch arm, and update % the inst of the switched-on variable to record it being bound % to one of the cons_ids that the switch arm is for. @@ -270,6 +278,7 @@ detect_switches_in_goal(InstMap0, MaybeRequiredVar, Goal0, Goal, !LocalInfo) :- GoalExpr = GoalExpr0 ; Disjuncts0 = [_ | _], + !LocalInfo ^ lsdi_depth := 0u, detect_switches_in_disj(InstMap0, MaybeRequiredVar, Disjuncts0, GoalInfo0, GoalExpr, !LocalInfo) ) @@ -410,13 +419,20 @@ detect_switches_in_disj(InstMap0, MaybeRequiredVar, Disjuncts0, GoalInfo, detect_switch_candidates_in_disj(!.LocalInfo, GoalInfo, Disjuncts0, InstMap0, MaybeRequiredVar, VarsToTry, no_candidate_switch, BestCandidateSoFar), + CurDepth = !.LocalInfo ^ lsdi_depth, ( BestCandidateSoFar = no_candidate_switch, + trace [compile_time(flag("switch-detect-depth-stats")), io(!IO)] ( + record_miss(CurDepth, !IO) + ), detect_sub_switches_in_disj(InstMap0, Disjuncts0, Disjuncts, !LocalInfo), GoalExpr = disj(Disjuncts) ; BestCandidateSoFar = best_candidate_switch_so_far(BestCandidate), + trace [compile_time(flag("switch-detect-depth-stats")), io(!IO)] ( + record_hit(CurDepth, !IO) + ), BestRank = BestCandidate ^ cs_rank, ( BestRank = no_leftover_one_case, @@ -448,6 +464,7 @@ detect_switches_in_disj(InstMap0, MaybeRequiredVar, Disjuncts0, GoalInfo, GoalExpr = SwitchGoalExpr ; LeftDisjuncts0 = [_ | _], + !LocalInfo ^ lsdi_depth := CurDepth + 1u, detect_switches_in_disj(InstMap0, MaybeRequiredVar, LeftDisjuncts0, GoalInfo, LeftGoal, !LocalInfo), goal_to_disj_list(hlds_goal(LeftGoal, GoalInfo), @@ -1084,6 +1101,73 @@ gather_smallest_context([Goal | Goals], !SmallestContext) :- num_cases_in_table(cases_table(CasesMap, _)) = map.count(CasesMap). +%---------------------------------------------------------------------------% + +:- type hit_and_miss + ---> hit_and_miss( + ham_hit :: uint, + ham_miss :: uint + ). + +:- type hit_and_miss_map == map(uint, hit_and_miss). + +:- mutable(ham_map, hit_and_miss_map, map.init, ground, + [untrailed, attach_to_io_state]). + +:- pred record_hit(uint::in, io::di, io::uo) is det. + +record_hit(Depth, !IO) :- + get_ham_map(HamMap0, !IO), + ( if map.search(HamMap0, Depth, HitAndMiss0) then + HitAndMiss0 = hit_and_miss(Hit0, Miss0), + HitAndMiss = hit_and_miss(Hit0 + 1u, Miss0), + map.det_update(Depth, HitAndMiss, HamMap0, HamMap) + else + HitAndMiss = hit_and_miss(1u, 0u), + map.det_insert(Depth, HitAndMiss, HamMap0, HamMap) + ), + set_ham_map(HamMap, !IO). + +:- pred record_miss(uint::in, io::di, io::uo) is det. + +record_miss(Depth, !IO) :- + get_ham_map(HamMap0, !IO), + ( if map.search(HamMap0, Depth, HitAndMiss0) then + HitAndMiss0 = hit_and_miss(Hit0, Miss0), + HitAndMiss = hit_and_miss(Hit0, Miss0 + 1u), + map.det_update(Depth, HitAndMiss, HamMap0, HamMap) + else + HitAndMiss = hit_and_miss(0u, 1u), + map.det_insert(Depth, HitAndMiss, HamMap0, HamMap) + ), + set_ham_map(HamMap, !IO). + +record_switch_search_depth_results(!IO) :- + get_ham_map(HamMap, !IO), + map.to_assoc_list(HamMap, HamAL), + DescStrs = list.map(hit_and_miss_to_string, HamAL), + string.append_list(DescStrs, DescsStr), + ( if DescsStr = "" then + % We should get here if the trace flag --switch-detect-depth-stats + % is not set. + true + else + io.open_append("/tmp/SWITCH_DEPTH_RESULTS", Result, !IO), + ( + Result = error(_) + ; + Result = ok(OutStream), + io.write_string(OutStream, DescsStr, !IO), + io.close_output(OutStream, !IO) + ) + ). + +:- func hit_and_miss_to_string(pair(uint, hit_and_miss)) = string. + +hit_and_miss_to_string(Depth - hit_and_miss(Hit, Miss)) = Str :- + string.format("depth %u hit %3u miss %3u\n", + [u(Depth), u(Hit), u(Miss)], Str). + %---------------------------------------------------------------------------% :- end_module check_hlds.switch_detection. %---------------------------------------------------------------------------% diff --git a/tools/switch_depth b/tools/switch_depth new file mode 100755 index 000000000..dddcc87ad --- /dev/null +++ b/tools/switch_depth @@ -0,0 +1,36 @@ +#!/usr/bin/awk -f +# vim: ft=awk ts=4 sw=4 et +# +# This script summarizes the contents of the SWITCH_DEPTH_RESULTS files +# generated by the conditionally-enabled statistics-gathering code +# in switch_detection.m. +# + +NF == 6 && $1 == "depth" { + depth = $2; + hit = $4; + miss = $6; + hits[depth] += hit; + hit_and_miss = hit + miss; + trials[depth] += hit_and_miss; + } +END { + max_depth = 0; + for (depth in trials) { + if (depth > max_depth) { + max_depth = depth; + } + + hit_rate[depth] = (100 * hits[depth]) / trials[depth]; + printf "depth %d: %5d trials, %5d hits, hit rate = %5.2f%%\n", + depth, trials[depth], hits[depth], hit_rate[depth]; + } + + i = 1; + while (i <= max_depth) { + leak_rate = (100 * trials[i]) / trials[i - 1]; + printf "leak rate to depth %d: %5.2f%%\n", i, leak_rate; + i = i + 1; + } + } +