%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2008-2011 The University of Melbourne.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%---------------------------------------------------------------------------%
%
% File: display_report.m.
% Author: pbone.
%
% This module contains code to create a display data structure from a deep
% profiling report.
%
%---------------------------------------------------------------------------%

:- module display_report.
:- interface.

:- import_module display.
:- import_module profile.
:- import_module report.

% XXX: This include should be removed or replaced. Some data structures
% such as preferences are currently defined in query; they should be moved
% into a different module so that this module doesn't need to include
% the whole of query.
:- import_module query.

%---------------------------------------------------------------------------%

:- func report_to_display(deep, preferences, deep_report) = display.

%---------------------------------------------------------------------------%

:- implementation.

:- import_module coverage.
:- import_module exclude.
:- import_module mdbcomp.
:- import_module mdbcomp.goal_path.
:- import_module mdbcomp.program_representation.
:- import_module measurement_units.
:- import_module program_representation_utils.
:- import_module var_use_analysis.

:- import_module array.
:- import_module assoc_list.
:- import_module bool.
:- import_module cord.
:- import_module counter.
:- import_module float.
:- import_module int.
:- import_module list.
:- import_module map.
:- import_module maybe.
:- import_module pair.
:- import_module require.
:- import_module set.
:- import_module string.
:- import_module unit.

%---------------------------------------------------------------------------%

report_to_display(Deep, Prefs, Report) = Display :-
    (
        Report = report_message(message_report(Msg)),
        Display = display(no, [display_heading(Msg)])
    ;
        Report = report_menu(MaybeMenuReport),
        (
            MaybeMenuReport = ok(MenuReport),
            display_report_menu(Deep, Prefs, MenuReport, Display)
        ;
            MaybeMenuReport = error(Msg),
            Display = display(no, [display_heading(Msg)])
        )
    ;
        Report = report_clique(MaybeCliqueReport),
        (
            MaybeCliqueReport = ok(CliqueReport),
            display_report_clique(Prefs, CliqueReport, Display)
        ;
            MaybeCliqueReport = error(Msg),
            Display = display(no, [display_heading(Msg)])
        )
    ;
        Report = report_clique_recursion_costs(MaybeCliqueRecursionReport),
        (
            MaybeCliqueRecursionReport = ok(CliqueRecursionReport),
            display_report_clique_recursion(Prefs, CliqueRecursionReport,
                Display)
        ;
            MaybeCliqueRecursionReport = error(Msg),
            Display = display(no, [display_heading(Msg)])
        )
    ;
        Report = report_recursion_types_frequency(MaybeRecTypesFreqReport),
        (
            MaybeRecTypesFreqReport = ok(RecTypesFreqReport),
            display_report_recursion_types_frequency(Prefs, RecTypesFreqReport,
                Display)
        ;
            MaybeRecTypesFreqReport = error(Msg),
            Display = display(no, [display_heading(Msg)])
        )
    ;
        Report = report_program_modules(MaybeProgramModulesReport),
        (
            MaybeProgramModulesReport = ok(ProgramModulesReport),
            display_report_program_modules(Prefs, ProgramModulesReport,
                Display)
        ;
            MaybeProgramModulesReport = error(Msg),
            Display = display(no, [display_heading(Msg)])
        )
    ;
        Report = report_module(MaybeModuleReport),
        (
            MaybeModuleReport = ok(ModuleReport),
            display_report_module(Prefs, ModuleReport, Display)
        ;
            MaybeModuleReport = error(Msg),
            Display = display(no, [display_heading(Msg)])
        )
    ;
        Report = report_module_getter_setters(MaybeModuleGetterSettersReport),
        (
            MaybeModuleGetterSettersReport = ok(ModuleGetterSettersReport),
            display_report_module_getter_setters(Prefs,
                ModuleGetterSettersReport, Display)
        ;
            MaybeModuleGetterSettersReport = error(Msg),
            Display = display(no, [display_heading(Msg)])
        )
    ;
        Report = report_module_rep(MaybeModuleRepReport),
        (
            MaybeModuleRepReport = ok(ModuleRepReport),
            display_report_module_rep(Prefs, ModuleRepReport, Display)
        ;
            MaybeModuleRepReport = error(Msg),
            Display = display(no, [display_heading(Msg)])
        )
    ;
        Report = report_top_procs(MaybeTopProcsReport),
        (
            MaybeTopProcsReport = ok(TopProcsReport),
            display_report_top_procs(Prefs, TopProcsReport, Display)
        ;
            MaybeTopProcsReport = error(Msg),
            Display = display(no, [display_heading(Msg)])
        )
    ;
        Report = report_proc(MaybeProcReport),
        (
            MaybeProcReport = ok(ProcReport),
            display_report_proc(Deep, Prefs, ProcReport, Display)
        ;
            MaybeProcReport = error(Msg),
            Display = display(no, [display_heading(Msg)])
        )
    ;
        Report = report_procrep_coverage(MaybeProcrepCoverageInfo),
        (
            MaybeProcrepCoverageInfo = ok(ProcrepCoverageInfo),
            display_report_procrep_coverage_info(Prefs, ProcrepCoverageInfo,
                Display)
        ;
            MaybeProcrepCoverageInfo = error(Msg),
            Display = display(no, [display_text(Msg)])
        )
    ;
        Report = report_proc_callers(MaybeProcCallersReport),
        (
            MaybeProcCallersReport = ok(ProcCallersReport),
            display_report_proc_callers(Deep, Prefs, ProcCallersReport,
                Display)
        ;
            MaybeProcCallersReport = error(Msg),
            Display = display(no, [display_heading(Msg)])
        )
    ;
        Report = report_proc_static_dump(MaybeProcStaticDumpInfo),
        (
            MaybeProcStaticDumpInfo = ok(ProcStaticDumpInfo),
            display_report_proc_static_dump(ProcStaticDumpInfo, Display)
        ;
            MaybeProcStaticDumpInfo = error(Msg),
            Display = display(no, [display_heading(Msg)])
        )
    ;
        Report = report_proc_dynamic_dump(MaybeProcDynamicDumpInfo),
        (
            MaybeProcDynamicDumpInfo = ok(ProcDynamicDumpInfo),
            display_report_proc_dynamic_dump(Deep, Prefs, ProcDynamicDumpInfo,
                Display)
        ;
            MaybeProcDynamicDumpInfo = error(Msg),
            Display = display(no, [display_heading(Msg)])
        )
    ;
        Report = report_call_site_static_dump(MaybeCallSiteStaticDumpInfo),
        (
            MaybeCallSiteStaticDumpInfo = ok(CallSiteStaticDumpInfo),
            display_report_call_site_static_dump(Prefs, CallSiteStaticDumpInfo,
                Display)
        ;
            MaybeCallSiteStaticDumpInfo = error(Msg),
            Display = display(no, [display_heading(Msg)])
        )
    ;
        Report = report_call_site_dynamic_dump(MaybeCallSiteDynamicDumpInfo),
        (
            MaybeCallSiteDynamicDumpInfo = ok(CallSiteDynamicDumpInfo),
            display_report_call_site_dynamic_dump(Prefs,
                CallSiteDynamicDumpInfo, Display)
        ;
            MaybeCallSiteDynamicDumpInfo = error(Msg),
            Display = display(no, [display_heading(Msg)])
        )
    ;
        Report = report_clique_dump(MaybeCliqueDumpInfo),
        (
            MaybeCliqueDumpInfo = ok(CliqueDumpInfo),
            display_report_clique_dump(Prefs, CliqueDumpInfo, Display)
        ;
            MaybeCliqueDumpInfo = error(Msg),
            Display = display(no, [display_heading(Msg)])
        )
    ;
        Report = report_call_site_dynamic_var_use(MaybeVarUseInfo),
        (
            MaybeVarUseInfo = ok(VarUseInfo),
            display_report_call_site_dynamic_var_use(Prefs, VarUseInfo,
                Display)
        ;
            MaybeVarUseInfo = error(Msg),
            Display = display(no, [display_heading(Msg)])
        )
    ).

%---------------------------------------------------------------------------%
%
% Code to display menu report.
%

:- pred display_report_menu(deep::in, preferences::in, menu_report::in,
    display::out) is det.

display_report_menu(Deep, Prefs, MenuReport, Display) :-
    MenuReport = menu_report(ProgramName, QuantaPerSec, UserQuanta, InstQuanta,
        NumCallseqs, NumCSD, NumCSS, NumPD, NumPS, NumClique),
    TotalQuanta = UserQuanta + InstQuanta,
    TotalTime = ticks_to_time(TotalQuanta, QuantaPerSec),

    ShouldDisplayTimes = should_display_times(Deep),

    % Display the links section of the report.
    ActionPrefs0 = Prefs,
    ActionPrefs1 = ActionPrefs0 ^ pref_inactive :=
        inactive_items(inactive_hide, inactive_hide, inactive_hide),
    ActionPrefs = ActionPrefs1 ^ pref_criteria :=
        by_cost(cost_time, self_and_desc, overall),

    LinksExploration = [
        link_base(deep_cmd_root(no), yes(ActionPrefs),
            "Exploring the call graph, starting at the root."),
        link_base(deep_cmd_root(yes(90)), yes(ActionPrefs),
            "Exploring the call graph, starting at the action."),
        link_base(deep_cmd_program_modules, yes(Prefs),
            "Exploring the program module by module.")
    ],

    (
        ShouldDisplayTimes = yes,

        Top100SelfCmd = deep_cmd_top_procs(rank_range(1, 100),
            cost_time, self, overall),
        Top100SelfAndDescCmd = deep_cmd_top_procs(rank_range(1, 100),
            cost_time, self_and_desc, overall),

        LinksTopProcsByLimitTime = [
            link_base(Top100SelfCmd, yes(Prefs),
                "Top 100 most expensive procedures: time, self."),
            link_base(Top100SelfAndDescCmd, yes(Prefs),
                "Top 100 most expensive procedures: time, self+descendants.")
        ]
    ;
        ShouldDisplayTimes = no,
        LinksTopProcsByLimitTime = []
    ),

    TopLimitCallSeqsSelf = deep_cmd_top_procs(rank_range(1, 100),
        cost_callseqs, self, overall),
    TopLimitCallSeqsSelfAndDesc = deep_cmd_top_procs(rank_range(1, 100),
        cost_callseqs, self_and_desc, overall),
    TopLimitWordsSelf = deep_cmd_top_procs(rank_range(1, 100),
        cost_words, self, overall),
    TopLimitWordsSelfAndDesc = deep_cmd_top_procs(rank_range(1, 100),
        cost_words, self_and_desc, overall),

    LinksTopProcsByLimit = [
        link_base(TopLimitCallSeqsSelf, yes(Prefs),
            "Top 100 most expensive procedures: callseqs, self."),
        link_base(TopLimitCallSeqsSelfAndDesc, yes(Prefs),
            "Top 100 most expensive procedures: callseqs, self+descendants."),
        link_base(TopLimitWordsSelf, yes(Prefs),
            "Top 100 most expensive procedures: words, self."),
        link_base(TopLimitWordsSelfAndDesc, yes(Prefs),
            "Top 100 most expensive procedures: words, self+descendants.")
    ],

    (
        ShouldDisplayTimes = yes,
        TimeAbove01PercentCmd = deep_cmd_top_procs(threshold_percent(0.1),
            cost_time, self, overall),
        TimeAbove1PercentCmd = deep_cmd_top_procs(threshold_percent(1.0),
            cost_time, self, overall),
        TimeAbove1SecondCmd = deep_cmd_top_procs(threshold_value(100.0),
            cost_time, self_and_desc, overall),

        LinksTopProcsByPercentTime = [
            link_base(TimeAbove01PercentCmd, yes(Prefs),
                "Procedures above 0.1% threshold: time, self."),
            link_base(TimeAbove1PercentCmd, yes(Prefs),
                "Procedures above 1% threshold: time, self+descendants."),
            link_base(TimeAbove1SecondCmd, yes(Prefs),
                "Procedures above 1 second threshold: time, self+descendants.")
        ]
    ;
        ShouldDisplayTimes = no,
        LinksTopProcsByPercentTime = []
    ),

    CallSeqsAbove01PercentCmd = deep_cmd_top_procs(threshold_percent(0.1),
        cost_callseqs, self, overall),
    CallSeqsAbove1PercentCmd = deep_cmd_top_procs(threshold_percent(1.0),
        cost_callseqs, self_and_desc, overall),
    CallSeqsAboveMillionCmd = deep_cmd_top_procs(threshold_value(1000000.0),
        cost_callseqs, self_and_desc, overall),
    WordsAbove01PercentCmd = deep_cmd_top_procs(threshold_percent(0.1),
        cost_words, self, overall),
    WordsAbove1PercentCmd = deep_cmd_top_procs(threshold_percent(1.0),
        cost_words, self_and_desc, overall),
    % 2M words is 8MB on ia32.
    WordsAbove2Megawords = deep_cmd_top_procs(
        threshold_value(float(1024 * 1024 * 2)),
        cost_words, self_and_desc, overall),

    LinksTopProcsByPercent = [
        link_base(CallSeqsAbove01PercentCmd, yes(Prefs),
            "Procedures above 0.1% threshold: callseqs, self."),
        link_base(CallSeqsAbove1PercentCmd, yes(Prefs),
            "Procedures above 1% threshold: callseqs, self+descendants."),
        link_base(CallSeqsAboveMillionCmd, yes(Prefs),
            ("Procedures above 1,000,000 callseqs threshold: callseqs, " ++
                "self+descendants.")),
        link_base(WordsAbove01PercentCmd, yes(Prefs),
            "Procedures above 0.1% threshold: words, self."),
        link_base(WordsAbove1PercentCmd, yes(Prefs),
            "Procedures above 1% threshold: words, self+descendants."),
        link_base(WordsAbove2Megawords, yes(Prefs),
            "Procedures above 2M words threshold: words, self+descendants.")
    ],

    LinkCmds = LinksExploration ++
        LinksTopProcsByLimitTime ++ LinksTopProcsByLimit ++
        LinksTopProcsByPercentTime ++ LinksTopProcsByPercent,
    list.map(make_link, LinkCmds, LinksList),
    Links = display_list(list_class_vertical_bullets,
        yes("You can start exploring the deep profile at the following" ++
            " points."), LinksList),

    % Produce the developer-only options list.
    RecursionTypeFrequenciesCmd = deep_cmd_recursion_types_frequency,

    LinksDeveloperCmds = [
        link_base(RecursionTypeFrequenciesCmd, yes(Prefs),
            "Frequencies of different types of recursion used in the program.")
    ],
    list.map(make_link, LinksDeveloperCmds, DeveloperLinksList),
    DeveloperLinks = display_developer(
        display_list(list_class_vertical_bullets,
            yes("Options that are only useful to Mercury developers"),
            DeveloperLinksList)),

    % Display the table section of the report.
    ProfilingStatistics =
        [("Profile generated for:"      - td_s(ProgramName)),
        ("Quanta per second:"           - td_i(QuantaPerSec)),
        ("Quanta in user code:"         - td_i(UserQuanta)),
        ("Quanta in instrumentation:"   - td_i(InstQuanta)),
        ("Total quanta:"                - td_i(TotalQuanta)),
        ("Total time:"                  - td_t(TotalTime)),
        ("Call sequence numbers:"       - td_i(NumCallseqs)),
        ("CallSiteDynamic structures:"  - td_i(NumCSD)),
        ("ProcDynamic structures:"      - td_i(NumPD)),
        ("CallSiteStatic structures:"   - td_i(NumCSS)),
        ("ProcStatic structures:"       - td_i(NumPS)),
        ("Cliques:"                     - td_i(NumClique))],

    Rows = list.map(make_labelled_table_row, ProfilingStatistics),
    Table = table(table_class_do_not_box, 2, no, Rows),

    OptionsControls = general_options_controls(deep_cmd_menu, Prefs),

    MenuRestartQuitControls = cmds_menu_restart_quit(yes(Prefs)),

    % Construct the complete representation of what to display.
    Display = display(yes("Deep profiler menu"),
        [Links, DeveloperLinks, display_table(Table),
        display_paragraph_break, MenuRestartQuitControls,
        display_paragraph_break, OptionsControls]).

%---------------------------------------------------------------------------%
%
% Code to display a clique report.
%

    % Create a display_report structure for a clique report.
    %
:- pred display_report_clique(preferences::in, clique_report::in,
    display::out) is det.

display_report_clique(Prefs, CliqueReport, Display) :-
    CliqueReport = clique_report(CliquePtr, InnerToOuterAncestorCallSites0,
        CliqueProcs0),
    Cmd = deep_cmd_clique(CliquePtr),
    CliquePtr = clique_ptr(CliqueNum),
    Title = string.format("Clique %d:", [i(CliqueNum)]),

    % Build the table of all modules.
    SortByContextPrefs = Prefs ^ pref_criteria := by_context,
    SortByNamePrefs = Prefs ^ pref_criteria := by_name,
    SourceHeaderCell = td_l(deep_link(Cmd, yes(SortByContextPrefs),
        attr_str([], "Source"), link_class_link)),
    ProcHeaderCell = td_l(deep_link(Cmd, yes(SortByNamePrefs),
        attr_str([], "Procedure"), link_class_link)),
    SourceHeaderGroup =
        make_single_table_header_group(SourceHeaderCell,
            table_column_class_source_context, column_do_not_colour),
    ProcHeaderGroup =
        make_single_table_header_group(ProcHeaderCell,
            table_column_class_proc, column_do_not_colour),
    MakeHeaderData = override_order_criteria_header_data(Cmd),
    perf_table_header(total_columns_meaningful, Prefs, MakeHeaderData,
        PerfHeaderGroups),
    AllHeaderGroups = [SourceHeaderGroup, ProcHeaderGroup] ++ PerfHeaderGroups,
    header_groups_to_header(AllHeaderGroups, NumColumns, Header),

    list.length(InnerToOuterAncestorCallSites0, NumAncestors),
    MaybeAncestorLimit = Prefs ^ pref_anc,
    ( if
        MaybeAncestorLimit = yes(AncestorLimit),
        NumAncestors > AncestorLimit
    then
        AncestorTitle = string.format("The %d closest ancestor call sites:",
            [i(AncestorLimit)]),
        list.take_upto(AncestorLimit,
            InnerToOuterAncestorCallSites0, InnerToOuterAncestorCallSites)
    else
        AncestorTitle = "Ancestor call sites:",
        InnerToOuterAncestorCallSites = InnerToOuterAncestorCallSites0
    ),

    list.reverse(InnerToOuterAncestorCallSites, AncestorCallSites),
    ModuleQual = Prefs ^ pref_module_qual,
    CliqueModuleNames = list.map(clique_proc_report_module_name, CliqueProcs0),
    ( if
        CliqueModuleNames = [FirstModuleName | _],
        list.all_same(CliqueModuleNames)
    then
        MaybeCurModuleName = yes(FirstModuleName)
    else
        MaybeCurModuleName = no
    ),
    AncestorDataRows = list.map(
        clique_ancestor_to_row(MaybeCurModuleName, ModuleQual, Prefs),
        AncestorCallSites),
    AncestorSectionHeaderRow = table_section_header(td_s(AncestorTitle)),
    AncestorRows = [AncestorSectionHeaderRow, table_separator_row] ++
        AncestorDataRows,

    list.length(CliqueProcs0, NumCliqueprocs),
    ( if NumCliqueprocs > 1 then
        ProcsTitle = "Procedures of the clique:"
    else
        ProcsTitle = "The clique has one procedure:"
    ),
    CliqueProcsHeaderRow = table_section_header(td_s(ProcsTitle)),

    sort_clique_procs_by_preferences(MaybeCurModuleName, ModuleQual, Prefs,
        CliqueProcs0, CliqueProcs),
    ProcRowLists0 = list.map(
        clique_proc_to_table_rows(MaybeCurModuleName, ModuleQual, Prefs,
            CliquePtr),
        CliqueProcs),
    ProcRowLists = list.map(add_front_separator_row, ProcRowLists0),
    list.condense(ProcRowLists, ProcRows),

    AllRows = AncestorRows ++
        [table_separator_row, CliqueProcsHeaderRow] ++ ProcRows,
    Table = table(table_class_box_if_pref, NumColumns, yes(Header), AllRows),
    DisplayTable = display_table(Table),

    % Build controls at the bottom of the page.
    AncestorControls = ancestor_controls(Prefs, Cmd),
    InactiveCallSiteControls = inactive_call_site_controls(Prefs, Cmd),
    ModuleQualControls = module_qual_controls(Prefs, Cmd),
    FieldControls = field_controls(Prefs, Cmd),
    FormatControls = format_controls(Prefs, Cmd),
    CliqueReportControls = clique_reports_controls(Prefs, CliquePtr, Cmd),
    MenuRestartQuitControls = cmds_menu_restart_quit(yes(Prefs)),

    Display = display(yes(Title),
        [DisplayTable,
        display_paragraph_break, AncestorControls,
        display_paragraph_break, InactiveCallSiteControls,
        display_paragraph_break, ModuleQualControls,
        display_paragraph_break, FieldControls,
        display_paragraph_break, FormatControls,
        display_paragraph_break, CliqueReportControls,
        display_paragraph_break, MenuRestartQuitControls]).

:- func clique_proc_report_module_name(clique_proc_report) = string.

clique_proc_report_module_name(CliqueProc) =
    CliqueProc ^ cpr_proc_summary ^ perf_row_subject ^ pdesc_module_name.

:- func add_front_separator_row(list(table_row)) = list(table_row).

add_front_separator_row(Rows) = [table_separator_row | Rows].

:- func clique_ancestor_to_row(maybe(string), module_qual, preferences,
    perf_row_data(ancestor_desc)) = table_row.

clique_ancestor_to_row(MaybeCurModuleName, ModuleQual, Prefs, AncestorRowData)
        = Row :-
    AncestorDesc = AncestorRowData ^ perf_row_subject,
    CallSiteDesc = AncestorDesc ^ ad_call_site_desc,
    SourceCell = call_site_desc_source_cell(CallSiteDesc),
    CliqueProcCell = call_site_desc_clique_proc_cell(MaybeCurModuleName,
        ModuleQual, Prefs, AncestorDesc),
    Fields = Prefs ^ pref_fields,
    perf_table_row(total_columns_meaningful, Fields, AncestorRowData,
        PerfCells),
    AllCells = [SourceCell, CliqueProcCell] ++ PerfCells,
    Row = table_row(AllCells).

:- func clique_proc_to_table_rows(maybe(string), module_qual, preferences,
    clique_ptr, clique_proc_report) = list(table_row).

clique_proc_to_table_rows(MaybeCurModuleName, ModuleQual, Prefs, CliquePtr,
        CliqueProcReport) = ProcRows :-
    CliqueProcReport = clique_proc_report(SummaryRowData,
        FirstPDReport, LaterPDReports),
    (
        LaterPDReports = [],
        ProcRows = clique_proc_dynamic_to_table_rows(MaybeCurModuleName,
            ModuleQual, Prefs, CliquePtr, FirstPDReport)
    ;
        LaterPDReports = [_ | _],
        AllPDReports = [FirstPDReport | LaterPDReports],
        sort_clique_proc_dynamics_by_preferences(Prefs, AllPDReports,
            SortedAllPDReports),
        PDProcRowLists =
            list.map(
                clique_proc_dynamic_to_table_rows(MaybeCurModuleName,
                    ModuleQual, Prefs, CliquePtr),
                SortedAllPDReports),
        % Do we want separators between the rows of different proc dynamics?
        list.condense(PDProcRowLists, PDProcRows),
        Fields = Prefs ^ pref_fields,
        perf_table_row(total_columns_meaningful, Fields, SummaryRowData,
            SummaryPerfCells),
        ProcDesc = SummaryRowData ^ perf_row_subject,
        SourceCell = proc_desc_to_source_cell(ProcDesc),
        ProcCell = proc_desc_to_prefix_proc_name_cell(no,
            ModuleQual, Prefs, [attr_bold], ProcDesc, "summary "),
        SummaryRowCells = [SourceCell, ProcCell] ++ SummaryPerfCells,
        SummaryRow = table_row(SummaryRowCells),
        ProcRows = [SummaryRow, table_separator_row] ++ PDProcRows
    ).

:- func clique_proc_dynamic_to_table_rows(maybe(string), module_qual,
    preferences, clique_ptr, clique_proc_dynamic_report) = list(table_row).

clique_proc_dynamic_to_table_rows(MaybeCurModuleName, ModuleQual,
        Prefs, CliquePtr, CliqueProcDynamicReport) = ProcRows :-
    CliqueProcDynamicReport = clique_proc_dynamic_report(SummaryRowData,
        CallSiteReports0),
    ProcDesc = SummaryRowData ^ perf_row_subject,
    ProcCell = proc_desc_to_proc_name_cell_span(no, ModuleQual, Prefs,
        [attr_bold], ProcDesc, 2),
    Fields = Prefs ^ pref_fields,
    perf_table_row(total_columns_meaningful, Fields, SummaryRowData,
        SummaryPerfCells),
    SummaryRowCells = [ProcCell] ++ SummaryPerfCells,
    SummaryRow = table_row(SummaryRowCells),
    sort_clique_call_site_reports_by_preferences(MaybeCurModuleName,
        ModuleQual, Prefs, CallSiteReports0, CallSiteReports),
    CallSiteRowLists =
        list.map(
            clique_call_site_to_rows(MaybeCurModuleName, ModuleQual, Prefs,
                CliquePtr),
        CallSiteReports),
    list.condense(CallSiteRowLists, CallSiteRows),
    ProcRows = [SummaryRow, table_separator_row] ++ CallSiteRows.

:- func clique_call_site_to_rows(maybe(string), module_qual, preferences,
    clique_ptr, clique_call_site_report) = list(table_row).

clique_call_site_to_rows(MaybeCurModuleName, ModuleQual, Prefs,
        CliquePtr, CallSiteReport) = Rows :-
    CallSiteReport = clique_call_site_report(CallSiteRowData, Kind,
        CalleePerfs),
    CallSiteDesc = CallSiteRowData ^ perf_row_subject,
    SourceCell = call_site_desc_source_cell(CallSiteDesc),
    Fields = Prefs ^ pref_fields,
    perf_table_row(total_columns_meaningful, Fields, CallSiteRowData,
        SummaryPerfCells),
    (
        Kind = normal_call_and_callee(CalleeProcDesc, _TypeSubst),
        (
            CalleePerfs = [],
            Inactive = Prefs ^ pref_inactive,
            Inactive = inactive_items(InactiveCallSites, _, _),
            (
                InactiveCallSites = inactive_show,
                CalleeProcCell = proc_desc_to_prefix_proc_name_cell(
                    MaybeCurModuleName, ModuleQual, Prefs,
                    [], CalleeProcDesc, "no calls made to "),
                RowCells = [SourceCell, CalleeProcCell] ++ SummaryPerfCells,
                Row = table_row(RowCells),
                Rows = [Row]
            ;
                InactiveCallSites = inactive_hide,
                Rows = []
            )
        ;
            CalleePerfs = [CalleePerf],
            CalleeCliqueDesc = CalleePerf ^ perf_row_subject,
            CalleeProcCell = clique_desc_to_non_self_link_proc_name_cell(
                MaybeCurModuleName, ModuleQual, Prefs,
                CalleeCliqueDesc, CliquePtr),
            RowCells = [SourceCell, CalleeProcCell] ++ SummaryPerfCells,
            Row = table_row(RowCells),
            Rows = [Row]
        ;
            CalleePerfs = [_, _ | _],
            unexpected($pred, "more than one callee at normal")
        )
    ;
        (
            Kind = special_call_and_no_callee,
            CalleeCellStr0 = "special call"
        ;
            Kind = higher_order_call_and_no_callee,
            CalleeCellStr0 = "higher order call"
        ;
            Kind = method_call_and_no_callee,
            CalleeCellStr0 = "method call"
        ;
            Kind = callback_and_no_callee,
            CalleeCellStr0 = "callback"
        ),
        (
            CalleePerfs = [],
            CalleeCellStr = CalleeCellStr0 ++ " (no calls made)"
        ;
            CalleePerfs = [_ | _],
            CalleeCellStr = CalleeCellStr0 ++ " (summary)"
        ),
        CalleeCell = table_cell(td_s(CalleeCellStr)),
        SummaryRowCells = [SourceCell, CalleeCell] ++ SummaryPerfCells,
        SummaryRow = table_row(SummaryRowCells),
        Summarize = Prefs ^ pref_summarize,
        (
            Summarize = summarize_ho_call_sites,
            Rows = [SummaryRow]
        ;
            Summarize = do_not_summarize_ho_call_sites,
            sort_clique_rows_by_preferences(Prefs, CalleePerfs,
                SortedCalleePerfs),
            CalleeRows =
                list.map(
                    clique_call_site_callee_to_row(MaybeCurModuleName,
                        ModuleQual, Prefs, CliquePtr),
                    SortedCalleePerfs),
            Rows = [SummaryRow] ++ CalleeRows
        )
    ).

:- func clique_call_site_callee_to_row(maybe(string), module_qual,
    preferences, clique_ptr, perf_row_data(clique_desc)) = table_row.

clique_call_site_callee_to_row(MaybeCurModuleName, ModuleQual, Prefs,
        CliquePtr, CalleeRowData) = Row :-
    CalleeCliqueDesc = CalleeRowData ^ perf_row_subject,
    CalleeProcCell = clique_desc_to_non_self_link_proc_name_cell(
        MaybeCurModuleName, ModuleQual, Prefs, CalleeCliqueDesc, CliquePtr),
    Fields = Prefs ^ pref_fields,
    perf_table_row(total_columns_meaningful, Fields, CalleeRowData, PerfCells),
    EmptyCell = table_cell(td_s("")),
    Cells = [EmptyCell, CalleeProcCell] ++ PerfCells,
    Row = table_row(Cells).

%---------------------------------------------------------------------------%
%
% Code to display a clique recursion report.
%

:- pred display_report_clique_recursion(preferences::in,
    clique_recursion_report::in, display::out) is det.

display_report_clique_recursion(Prefs, CliqueRecursionReport, Display) :-
    CliqueRecursionReport = clique_recursion_report(CliquePtr, RecursionType,
        _NumProcs),
    Cmd = deep_cmd_clique_recursive_costs(CliquePtr),
    CliquePtr = clique_ptr(CliqueNum),
    Title = string.format("The recursion information for clique %d:",
        [i(CliqueNum)]),

    display_recursion_type(RecursionType, DisplayRecursionType),

    CliqueReportsControls = clique_reports_controls(Prefs, CliquePtr, Cmd),

    MenuRestartQuitControls = cmds_menu_restart_quit(yes(Prefs)),
    Display = display(yes(Title), DisplayRecursionType ++
        [display_paragraph_break, CliqueReportsControls,
         display_paragraph_break, MenuRestartQuitControls]).

:- pred display_recursion_type(recursion_type::in, list(display_item)::out)
    is det.

display_recursion_type(RecursionType, Items) :-
    (
        (
            RecursionType = rt_not_recursive,
            Text = "Clique is non-recursive"
        ;
            RecursionType = rt_mutual_recursion(NumProcs),
            Text = string.format("Mutual recursion between %d procedures",
                [i(NumProcs)])
        ),
        Items = [display_text(Text)]
    ;
        RecursionType = rt_errors(Errors),
        ErrorItems = list.map((func(Text) = display_text(Text)), Errors),
        Items = [display_list(list_class_vertical_no_bullets,
            yes("Unknown, error(s) occured"), ErrorItems)]
    ;
        (
            RecursionType = rt_single(BaseLevel, RecLevel, AvgDepth,
                AvgRecCost, AnyRecCost),
            RowData =
                [{"Base case", BaseLevel},
                {"Recursive case", RecLevel}],
            Text = "Single-recursion:",

            MaxDepthI = float.round_to_int(AvgDepth),
            CostAtDepthFunc =
                ( func(Level) =
                    {string.format("Cost at depth %d:", [i(Level)]),
                        AnyRecCost(Level)}
                ),
            CostAtDepths = [0, 1, 2, float.round_to_int(AvgDepth / 2.0),
                MaxDepthI - 2, MaxDepthI - 1, MaxDepthI],
            ExtraTableRows0 =
                [{"Average recursion depth:", AvgDepth},
                {"Average recursive call cost (excluding the call itself):",
                    AvgRecCost}] ++
                list.map(CostAtDepthFunc, CostAtDepths),
            ExtraTableRows = list.map(
                ( func({Label, Value}) = table_row(
                    [table_cell(td_s(Label)), table_cell(td_f(Value))])
                ), ExtraTableRows0)
        ;
            RecursionType = rt_divide_and_conquer(BaseLevel, RecLevel),
            RowData =
                [{"Base case", BaseLevel},
                {"Doubly-recursive case", RecLevel}],
            Text = "Double-recursion (Probably Divide and Conquer):",

            ExtraTableRows = []
        ;
            RecursionType = rt_other(Levels),
            RowData = list.map(
                ( func(Level) = {Label, Level} :-
                    Label = string.format("Case for %d recursive calls",
                        [i(Level ^ rlr_level)])
                ), Levels),
            Text = "Unknown recursion type:",

            ExtraTableRows = []
        ),
        Rows = list.map(make_recursion_table_row, RowData),

        ExtraTable = display_table(
            table(table_class_do_not_box, 2, no, ExtraTableRows)),

        NameClassGroupFunc =
            ( func({Name, Class}) =
                table_header_group(table_header_group_single(td_s(Name)),
                    Class, column_do_not_colour)
            ),
        NamesClasses =
            [{"Recursion type", table_column_class_field_name},
            {"Exec count", table_column_class_number},
            {"Non recursive calls per-call cost",
                table_column_class_callseqs},
            {"Recursive calls per-call cost (ex children)",
                table_column_class_callseqs}],
        Header = table_header(list.map(NameClassGroupFunc, NamesClasses)),
        Table = display_table(
            table(table_class_box_if_pref, 4, yes(Header), Rows)),
        Description = display_text(Text),
        Items = [Description,
            display_paragraph_break, Table,
            display_paragraph_break, ExtraTable]
    ).

:- func make_recursion_table_row({string, recursion_level_report}) = table_row.

make_recursion_table_row({Label, Report}) =
    table_row([table_cell(td_s(Label)),
        table_cell(td_i(Report ^ rlr_calls)),
        table_cell(td_f(Report ^ rlr_non_rec_calls_cost)),
        table_cell(td_f(Report ^ rlr_rec_calls_ex_chld_cost))]).

:- pred display_report_recursion_types_frequency(preferences::in,
    recursion_types_frequency_report::in, display::out) is det.

display_report_recursion_types_frequency(Prefs, Report, Display) :-
    Cmd = deep_cmd_recursion_types_frequency,
    Report = recursion_types_frequency_report(Histogram0),

    Title = "Frequencies of recognized recursion types",

    % Build the table.
    RecursionTypeLink = deep_link(Cmd,
        yes(Prefs ^ pref_criteria := by_context),
        attr_str([], "Recursion Type"), link_class_link),
    RecursionTypeHeaderGroup = make_single_table_header_group(
        td_l(RecursionTypeLink), table_column_class_no_class,
        column_do_not_colour),
    FreqHeaderGroup = make_single_table_header_group(
        td_s("Frequency"), table_column_class_no_class,
        column_do_not_colour),
    PercentageHeaderGroup = make_single_table_header_group(
        td_s("Percentage"), table_column_class_no_class,
        column_do_not_colour),
    perf_table_header(total_columns_meaningful, Prefs,
        override_order_criteria_header_data(Cmd), PerfHeaderGroups),
    AllHeaderGroups = [RecursionTypeHeaderGroup, FreqHeaderGroup,
        PercentageHeaderGroup] ++ PerfHeaderGroups,
    header_groups_to_header(AllHeaderGroups, NumColumns, Header),

    Histogram1 = map.to_assoc_list(Histogram0),
    sort_recursion_types_by_preferences(Prefs, Histogram1, Histogram),
    list.map(display_report_rec_type_freq_rows(Prefs, NumColumns),
        Histogram, Rowss),
    list.condense(Rowss, Rows),
    RecursionTypesTable = display_table(table(table_class_box_if_pref,
        NumColumns, yes(Header), Rows)),

    ModuleQualControls = module_qual_controls(Prefs, Cmd),
    FieldControls = field_controls(Prefs, Cmd),
    FormatControls = format_controls(Prefs, Cmd),
    MenuRestartQuitControls = cmds_menu_restart_quit(yes(Prefs)),

    Display = display(yes(Title), [RecursionTypesTable,
        display_paragraph_break, ModuleQualControls,
        display_paragraph_break, FieldControls,
        display_paragraph_break, FormatControls,
        display_paragraph_break, MenuRestartQuitControls]).

:- pred sort_recursion_types_by_preferences(preferences::in,
    assoc_list(recursion_type_simple, recursion_type_freq_data)::in,
    assoc_list(recursion_type_simple, recursion_type_freq_data)::out) is det.

sort_recursion_types_by_preferences(Prefs, !RecursionTypes) :-
    OrderCriteria = Prefs ^ pref_criteria,
    (
        ( OrderCriteria = by_context
        ; OrderCriteria = by_name
        ),
        % Sort by the type of recursion.
        list.sort(compare_recursion_type_row_by_rec_type, !RecursionTypes)
    ;
        OrderCriteria = by_cost(CostKind, InclDesc, Scope),
        list.sort(
            compare_rec_type_row_datas_by_cost(CostKind, InclDesc, Scope),
            !RecursionTypes),
        % We want the most expensive rows to appear first.
        list.reverse(!RecursionTypes)
    ).

:- pred sort_recursion_type_procs_by_preferences(preferences::in,
    list(recursion_type_proc_freq_data)::in,
    list(recursion_type_proc_freq_data)::out) is det.

sort_recursion_type_procs_by_preferences(Prefs, !RecursionTypeProcs) :-
    OrderCriteria = Prefs ^ pref_criteria,
    (
        ( OrderCriteria = by_context
        ; OrderCriteria = by_name
        ),
        % Since in this case we sort recursion type rows by their recursion
        % type It makes sense to sort the procs within each type by their
        % frequency.
        sort(compare_recursion_proc_row_by_frequency_rev, !RecursionTypeProcs)
    ;
        OrderCriteria = by_cost(CostKind, InclDesc, Scope),
        sort(compare_rec_proc_row_datas_by_cost(CostKind, InclDesc, Scope),
            !RecursionTypeProcs),

        % We want the most expensive rows to appear first.
        reverse(!RecursionTypeProcs)
    ).

:- pred compare_recursion_type_row_by_rec_type(
    pair(recursion_type_simple, T)::in,
    pair(recursion_type_simple, T)::in, comparison_result::out) is det.

compare_recursion_type_row_by_rec_type(RTA - _, RTB - _, Result) :-
    compare(Result, RTA, RTB).

:- pred compare_recursion_proc_row_by_frequency_rev(
    recursion_type_proc_freq_data::in, recursion_type_proc_freq_data::in,
    comparison_result::out) is det.

compare_recursion_proc_row_by_frequency_rev(
        recursion_type_proc_freq_data(FreqA, _, _),
        recursion_type_proc_freq_data(FreqB, _, _), Result) :-
    % The reverse is so that entries with larger frequencies are listed first.
    compare(Result, FreqB, FreqA).

:- pred compare_rec_type_row_datas_by_cost(cost_kind::in,
    include_descendants::in, measurement_scope::in,
    pair(T, recursion_type_freq_data)::in,
    pair(T, recursion_type_freq_data)::in,
    comparison_result::out) is det.

compare_rec_type_row_datas_by_cost(CostKind, IncDesc, Scope, _ - DataA,
        _ - DataB, Result) :-
    MaybePerfA = DataA ^ rtfd_maybe_summary,
    MaybePerfB = DataB ^ rtfd_maybe_summary,
    (
        MaybePerfA = yes(PerfA),
        MaybePerfB = yes(PerfB),
        compare_perf_row_datas_by_cost(CostKind, IncDesc, Scope,
            PerfA, PerfB, Result)
    ;
        MaybePerfA = yes(_),
        MaybePerfB = no,
        Result = (>)
    ;
        MaybePerfA = no,
        MaybePerfB = yes(_),
        Result = (=)
    ;
        MaybePerfA = no,
        MaybePerfB = no,
        Result = (=)
    ).

:- pred compare_rec_proc_row_datas_by_cost(cost_kind::in,
    include_descendants::in, measurement_scope::in,
    recursion_type_proc_freq_data::in, recursion_type_proc_freq_data::in,
    comparison_result::out) is det.

compare_rec_proc_row_datas_by_cost(CostKind, InclDesc, Scope,
        recursion_type_proc_freq_data(_, _, PerfA),
        recursion_type_proc_freq_data(_, _, PerfB), Result) :-
    compare_perf_row_datas_by_cost(CostKind, InclDesc, Scope, PerfA, PerfB,
        Result).

:- pred display_report_rec_type_freq_rows(preferences::in, int::in,
    pair(recursion_type_simple, recursion_type_freq_data)::in,
    list(table_row)::out) is det.

display_report_rec_type_freq_rows(Prefs, NumColumns, Type - FreqData, Rows) :-
    FreqData = recursion_type_freq_data(Frequency, Percent, MaybeSummary,
        ProcsMap),

    % Make the header row.
    display_report_recursion_type_simple(Type, TypeStr),
    HeaderRow = table_section_header(td_s(TypeStr)),

    % Make the row summarising the recursion type.
    (
        MaybeSummary = yes(Summary),
        perf_table_row(total_columns_meaningful, Prefs ^ pref_fields, Summary,
            SummaryCells)
    ;
        MaybeSummary = no,
        duplicate(NumColumns - 3, table_empty_cell, SummaryCells)
    ),
    Row = table_row([table_cell(td_s("Totals")), table_cell(td_i(Frequency)),
        table_cell(td_p(Percent))] ++ SummaryCells),

    % Make rows for the top proc statics.
    Procs0 = values(ProcsMap),
    sort_recursion_type_procs_by_preferences(Prefs, Procs0, Procs1),
    take_upto(Prefs ^ pref_proc_statics_per_rec_type, Procs1, Procs),
    list.map(display_report_rec_type_proc_rows(Prefs), Procs, ProcRows),

    Rows = [HeaderRow | [Row | ProcRows ++
        [table_separator_row]]].

:- pred display_report_rec_type_proc_rows(preferences::in,
    recursion_type_proc_freq_data::in, table_row::out) is det.

display_report_rec_type_proc_rows(Prefs,
        recursion_type_proc_freq_data(Freq, Percent, Summary), Row) :-
    perf_table_row(total_columns_meaningful, Prefs ^ pref_fields, Summary,
        SummaryCells),
    ProcDesc = Summary ^ perf_row_subject,
    ProcCell = proc_desc_to_proc_name_cell(no, Prefs ^ pref_module_qual, Prefs,
        ProcDesc),
    Row = table_row([ProcCell, table_cell(td_i(Freq)),
        table_cell(td_p(Percent))] ++ SummaryCells).

:- pred display_report_recursion_type_simple(recursion_type_simple::in,
    string::out) is det.

display_report_recursion_type_simple(rts_not_recursive, "Not recursive").
display_report_recursion_type_simple(rts_single, "Single-recursion").
display_report_recursion_type_simple(rts_divide_and_conquer,
        "Divide and conquer").
display_report_recursion_type_simple(rts_mutual_recursion(NumProcs), String) :-
    format("Mutual recursion between %d procs", [i(NumProcs)], String).
display_report_recursion_type_simple(rts_other(Levels), String) :-
    LevelsStr = join_list(", ", list.map(string, set.to_sorted_list(Levels))),
    format("Other recursion with levels: %s", [s(LevelsStr)], String).
display_report_recursion_type_simple(rts_total_error_instances,
        "Total errors").
display_report_recursion_type_simple(rts_error(Error), "Error: " ++ Error).

%---------------------------------------------------------------------------%
%
% Code to display a program_modules report.
%

    % Create a display_report structure for a program_modules report.
    %
:- pred display_report_program_modules(preferences::in,
    program_modules_report::in, display::out) is det.

display_report_program_modules(Prefs, ProgramModulesReport, Display) :-
    ProgramModulesReport = program_modules_report(ModuleRowDatas0),
    Cmd = deep_cmd_program_modules,
    Title = "The modules of the program:",

    ShowInactiveModules = Prefs ^ pref_inactive ^ inactive_modules,
    (
        ShowInactiveModules = inactive_show,
        ModuleRowDatas1 = ModuleRowDatas0
    ;
        ShowInactiveModules = inactive_hide,
        list.filter(active_module, ModuleRowDatas0, ModuleRowDatas1)
    ),

    SortPrefs = avoid_sort_self_and_desc(Prefs),
    sort_module_active_rows_by_preferences(SortPrefs,
        ModuleRowDatas1, ModuleRowDatas),

    % Build the table of all modules.
    SortByNamePrefs = Prefs ^ pref_criteria := by_name,
    ModuleHeaderCell = td_l(deep_link(Cmd, yes(SortByNamePrefs),
        attr_str([], "Module"), link_class_link)),
    RankHeaderGroup =
        make_single_table_header_group(td_s("Rank"),
            table_column_class_ordinal_rank, column_do_not_colour),
    ModuleHeaderGroup =
        make_single_table_header_group(ModuleHeaderCell,
            table_column_class_module_name, column_do_not_colour),
    MakeHeaderData = override_order_criteria_header_data(Cmd),
    perf_table_header(total_columns_not_meaningful, Prefs, MakeHeaderData,
        PerfHeaderGroups),
    AllHeaderGroups =
        [RankHeaderGroup, ModuleHeaderGroup] ++ PerfHeaderGroups,
    header_groups_to_header(AllHeaderGroups, NumColumns, Header),

    list.map_foldl(
        maybe_ranked_subject_perf_table_row(Prefs, ranked,
            total_columns_not_meaningful, module_active_to_module_name_cell),
        ModuleRowDatas, Rows, 1, _),
    Table = table(table_class_box_if_pref, NumColumns, yes(Header), Rows),
    DisplayTable = display_table(Table),

    % Build controls at the bottom of the page.
    InactiveControls = inactive_module_controls(Prefs, Cmd),
    FieldControls = field_controls(Prefs, Cmd),
    FormatControls = format_controls(Prefs, Cmd),
    MenuRestartQuitControls = cmds_menu_restart_quit(yes(Prefs)),

    Display = display(yes(Title),
        [DisplayTable,
        display_paragraph_break, InactiveControls,
        display_paragraph_break, FieldControls,
        display_paragraph_break, FormatControls,
        display_paragraph_break, MenuRestartQuitControls]).

:- pred active_module(perf_row_data(module_active)::in) is semidet.

active_module(ModuleRowData) :-
    ModuleActive = ModuleRowData ^ perf_row_subject,
    ModuleActive ^ ma_is_active = module_is_active.

:- func avoid_sort_self_and_desc(preferences) = preferences.

avoid_sort_self_and_desc(Prefs) = SortPrefs :-
    ( if Prefs ^ pref_criteria = by_cost(CostKind, self_and_desc, Scope) then
        SortPrefs = Prefs ^ pref_criteria := by_cost(CostKind, self, Scope)
    else
        SortPrefs = Prefs
    ).

%---------------------------------------------------------------------------%
%
% Code to display a module report.
%

    % Create a display_report structure for a module report.
    %
:- pred display_report_module(preferences::in, module_report::in, display::out)
    is det.

display_report_module(Prefs, ModuleReport, Display) :-
    ModuleReport = module_report(ModuleName, HaveModuleRep, ProcRowDatas0),
    Cmd = deep_cmd_module(ModuleName),
    Title = string.format("The procedures of module %s:", [s(ModuleName)]),

    ShowInactiveProcs = Prefs ^ pref_inactive ^ inactive_procs,
    (
        ShowInactiveProcs = inactive_show,
        ProcRowDatas1 = ProcRowDatas0
    ;
        ShowInactiveProcs = inactive_hide,
        list.filter(active_proc, ProcRowDatas0, ProcRowDatas1)
    ),

    MaybeCurModuleName = yes(ModuleName),
    ModuleQual = Prefs ^ pref_module_qual,
    sort_proc_active_rows_by_preferences(MaybeCurModuleName, ModuleQual,
        Prefs, ProcRowDatas1, ProcRowDatas),

    % Build the table of all modules.
    SortByNamePrefs = Prefs ^ pref_criteria := by_name,
    ProcHeaderCell = td_l(deep_link(Cmd, yes(SortByNamePrefs),
        attr_str([], "Procedure"), link_class_link)),
    RankHeaderGroup =
        make_single_table_header_group(td_s("Rank"),
            table_column_class_ordinal_rank, column_do_not_colour),
    ProcHeaderGroup =
        make_single_table_header_group(ProcHeaderCell,
            table_column_class_module_name, column_do_not_colour),
    MakeHeaderData = override_order_criteria_header_data(Cmd),
    perf_table_header(total_columns_meaningful, Prefs, MakeHeaderData,
        PerfHeaderGroups),
    AllHeaderGroups =
        [RankHeaderGroup, ProcHeaderGroup] ++ PerfHeaderGroups,
    header_groups_to_header(AllHeaderGroups, NumColumns, Header),

    list.map_foldl(
        maybe_ranked_subject_perf_table_row(Prefs, ranked,
            total_columns_meaningful,
            proc_active_to_proc_name_cell(MaybeCurModuleName, ModuleQual)),
        ProcRowDatas, Rows, 1, _),
    Table = table(table_class_box_if_pref, NumColumns, yes(Header), Rows),
    DisplayTable = display_table(Table),

    % Build controls at the bottom of the page.
    GetterSetterCmd = deep_cmd_module_getter_setters(ModuleName),
    GetterSetterControl = display_link(deep_link(GetterSetterCmd, yes(Prefs),
        attr_str([], "Show field getters and setters"), link_class_link)),
    (
        HaveModuleRep = do_not_have_module_rep,
        ModuleControls =
            [display_paragraph_break, GetterSetterControl]
    ;
        HaveModuleRep = have_module_rep,
        RepCmd = deep_cmd_module_rep(ModuleName),
        RepControl = display_link(deep_link(RepCmd, yes(Prefs),
            attr_str([], "Show module representation"), link_class_link)),
        ModuleControls =
            [display_paragraph_break, GetterSetterControl,
            display_paragraph_break, RepControl]
    ),

    InactiveControls = inactive_proc_controls(Prefs, Cmd),
    FieldControls = field_controls(Prefs, Cmd),
    FormatControls = format_controls(Prefs, Cmd),
    MenuRestartQuitControls = cmds_menu_restart_quit(yes(Prefs)),
    GeneralControls =
        [display_paragraph_break, InactiveControls,
        display_paragraph_break, FieldControls,
        display_paragraph_break, FormatControls,
        display_paragraph_break, MenuRestartQuitControls],

    Display = display(yes(Title),
        [DisplayTable] ++ ModuleControls ++ GeneralControls).

:- pred active_proc(perf_row_data(proc_active)::in) is semidet.

active_proc(ProcRowData) :-
    ProcActive = ProcRowData ^ perf_row_subject,
    ProcActive ^ pa_is_active = proc_is_active.

%---------------------------------------------------------------------------%
%
% Code to display a module_getter_setters report.
%

    % Create a display_report structure for a module_getter_setters report.
    %
:- pred display_report_module_getter_setters(preferences::in,
    module_getter_setters_report::in, display::out) is det.

display_report_module_getter_setters(Prefs, Report, Display) :-
    Report = module_getter_setters_report(ModuleName, GSMap),
    Title = string.format("The getters and setters of module %s:",
        [s(ModuleName)]),
    map.to_assoc_list(GSMap, GSPairs),
    RowLists = list.map(display_data_struct_getter_setters(Prefs, ModuleName),
        GSPairs),
    list.condense(RowLists, Rows),

    SortByNamePrefs = Prefs ^ pref_criteria := by_name,
    FieldNameHeaderCell = td_l(deep_link(Cmd, yes(SortByNamePrefs),
        attr_str([], "FieldName"), link_class_link)),

    RankHeaderGroup = make_single_table_header_group(td_s("Rank"),
        table_column_class_ordinal_rank, column_do_not_colour),
    FieldNameHeaderGroup = make_single_table_header_group(FieldNameHeaderCell,
        table_column_class_field_name, column_do_not_colour),
    Cmd = deep_cmd_module_getter_setters(ModuleName),
    MakeHeaderData = override_order_criteria_header_data(Cmd),
    perf_table_header(total_columns_meaningful, Prefs, MakeHeaderData,
        PerfHeaderGroups),
    header_groups_to_header([RankHeaderGroup, FieldNameHeaderGroup
        | PerfHeaderGroups], NumColumns, Header),

    Table = table(table_class_box_if_pref, NumColumns, yes(Header), Rows),
    TableItem = display_table(Table),

    ModuleCmd = deep_cmd_module(ModuleName),
    ModuleControl = display_link(deep_link(ModuleCmd, yes(Prefs),
        attr_str([], "Show all the procedures of the module"),
        link_class_link)),
    MenuResetQuitControls = cmds_menu_restart_quit(yes(Prefs)),
    Controls =
        [display_paragraph_break, ModuleControl,
        display_paragraph_break, MenuResetQuitControls],

    Display = display(yes(Title), [TableItem] ++ Controls).

:- func display_data_struct_getter_setters(preferences, string,
    pair(data_struct_name, gs_field_map)) = list(table_row).

display_data_struct_getter_setters(Prefs, ModuleName,
        DataStructName - FieldMap) = Rows :-
    DataStructName = data_struct_name(Name),
    Title = string.format("The getters and setters of %s:", [s(Name)]),
    TitleHeader = table_section_header(td_as(attr_str([attr_bold], Title))),
    map.to_assoc_list(FieldMap, FieldPairs0),
    sort_getter_setter_fields(Prefs, FieldPairs0, FieldPairs),
    list.map_foldl(display_field_getter_setters(Prefs, ModuleName),
        FieldPairs, DataRowLists, 1, _),
    list.condense(DataRowLists, DataRows),
    Rows = [table_separator_row, TitleHeader, table_separator_row | DataRows].

:- pred display_field_getter_setters(preferences::in, string::in,
    pair(field_name, gs_field_info)::in, list(table_row)::out,
    int::in, int::out) is det.

display_field_getter_setters(Prefs, ModuleName, FieldName - FieldInfo, Rows,
        !Rank):-
    Fields = Prefs ^ pref_fields,
    FieldName = field_name(Name),
    RankCell = table_cell(td_i(!.Rank)),
    MaybeCurModuleName = yes(ModuleName),
    ModuleQual = Prefs ^ pref_module_qual,
    (
        FieldInfo = gs_field_getter(GetterRowData),
        perf_table_row(total_columns_meaningful, Fields, GetterRowData,
            GetterPerfCells),
        GetterProcDesc = GetterRowData ^ perf_row_subject,
        GetterFieldNameCell = proc_desc_to_proc_name_cell(MaybeCurModuleName,
            ModuleQual, Prefs, GetterProcDesc),
        GetterRow =
            table_row([RankCell, GetterFieldNameCell | GetterPerfCells]),
        Rows = [table_separator_row, GetterRow]
    ;
        FieldInfo = gs_field_setter(SetterRowData),
        perf_table_row(total_columns_meaningful, Fields, SetterRowData,
            SetterPerfCells),
        SetterProcDesc = SetterRowData ^ perf_row_subject,
        SetterFieldNameCell = proc_desc_to_proc_name_cell(MaybeCurModuleName,
            ModuleQual, Prefs, SetterProcDesc),
        SetterRow =
            table_row([RankCell, SetterFieldNameCell | SetterPerfCells]),
        Rows = [table_separator_row, SetterRow]
    ;
        FieldInfo = gs_field_both(GetterRowData, SetterRowData, SumRowData),
        EmptyCell = table_cell(td_s("")),

        perf_table_row(total_columns_meaningful, Fields, SumRowData,
            SumPerfCells),
        SummaryName = string.format("%s summary", [s(Name)]),
        SumFieldNameCell = table_cell(td_s(SummaryName)),
        SumRow = table_row([RankCell, SumFieldNameCell | SumPerfCells]),

        perf_table_row(total_columns_meaningful, Fields, GetterRowData,
            GetterPerfCells),
        GetterProcDesc = GetterRowData ^ perf_row_subject,
        GetterFieldNameCell = proc_desc_to_proc_name_cell(MaybeCurModuleName,
            ModuleQual, Prefs, GetterProcDesc),
        GetterRow =
            table_row([EmptyCell, GetterFieldNameCell | GetterPerfCells]),

        perf_table_row(total_columns_meaningful, Fields, SetterRowData,
            SetterPerfCells),
        SetterProcDesc = SetterRowData ^ perf_row_subject,
        SetterFieldNameCell = proc_desc_to_proc_name_cell(MaybeCurModuleName,
            ModuleQual, Prefs, SetterProcDesc),
        SetterRow =
            table_row([EmptyCell, SetterFieldNameCell | SetterPerfCells]),

        Rows = [table_separator_row, SumRow, GetterRow, SetterRow]
    ),
    !:Rank = !.Rank + 1.

%---------------------------------------------------------------------------%
%
% Code to display a module_rep report.
%

    % Create a display_report structure for a module_rep report.
    %
:- pred display_report_module_rep(preferences::in, module_rep_report::in,
    display::out) is det.

display_report_module_rep(Prefs, Report, Display) :-
    Report = module_rep_report(ModuleName, ModuleRepStr),
    Title = string.format("The representation of module %s:",
        [s(ModuleName)]),

    BodyItem = display_verbatim(ModuleRepStr),

    ModuleCmd = deep_cmd_module(ModuleName),
    ModuleControl = display_link(deep_link(ModuleCmd, yes(Prefs),
        attr_str([], "Show all the procedures of the module"),
        link_class_link)),
    MenuResetQuitControls = cmds_menu_restart_quit(yes(Prefs)),
    Controls =
        [display_paragraph_break, ModuleControl,
        display_paragraph_break, MenuResetQuitControls],

    Display = display(yes(Title), [BodyItem | Controls]).

%---------------------------------------------------------------------------%
%
% Code to display a top procedures report.
%

    % Create a display_report structure for a top_procedures report.
    %
:- pred display_report_top_procs(preferences::in, top_procs_report::in,
    display::out) is det.

display_report_top_procs(Prefs, TopProcsReport, Display) :-
    TopProcsReport = top_procs_report(Ordering, TopProcRowDatas),
    Ordering = report_ordering(DisplayLimit, CostKind, InclDesc, Scope),
    Desc = cost_criteria_to_description(CostKind, InclDesc, Scope),
    Title = "Top procedures " ++ Desc,

    % Build the table of the top procedures.
    Cmd = deep_cmd_top_procs(DisplayLimit, CostKind, InclDesc, Scope),
    MakeHeaderData = override_order_criteria_header_data(Cmd),
    maybe_ranked_proc_table_header(Prefs, ranked, MakeHeaderData,
        NumColumns, Header),

    MaybeCurModuleName = no,
    ModuleQual = Prefs ^ pref_module_qual,
    sort_proc_desc_rows_by_preferences(MaybeCurModuleName, ModuleQual,
        Prefs, TopProcRowDatas, OrderedTopProcRowDatas),
    list.map_foldl(
        maybe_ranked_subject_perf_table_row(Prefs, ranked,
            total_columns_meaningful,
            proc_desc_to_proc_name_cell(no, ModuleQual)),
        OrderedTopProcRowDatas, Rows, 1, _),
    Table = table(table_class_box_if_pref, NumColumns, yes(Header), Rows),
    DisplayTable = display_table(Table),

    % Build controls at the bottom of the page.
    TopProcsControls = top_procs_controls(Prefs,
        DisplayLimit, CostKind, InclDesc, Scope),
    FieldControls = field_controls(Prefs, Cmd),
    FormatControls = format_controls(Prefs, Cmd),
    MenuRestartQuitControls = cmds_menu_restart_quit(yes(Prefs)),

    Display = display(yes(Title),
        [DisplayTable,
        display_paragraph_break, TopProcsControls,
        display_paragraph_break, FieldControls,
        display_paragraph_break, FormatControls,
        display_paragraph_break, MenuRestartQuitControls]).

    % Create a phrase describing how the top procedures may be sorted.
    %
:- func cost_criteria_to_description(cost_kind, include_descendants,
    measurement_scope) = string.

cost_criteria_to_description(CostKind, InclDesc, Scope) = Desc :-
    Desc =
        "ordered by " ++
        incl_desc_to_description(InclDesc) ++ " " ++
        cost_kind_to_description(CostKind) ++ " " ++
        scope_to_description(Scope).

    % Give the short name for what profiling data a field may be measuring.
    %
:- func incl_desc_to_description(include_descendants) = string.

incl_desc_to_description(self) = "self".
incl_desc_to_description(self_and_desc) = "total".

    % Describe the a measurement used by the deep profiler.
    %
:- func cost_kind_to_description(cost_kind) = string.

cost_kind_to_description(cost_calls)    = "number of calls".
cost_kind_to_description(cost_redos)    = "number of redos".
cost_kind_to_description(cost_time)     = "time".
cost_kind_to_description(cost_callseqs) = "call sequence numbers".
cost_kind_to_description(cost_allocs)   = "memory allocations".
cost_kind_to_description(cost_words)    = "words allocated".

    % Describe a scope of profiling data.
    %
:- func scope_to_description(measurement_scope) = string.

scope_to_description(per_call) = "per call".
scope_to_description(overall) = "overall".

%---------------------------------------------------------------------------%
%
% Code to display a procedure report.
%

    % Create a display_report structure for a proc report.
    %
:- pred display_report_proc(deep::in, preferences::in, proc_report::in,
    display::out) is det.

display_report_proc(Deep, Prefs, ProcReport, Display) :-
    ProcReport = proc_report(CallersSummaryRowData, ProcSummaryRowData,
        CallSitePerfs0),
    ProcDesc = ProcSummaryRowData ^ perf_row_subject,
    RefinedName = ProcDesc ^ pdesc_q_refined_name,
    Title = "Summary of procedure " ++ RefinedName,

    PSPtr = ProcDesc ^ pdesc_ps_ptr,
    Cmd = deep_cmd_proc(PSPtr),

    SortByContextPrefs = Prefs ^ pref_criteria := by_context,
    SourceHeaderCell = td_l(deep_link(Cmd, yes(SortByContextPrefs),
        attr_str([], "Source"), link_class_link)),

    SortByNamePrefs = Prefs ^ pref_criteria := by_name,
    ProcHeaderCell = td_l(deep_link(Cmd, yes(SortByNamePrefs),
        attr_str([], "Procedure"), link_class_link)),

    SourceHeaderGroup = make_single_table_header_group(SourceHeaderCell,
        table_column_class_source_context, column_do_not_colour),
    ProcHeaderGroup = make_single_table_header_group(ProcHeaderCell,
        table_column_class_proc, column_do_not_colour),
    MakeHeaderData = override_order_criteria_header_data(Cmd),
    perf_table_header(total_columns_meaningful, Prefs, MakeHeaderData,
        PerfHeaderGroups),
    AllHeaderGroups =
        [SourceHeaderGroup, ProcHeaderGroup] ++ PerfHeaderGroups,
    header_groups_to_header(AllHeaderGroups, NumColumns, Header),

    Fields = Prefs ^ pref_fields,
    CallersCounts = CallersSummaryRowData ^ perf_row_subject,
    CallersSummaryText = format("%d static & %d dynamic call sites",
        [i(CallersCounts ^ cc_static), i(CallersCounts ^ cc_dynamic)]),
    CallersSummaryCell = table_multi_cell(td_s(CallersSummaryText), 2),
    perf_table_row(total_columns_meaningful, Fields, CallersSummaryRowData,
        CallersPerfCells),
    CallersCells = [CallersSummaryCell] ++ CallersPerfCells,
    CallersRow = table_row(CallersCells),

    % We could make SummaryProcCell a link, but the only link that would make
    % sense (and the link that pre-display versions of the deep profiler
    % generated) point back to this page itself, which is not useful and
    % could be considered misleading.
    %
    % SummaryProcCell spans two columns: the ones that contain (1) the context
    % and (2) the callee of each call site in the rows below.
    SummaryProcCell = table_multi_cell(td_s(RefinedName), 2),
    perf_table_row(total_columns_meaningful, Fields, ProcSummaryRowData,
        SummaryPerfCells),
    SummaryCells = [SummaryProcCell] ++ SummaryPerfCells,
    SummaryRow = table_row(SummaryCells),

    MaybeCurModuleName = yes(ProcDesc ^ pdesc_module_name),
    ModuleQual = Prefs ^ pref_module_qual,
    sort_call_sites_by_preferences(MaybeCurModuleName, ModuleQual, Prefs,
        CallSitePerfs0, CallSitePerfs),
    CallSiteRowLists = list.map(
        report_proc_call_site(MaybeCurModuleName, ModuleQual, Prefs),
        CallSitePerfs),
    list.condense(CallSiteRowLists, CallSiteRows),
    DeveloperRows = list.map((func(X) = table_developer_row(X)),
        [table_section_header(td_s(
            "Callers excluding directly-recursive calls")),
        CallersRow, table_separator_row]),
    CommonRows = [SummaryRow, table_separator_row] ++ CallSiteRows,
    AllRows = DeveloperRows ++ CommonRows,
    Table = table(table_class_box_if_pref, NumColumns, yes(Header), AllRows),
    DisplayTable = display_table(Table),

    % Build the controls at the bottom of the page.
    ProcCallersControls = proc_callers_group_controls(Deep, Prefs, Cmd,
        PSPtr, group_by_call_site, default_callers_per_bunch,
        Prefs ^ pref_contour),
    SummarizeControls = summarize_controls(Prefs, Cmd),
    InactiveCallSitesControls = inactive_call_site_controls(Prefs, Cmd),
    ModuleQualControls = module_qual_controls(Prefs, Cmd),
    SortControls = sort_controls(Prefs, Cmd),
    FieldControls = field_controls(Prefs, Cmd),
    FormatControls = format_controls(Prefs, Cmd),
    ProcReportControls = proc_reports_controls(Prefs, PSPtr, Cmd),
    MenuRestartQuitControls = cmds_menu_restart_quit(yes(Prefs)),

    Display = display(yes(Title),
        [DisplayTable,
        display_paragraph_break, ProcCallersControls,
        display_paragraph_break, SummarizeControls,
        display_paragraph_break, InactiveCallSitesControls,
        display_paragraph_break, ModuleQualControls,
        display_paragraph_break, SortControls,
        display_paragraph_break, FieldControls,
        display_paragraph_break, FormatControls,
        display_paragraph_break, ProcReportControls,
        display_paragraph_break, MenuRestartQuitControls]).

:- func default_callers_per_bunch = int.

default_callers_per_bunch = 20.

:- func report_proc_call_site(maybe(string), module_qual, preferences,
    call_site_perf) = list(table_row).

report_proc_call_site(MaybeCurModuleName, ModuleQual, Prefs, CallSitePerf)
        = Rows :-
    CallSitePerf =
        call_site_perf(KindAndCallee, SummaryPerfRowData, SubPerfs0),

    CallSiteDesc = SummaryPerfRowData ^ perf_row_subject,
    ContextCell = call_site_desc_source_cell(CallSiteDesc),

    (
        KindAndCallee = normal_call_and_info(NormalCalleeId),
        NormalCalleeId = normal_callee_id(CalleeDesc, TypeSubstStr),
        CalleeRefinedName = proc_desc_get_refined_id(MaybeCurModuleName,
            ModuleQual, CalleeDesc),
        ( if TypeSubstStr = "" then
            CallSiteStr = CalleeRefinedName
        else
            CallSiteStr = string.format("%s [%s]",
                [s(CalleeRefinedName), s(TypeSubstStr)])
        ),
        CalleePSPtr = CalleeDesc ^ pdesc_ps_ptr,
        CallSiteLinkCmd = deep_cmd_proc(CalleePSPtr),
        CallSiteLink = deep_link(CallSiteLinkCmd, yes(Prefs),
            attr_str([], CallSiteStr), link_class_link),
        CallSiteCell = table_cell(td_l(CallSiteLink)),

        require(unify(SubPerfs0, []),
            "report_proc_call_site: SubPerfs0 != [] for normal call site")
    ;
        (
            KindAndCallee = special_call_and_no_info,
            CallSiteStr = "special call"
        ;
            KindAndCallee = higher_order_call_and_no_info,
            CallSiteStr = "higher order call"
        ;
            KindAndCallee = method_call_and_no_info,
            CallSiteStr = "method call"
        ;
            KindAndCallee = callback_and_no_info,
            CallSiteStr = "callback"
        ),
        CallSiteCell = table_cell(td_s(CallSiteStr))
    ),

    Fields = Prefs ^ pref_fields,
    perf_table_row(total_columns_meaningful, Fields, SummaryPerfRowData,
        SummaryPerfCells),
    SummaryCells = [ContextCell, CallSiteCell] ++ SummaryPerfCells,
    SummaryRow = table_row(SummaryCells),

    Summarize = Prefs ^ pref_summarize,
    (
        Summarize = summarize_ho_call_sites,
        Rows = [SummaryRow]
    ;
        Summarize = do_not_summarize_ho_call_sites,
        sort_proc_desc_rows_by_preferences(MaybeCurModuleName, ModuleQual,
            Prefs, SubPerfs0, SubPerfs),
        SubRows = list.map(
            report_proc_call_site_callee(MaybeCurModuleName, ModuleQual,
                Prefs),
            SubPerfs),
        Rows = [SummaryRow] ++ SubRows
    ).

:- func report_proc_call_site_callee(maybe(string), module_qual,
    preferences, perf_row_data(proc_desc)) = table_row.

report_proc_call_site_callee(MaybeCurModuleName, ModuleQual,
        Prefs, RowData) = Row :-
    Fields = Prefs ^ pref_fields,
    EmptyCell = table_empty_cell,
    ProcDesc = RowData ^ perf_row_subject,
    ProcCell = proc_desc_to_proc_name_cell(MaybeCurModuleName, ModuleQual,
        Prefs, ProcDesc),
    perf_table_row(total_columns_meaningful, Fields, RowData, PerfCells),
    Cells = [EmptyCell, ProcCell] ++ PerfCells,
    Row = table_row(Cells).

%---------------------------------------------------------------------------%
%
% Code to display a procedure callers report.
%

    % Create a display_report structure for a proc_callers report.
    %
:- pred display_report_proc_callers(deep::in, preferences::in,
    proc_callers_report::in, display::out) is det.

display_report_proc_callers(Deep, Prefs0, ProcCallersReport, Display) :-
    ProcCallersReport = proc_callers_report(ProcDesc, CallerRowDatas,
        BunchNum, BunchSize, ContourExcl, MaybeContourWarnMessage),

    (
        MaybeContourWarnMessage = no,
        WarnItems = []
    ;
        MaybeContourWarnMessage = yes(ContourWarnMessage),
        ContourWarnItem = display_text(ContourWarnMessage),
        WarnItems = [display_paragraph_break, ContourWarnItem]
    ),

    RefinedName = ProcDesc ^ pdesc_q_refined_name,

    MaybeCurModuleName = yes(ProcDesc ^ pdesc_module_name),
    ModuleQual = Prefs ^ pref_module_qual,

    % Remember the selected value of contour exclusion.
    Prefs = Prefs0 ^ pref_contour := ContourExcl,

    PSPtr = ProcDesc ^ pdesc_ps_ptr,

    (
        CallerRowDatas = proc_caller_call_sites(CallSiteRowDatas),
        CallerGroups = group_by_call_site,
        Cmd = deep_cmd_proc_callers(PSPtr, CallerGroups, BunchNum, BunchSize,
            ContourExcl),
        Title = "The call sites calling " ++ RefinedName,
        sort_call_site_desc_rows_by_preferences(MaybeCurModuleName, ModuleQual,
            Prefs, CallSiteRowDatas, SortedCallSiteRowDatas),
        select_displayed_rows(SortedCallSiteRowDatas, BunchNum, BunchSize,
            DisplayedCallSiteRowDatas, TotalNumRows, FirstRowNum, LastRowNum,
            DisplayedBunchNum, MaybeFirstAndLastBunchNum),
        list.map_foldl(
            display_caller_call_site(MaybeCurModuleName, ModuleQual, Prefs),
            DisplayedCallSiteRowDatas, Rows, FirstRowNum, AfterLastRowNum),

        SortByContextPrefs = Prefs ^ pref_criteria := by_context,
        SourceHeaderCell = td_l(deep_link(Cmd, yes(SortByContextPrefs),
            attr_str([], "Source"), link_class_link)),
        SortByNamePrefs = Prefs ^ pref_criteria := by_context,
        ProcHeaderCell = td_l(deep_link(Cmd, yes(SortByNamePrefs),
            attr_str([], "In procedure"), link_class_link)),

        SourceHeaderGroup = make_single_table_header_group(SourceHeaderCell,
            table_column_class_source_context, column_do_not_colour),
        ProcHeaderGroup = make_single_table_header_group(ProcHeaderCell,
            table_column_class_proc, column_do_not_colour),
        IdHeaderGroups = [SourceHeaderGroup, ProcHeaderGroup]
    ;
        CallerRowDatas = proc_caller_procedures(ProcRowDatas),
        CallerGroups = group_by_proc,
        Cmd = deep_cmd_proc_callers(PSPtr, CallerGroups, BunchNum, BunchSize,
            ContourExcl),
        Title = "The procedures calling " ++ RefinedName,
        sort_proc_desc_rows_by_preferences(MaybeCurModuleName, ModuleQual,
            Prefs, ProcRowDatas, SortedProcRowDatas),
        select_displayed_rows(SortedProcRowDatas, BunchNum, BunchSize,
            DisplayedProcRowDatas, TotalNumRows, FirstRowNum, LastRowNum,
            DisplayedBunchNum, MaybeFirstAndLastBunchNum),
        list.map_foldl(
            display_caller_proc(MaybeCurModuleName, ModuleQual, Prefs),
            DisplayedProcRowDatas, Rows, FirstRowNum, AfterLastRowNum),

        SortByContextPrefs = Prefs ^ pref_criteria := by_context,
        SourceHeaderCell = td_l(deep_link(Cmd, yes(SortByContextPrefs),
            attr_str([], "Source"), link_class_link)),
        SortByNamePrefs = Prefs ^ pref_criteria := by_context,
        ProcHeaderCell = td_l(deep_link(Cmd, yes(SortByNamePrefs),
            attr_str([], "Procedure"), link_class_link)),

        SourceHeaderGroup = make_single_table_header_group(SourceHeaderCell,
            table_column_class_source_context, column_do_not_colour),
        ProcHeaderGroup = make_single_table_header_group(ProcHeaderCell,
            table_column_class_proc, column_do_not_colour),
        IdHeaderGroups = [SourceHeaderGroup, ProcHeaderGroup]
    ;
        CallerRowDatas = proc_caller_modules(ModuleRowDatas),
        CallerGroups = group_by_module,
        Cmd = deep_cmd_proc_callers(PSPtr, CallerGroups, BunchNum, BunchSize,
            ContourExcl),
        Title = "The modules calling " ++ RefinedName,
        sort_module_name_rows_by_preferences(Prefs, ModuleRowDatas,
            SortedModuleRowDatas),
        select_displayed_rows(SortedModuleRowDatas, BunchNum, BunchSize,
            DisplayedModuleRowDatas, TotalNumRows, FirstRowNum, LastRowNum,
            DisplayedBunchNum, MaybeFirstAndLastBunchNum),
        list.map_foldl(display_caller_module(Prefs),
            DisplayedModuleRowDatas, Rows, FirstRowNum, AfterLastRowNum),

        SortByNamePrefs = Prefs ^ pref_criteria := by_name,
        ModuleHeaderCell = td_l(deep_link(Cmd, yes(SortByNamePrefs),
            attr_str([], "Module"), link_class_link)),

        ModuleHeaderGroup = make_single_table_header_group(ModuleHeaderCell,
            table_column_class_source_context, column_do_not_colour),
        IdHeaderGroups = [ModuleHeaderGroup]
    ;
        CallerRowDatas = proc_caller_cliques(CliqueRowDatas),
        CallerGroups = group_by_clique,
        Cmd = deep_cmd_proc_callers(PSPtr, CallerGroups, BunchNum, BunchSize,
            ContourExcl),
        Title = "The cliques calling " ++ RefinedName,
        sort_clique_rows_by_preferences(Prefs, CliqueRowDatas,
            SortedCliqueRowDatas),
        select_displayed_rows(SortedCliqueRowDatas, BunchNum, BunchSize,
            DisplayedCliqueRowDatas, TotalNumRows, FirstRowNum, LastRowNum,
            DisplayedBunchNum, MaybeFirstAndLastBunchNum),
        list.map_foldl(
            display_caller_clique(MaybeCurModuleName, ModuleQual, Prefs),
            DisplayedCliqueRowDatas, Rows, FirstRowNum, AfterLastRowNum),

        SortByNamePrefs = Prefs ^ pref_criteria := by_name,
        CliqueHeaderCell = td_l(deep_link(Cmd, yes(SortByNamePrefs),
            attr_str([], "Clique"), link_class_link)),

        CliqueHeaderGroup = make_single_table_header_group(CliqueHeaderCell,
            table_column_class_clique, column_do_not_colour),
        MembersHeaderGroup = make_single_table_header_group(td_s("Members"),
            table_column_class_clique, column_do_not_colour),
        IdHeaderGroups = [CliqueHeaderGroup, MembersHeaderGroup]
    ),

    RankHeaderGroup = make_single_table_header_group(td_s("Rank"),
        table_column_class_ordinal_rank, column_do_not_colour),
    MakeHeaderData = override_order_criteria_header_data(Cmd),
    perf_table_header(total_columns_meaningful, Prefs, MakeHeaderData,
        PerfHeaderGroups),
    AllHeaderGroups = [RankHeaderGroup] ++ IdHeaderGroups ++ PerfHeaderGroups,
    header_groups_to_header(AllHeaderGroups, NumColumns, Header),

    (
        MaybeFirstAndLastBunchNum = no,
        Message = "There are none.",
        Display = display(yes(Title), [display_text(Message)])
    ;
        MaybeFirstAndLastBunchNum = yes({FirstBunchNum, LastBunchNum}),
        require(unify(LastRowNum + 1, AfterLastRowNum),
            "display_report_proc_callers: row number mismatch"),
        require((FirstBunchNum =< DisplayedBunchNum),
            "display_report_proc_callers: display bunch number mismatch"),
        require((DisplayedBunchNum =< LastBunchNum),
            "display_report_proc_callers: display bunch number mismatch"),

        ( if FirstBunchNum = LastBunchNum then
            Message = string.format("There are %d:",
                [i(TotalNumRows)]),
            BunchControls = []
        else
            Message = string.format("There are %d, showing %d to %d:",
                [i(TotalNumRows), i(FirstRowNum), i(LastRowNum)]),
            ( if BunchNum > FirstBunchNum then
                BunchControlsFirst = [make_proc_callers_link(Prefs,
                    "First group", PSPtr, CallerGroups, FirstBunchNum,
                    BunchSize, ContourExcl)]
            else
                BunchControlsFirst = []
            ),
            ( if BunchNum - 1 > FirstBunchNum then
                BunchControlsPrev = [make_proc_callers_link(Prefs,
                    "Previous group", PSPtr, CallerGroups, BunchNum - 1,
                    BunchSize, ContourExcl)]
            else
                BunchControlsPrev = []
            ),
            ( if BunchNum + 1 < LastBunchNum then
                BunchControlsNext = [make_proc_callers_link(Prefs,
                    "Next group", PSPtr, CallerGroups, BunchNum + 1,
                    BunchSize, ContourExcl)]
            else
                BunchControlsNext = []
            ),
            ( if BunchNum < LastBunchNum then
                BunchControlsLast = [make_proc_callers_link(Prefs,
                    "Last group", PSPtr, CallerGroups, LastBunchNum,
                    BunchSize, ContourExcl)]
            else
                BunchControlsLast = []
            ),
            BunchControlList = BunchControlsFirst ++ BunchControlsPrev ++
                BunchControlsNext ++ BunchControlsLast,
            BunchControls = [display_paragraph_break,
                display_list(list_class_horizontal_except_title,
                    yes("Show other groups:"), BunchControlList)]
        ),

        ( if
            BunchSize > 5,
            HalfBunchSize = BunchSize / 2,
            HalfBunchSize \= 10,
            HalfBunchSize \= 20,
            HalfBunchSize \= 50,
            HalfBunchSize \= 100
        then
            BunchSizeControlPairsHalf = [(BunchSize / 2) -
                make_proc_callers_link(Prefs,
                    "Halve group size", PSPtr, CallerGroups, BunchNum * 2,
                    BunchSize / 2, ContourExcl)]
        else
            BunchSizeControlPairsHalf = []
        ),
        ( if
            DoubleBunchSize = BunchSize * 2,
            DoubleBunchSize \= 10,
            DoubleBunchSize \= 20,
            DoubleBunchSize \= 50,
            DoubleBunchSize \= 100
        then
            BunchSizeControlPairsDouble = [(BunchSize * 2) -
                make_proc_callers_link(Prefs,
                    "Double group size", PSPtr, CallerGroups, BunchNum / 2,
                    BunchSize * 2, ContourExcl)]
        else
            BunchSizeControlPairsDouble = []
        ),
        ( if
            TotalNumRows > 10,
            BunchSize \= 10
        then
            BunchSizeControlPairs10 = [10 -
                make_proc_callers_link(Prefs,
                    "Set group size to 10", PSPtr, CallerGroups,
                    (BunchNum * BunchSize) / 10, 10, ContourExcl)]
        else
            BunchSizeControlPairs10 = []
        ),
        ( if
            TotalNumRows > 20,
            BunchSize \= 20
        then
            BunchSizeControlPairs20 = [20 -
                make_proc_callers_link(Prefs,
                    "Set group size to 20", PSPtr, CallerGroups,
                    (BunchNum * BunchSize) / 20, 20, ContourExcl)]
        else
            BunchSizeControlPairs20 = []
        ),
        ( if
            TotalNumRows > 50,
            BunchSize \= 50
        then
            BunchSizeControlPairs50 = [50 -
                make_proc_callers_link(Prefs,
                    "Set group size to 50", PSPtr, CallerGroups,
                    (BunchNum * BunchSize) / 50, 50, ContourExcl)]
        else
            BunchSizeControlPairs50 = []
        ),
        ( if
            TotalNumRows > 100,
            BunchSize \= 100
        then
            BunchSizeControlPairs100 = [100 -
                make_proc_callers_link(Prefs,
                    "Set group size to 100", PSPtr, CallerGroups,
                    (BunchNum * BunchSize) / 100, 100, ContourExcl)]
        else
            BunchSizeControlPairs100 = []
        ),
        BunchSizeControlPairs =
            BunchSizeControlPairsHalf ++ BunchSizeControlPairsDouble ++
            BunchSizeControlPairs10 ++ BunchSizeControlPairs20 ++
            BunchSizeControlPairs50 ++ BunchSizeControlPairs100,
        list.sort(BunchSizeControlPairs, SortedBunchSizeControlPairs),
        assoc_list.values(SortedBunchSizeControlPairs, BunchSizeControlList),
        BunchSizeControls = [display_paragraph_break,
            display_list(list_class_horizontal_except_title,
            yes("Change group size:"), BunchSizeControlList)],

        Table = table(table_class_box_if_pref, NumColumns, yes(Header), Rows),
        DisplayTable = display_table(Table),

        % Build the controls at the bottom of the page.
        FirstCmd = deep_cmd_proc_callers(PSPtr, CallerGroups, 1, BunchSize,
            ContourExcl),
        CallerGroupControls = proc_callers_group_controls(Deep, Prefs,
            FirstCmd, PSPtr, CallerGroups, BunchSize, ContourExcl),
        FieldControls = field_controls(Prefs, Cmd),
        FormatControls = format_controls(Prefs, Cmd),
        MenuRestartQuitControls = cmds_menu_restart_quit(yes(Prefs)),

        Display = display(yes(Title),
            [display_text(Message)] ++
            WarnItems ++
            [display_paragraph_break, DisplayTable] ++
            BunchControls ++
            BunchSizeControls ++
            [display_paragraph_break, CallerGroupControls,
            display_paragraph_break, FieldControls,
            display_paragraph_break, FormatControls,
            display_paragraph_break, MenuRestartQuitControls])
    ).

:- pred select_displayed_rows(list(perf_row_data(T))::in, int::in, int::in,
    list(perf_row_data(T))::out, int::out, int::out, int::out,
    int::out, maybe({int, int})::out) is det.

select_displayed_rows(RowDatas, BunchNum, BunchSize, DisplayRowDatas,
        TotalNumRows, FirstRowNum, LastRowNum,
        DisplayedBunchNum, MaybeFirstAndLastBunchNum) :-
    list.length(RowDatas, TotalNumRows),
    NumRowsToDelete = (BunchNum - 1) * BunchSize,
    ( if
        list.drop(NumRowsToDelete, RowDatas, RemainingRowDatasPrime),
        RemainingRowDatasPrime = [_ | _]
    then
        DisplayedBunchNum = BunchNum,
        % We start counting rows at 1, not 0.
        FirstRowNum = NumRowsToDelete + 1,
        RemainingRowDatas = RemainingRowDatasPrime,
        NumRemainingRows = TotalNumRows - NumRowsToDelete
    else
        DisplayedBunchNum = 1,
        FirstRowNum = 1,
        RemainingRowDatas = RowDatas,
        NumRemainingRows = TotalNumRows
    ),
    ( if NumRemainingRows > BunchSize then
        list.take_upto(BunchSize, RemainingRowDatas, DisplayRowDatas)
    else
        DisplayRowDatas = RemainingRowDatas
    ),
    LastRowNum = FirstRowNum - 1 + list.length(DisplayRowDatas),
    ( if TotalNumRows > 0 then
        FirstBunchNum = 1,
        LastBunchNum = (TotalNumRows + BunchSize - 1) / BunchSize,
        MaybeFirstAndLastBunchNum = yes({FirstBunchNum, LastBunchNum})
    else
        MaybeFirstAndLastBunchNum = no
    ).

:- pred display_caller_call_site(maybe(string)::in, module_qual::in,
    preferences::in, perf_row_data(call_site_desc)::in, table_row::out,
    int::in, int::out) is det.

display_caller_call_site(MaybeCurModuleName, ModuleQual, Prefs,
        CallSiteRowData, Row, !RowNum) :-
    RankCell = table_cell(td_i(!.RowNum)),
    !:RowNum = !.RowNum + 1,

    CallSiteDesc = CallSiteRowData ^ perf_row_subject,
    SourceCell = call_site_desc_to_source_cell(CallSiteDesc),
    ProcCell = call_site_desc_to_caller_proc_name_cell(MaybeCurModuleName,
        ModuleQual, Prefs, CallSiteDesc),

    Fields = Prefs ^ pref_fields,
    perf_table_row(total_columns_meaningful, Fields, CallSiteRowData,
        PerfCells),
    Cells = [RankCell, SourceCell, ProcCell] ++ PerfCells,
    Row = table_row(Cells).

:- pred display_caller_proc(maybe(string)::in, module_qual::in,
    preferences::in, perf_row_data(proc_desc)::in, table_row::out,
    int::in, int::out) is det.

display_caller_proc(MaybeCurModuleName, ModuleQual, Prefs, ProcRowData, Row,
        !RowNum) :-
    RankCell = table_cell(td_i(!.RowNum)),
    !:RowNum = !.RowNum + 1,

    ProcDesc = ProcRowData ^ perf_row_subject,
    SourceCell = proc_desc_to_source_cell(ProcDesc),
    ProcCell = proc_desc_to_proc_name_cell(MaybeCurModuleName, ModuleQual,
        Prefs, ProcDesc),

    Fields = Prefs ^ pref_fields,
    perf_table_row(total_columns_meaningful, Fields, ProcRowData, PerfCells),
    Cells = [RankCell, SourceCell, ProcCell] ++ PerfCells,
    Row = table_row(Cells).

:- pred display_caller_module(preferences::in, perf_row_data(string)::in,
    table_row::out, int::in, int::out) is det.

display_caller_module(Prefs, ModuleRowData, Row, !RowNum) :-
    RankCell = table_cell(td_i(!.RowNum)),
    !:RowNum = !.RowNum + 1,

    ModuleName = ModuleRowData ^ perf_row_subject,
    ModuleCell = table_cell(td_s(ModuleName)),

    Fields = Prefs ^ pref_fields,
    perf_table_row(total_columns_meaningful, Fields, ModuleRowData, PerfCells),
    Cells = [RankCell, ModuleCell] ++ PerfCells,
    Row = table_row(Cells).

:- pred display_caller_clique(maybe(string)::in, module_qual::in,
    preferences::in, perf_row_data(clique_desc)::in, table_row::out,
    int::in, int::out) is det.

display_caller_clique(MaybeCurModuleName, ModuleQual, Prefs,
        CliqueRowData, Row, !RowNum) :-
    RankCell = table_cell(td_i(!.RowNum)),
    !:RowNum = !.RowNum + 1,

    CliqueDesc = CliqueRowData ^ perf_row_subject,
    CliqueDesc = clique_desc(CliquePtr, EntryProcDesc, OtherProcDescs),
    CliquePtr = clique_ptr(CliqueNum),

    CliqueLinkCmd = deep_cmd_clique(CliquePtr),
    CliqueText = string.format("clique %d", [i(CliqueNum)]),
    CliqueLink = deep_link(CliqueLinkCmd, yes(Prefs), attr_str([], CliqueText),
        link_class_link),
    CliqueCell = table_cell(td_l(CliqueLink)),

    MembersStrs = list.map(
        proc_desc_get_refined_id(MaybeCurModuleName, ModuleQual),
        [EntryProcDesc | OtherProcDescs]),
    MembersStr = string.join_list(", ", MembersStrs),
    MembersCell = table_cell(td_s(MembersStr)),

    Fields = Prefs ^ pref_fields,
    perf_table_row(total_columns_meaningful, Fields, CliqueRowData, PerfCells),
    Cells = [RankCell, CliqueCell, MembersCell] ++ PerfCells,
    Row = table_row(Cells).

:- func make_proc_callers_link(preferences, string, proc_static_ptr,
    caller_groups, int, int, contour_exclusion) = display_item.

make_proc_callers_link(Prefs, Label, PSPtr, CallerGroups, BunchNum, BunchSize,
        ContourExcl) = Item :-
    Cmd = deep_cmd_proc_callers(PSPtr, CallerGroups, BunchNum, BunchSize,
        ContourExcl),
    Link = deep_link(Cmd, yes(Prefs), attr_str([], Label), link_class_control),
    Item = display_link(Link).

%---------------------------------------------------------------------------%
%
% Code to display procrep_coverage information.
%

:- pred display_report_procrep_coverage_info(preferences::in,
    procrep_coverage_info::in, display::out) is det.

display_report_procrep_coverage_info(Prefs, ProcrepCoverageReport, Display) :-
    ProcrepCoverageReport =
        procrep_coverage_info(PSPtr, Procrep, CoverageArray),
    print_proc_to_strings(get_goal_attribute_det(CoverageArray), Procrep,
        ProcRepStrings),
    string.append_list(list(ProcRepStrings), ProcRepString),
    CoverageInfoItem = display_verbatim(ProcRepString),

    Cmd = deep_cmd_static_procrep_coverage(PSPtr),
    ProcReportControls = proc_reports_controls(Prefs, PSPtr, Cmd),
    MenuResetQuitControls = cmds_menu_restart_quit(yes(Prefs)),
    Controls =
        [display_paragraph_break, ProcReportControls,
        display_paragraph_break, MenuResetQuitControls],

    Title = "Procedure coverage:",
    Display = display(yes(Title), [CoverageInfoItem] ++ Controls).

:- instance goal_annotation(coverage_info) where [
        pred(print_goal_annotation_to_strings/3) is coverage_to_cord_string
    ].

    % Print the coverage information for a goal, this is used by
    % print_proc_to_strings.
    %
:- pred coverage_to_cord_string(var_name_table::in, coverage_info::in,
    cord(cord(string))::out) is det.

coverage_to_cord_string(_, Coverage, singleton(singleton(String))) :-
    (
        Coverage = coverage_unknown,
        String0 = "_ - _"
    ;
        Coverage = coverage_known(Before, After),
        String0 = string.format("%d - %d", [i(Before), i(After)])
    ;
        (
            Coverage = coverage_known_same(Count)
        ;
            Coverage = coverage_known_zero,
            Count = 0
        ),
        String0 = string.format("%d - %d", [i(Count), i(Count)])
    ;
        Coverage = coverage_known_before(Before),
        String0 = string.format("%d - _", [i(Before)])
    ;
        Coverage = coverage_known_after(After),
        String0 = string.format("_ - %d", [i(After)])
    ),
    String = "Coverage: " ++ String0.

%---------------------------------------------------------------------------%
%
% Code to display proc_static and proc_dynamic dumps.
%

    % Create a display_report structure for a proc_static_dump report.
    %
:- pred display_report_proc_static_dump(proc_static_dump_info::in,
    display::out) is det.

display_report_proc_static_dump(ProcStaticDumpInfo, Display) :-
    ProcStaticDumpInfo = proc_static_dump_info(PSPtr, RawName,
        UnQualRefinedName, QualRefinedName, FileName, LineNumber,
        NumCallSites, NumCoveragePoints),
    PSPtr = proc_static_ptr(PSI),
    string.format("Dump of proc_static %d", [i(PSI)], Title),

    Values =
        [("Raw name:"                   - td_s(RawName)),
        ("Unqualified refined name:"    - td_s(UnQualRefinedName)),
        ("Qualified refined name:"      - td_s(QualRefinedName)),
        ("File name:"                   - td_s(FileName)),
        ("Line number:"                 - td_i(LineNumber)),
        ("Number of call sites:"        - td_i(NumCallSites)),
        ("Number of coverage points:"   - td_i(NumCoveragePoints))],

    Rows = list.map(make_labelled_table_row, Values),
    Table = table(table_class_do_not_box, 2, no, Rows),
    Display = display(yes(Title), [display_table(Table)]).

    % Create a display_report structure for a proc_dynamic_dump report.
    %
:- pred display_report_proc_dynamic_dump(deep::in, preferences::in,
    proc_dynamic_dump_info::in, display::out) is det.

display_report_proc_dynamic_dump(_Deep, Prefs, ProcDynamicDumpInfo, Display) :-
    ProcDynamicDumpInfo = proc_dynamic_dump_info(PDPtr, PSPtr, RawName,
        ModuleName, UnQualRefinedName, QualRefinedName, CallSites,
        MaybeCoveragePoints),
    PDPtr = proc_dynamic_ptr(PDI),
    PSPtr = proc_static_ptr(PSI),
    string.format("Dump of proc_dynamic %d", [i(PDI)], Title),

    ProcStaticLink = deep_link(deep_cmd_dump_proc_static(PSPtr), yes(Prefs),
        attr_str([], string.int_to_string(PSI)), link_class_link),
    MainValues =
        [("Proc static:"                - td_l(ProcStaticLink)),
        ("Raw name:"                    - td_s(RawName)),
        ("Module name:"                 - td_s(ModuleName)),
        ("Unqualified refined name:"    - td_s(UnQualRefinedName)),
        ("Qualified refined name:"      - td_s(QualRefinedName))],

    MainRows = list.map(make_labelled_table_row, MainValues),
    MainTable = table(table_class_do_not_box, 2, no, MainRows),
    MainTableItem = display_table(MainTable),

    CallSitesTitle = "Call site dynamics:",
    CallSitesTitleItem = display_heading(CallSitesTitle),

    list.map_foldl(dump_psd_call_site(Prefs), CallSites, CallSitesRowsList,
        counter.init(1), _),
    list.condense(CallSitesRowsList, CallSitesRows),
    CallSitesTable = table(table_class_box_if_pref, 2, no, CallSitesRows),
    CallSitesTableItem = display_table(CallSitesTable),

    (
        MaybeCoveragePoints = yes(CoveragePoints),
        CoveragePointsTitle = "Coverage points:",
        CoveragePointsTitleItem = display_heading(CoveragePointsTitle),

        list.map(format_coverage_point_row, CoveragePoints,
            CoveragePointsRows),
        CoveragePointsTableHeader = table_header([
            table_header_group(table_header_group_single(td_s("Goal Path")),
                table_column_class_no_class, column_do_not_colour),
            table_header_group(table_header_group_single(td_s("Type")),
                table_column_class_no_class, column_do_not_colour),
            table_header_group(table_header_group_single(td_s("Count")),
                table_column_class_number, column_do_not_colour)]),
        CoveragePointsTable = table(table_class_box_if_pref, 3,
            yes(CoveragePointsTableHeader), CoveragePointsRows),
        CoveragePointsTableItem = display_table(CoveragePointsTable),

        CoveragePointsItems =
            [CoveragePointsTitleItem, CoveragePointsTableItem]
    ;
        MaybeCoveragePoints = no,
        CoveragePointsItems = []
    ),

    make_link(link_base(deep_cmd_dynamic_procrep_coverage(PDPtr), yes(Prefs),
            "Dynamic coverage annotated procedure representation"),
        CoverageAnnotatedProcrepItem),
    RelatedReportsList = display_list(list_class_horizontal_except_title,
        yes("Related reports:"), [CoverageAnnotatedProcrepItem]),

    Display = display(yes(Title),
        [MainTableItem, CallSitesTitleItem, CallSitesTableItem] ++
        CoveragePointsItems ++
        [display_paragraph_break, RelatedReportsList]).

:- pred dump_psd_call_site(preferences::in,
    call_site_array_slot::in, list(table_row)::out,
    counter::in, counter::out) is det.

dump_psd_call_site(Prefs, CallSite, Rows, !CallSiteCounter) :-
    counter.allocate(CallSiteNum, !CallSiteCounter),
    CallSiteNumCell = table_cell(td_i(CallSiteNum)),
    (
        CallSite = slot_normal(CSDPtr),
        CSDPtr = call_site_dynamic_ptr(CSDI),
        CSDLink = deep_link(deep_cmd_dump_call_site_dynamic(CSDPtr),
            yes(Prefs), attr_str([], string.int_to_string(CSDI)),
            link_class_link),
        CSDCell = table_cell(td_l(CSDLink)),
        FirstRow = table_row([CallSiteNumCell, CSDCell]),
        Rows = [FirstRow]
    ;
        CallSite = slot_multi(IsZeroed, CSDPtrArray),
        (
            IsZeroed = zeroed,
            IsZeroedStr = "zeroed"
        ;
            IsZeroed = not_zeroed,
            IsZeroedStr = "not_zeroed"
        ),
        array.to_list(CSDPtrArray, CSDPtrs),
        NumCSDPtrs = list.length(CSDPtrs),
        string.format("multi, %d csds (%s)", [i(NumCSDPtrs), s(IsZeroedStr)],
            MultiCellStr),
        MultiCell = table_cell(td_s(MultiCellStr)),
        FirstRow = table_row([CallSiteNumCell, MultiCell]),
        list.map(dump_psd_call_site_multi_entry(Prefs), CSDPtrs, LaterRows),
        Rows = [FirstRow | LaterRows]
    ).

:- pred dump_psd_call_site_multi_entry(preferences::in,
    call_site_dynamic_ptr::in, table_row::out) is det.

dump_psd_call_site_multi_entry(Prefs, CSDPtr, Row) :-
    CSDPtr = call_site_dynamic_ptr(CSDI),
    CSDLink = deep_link(deep_cmd_dump_call_site_dynamic(CSDPtr), yes(Prefs),
        attr_str([], string.int_to_string(CSDI)), link_class_link),
    CSDCell = table_cell(td_l(CSDLink)),
    EmptyCell = table_cell(td_s("")),
    Row = table_row([EmptyCell, CSDCell]).

:- pred format_coverage_point_row(coverage_point::in, table_row::out) is det.

format_coverage_point_row(CoveragePoint, Row) :-
    CoveragePoint = coverage_point(Count, RevGoalPath, CPType),
    GoalPathString = rev_goal_path_to_string(RevGoalPath),
    GoalPathCell = table_cell(td_s(GoalPathString)),
    TypeCell = table_cell(td_s(string(CPType))),
    CountCell = table_cell(td_i(Count)),
    Row = table_row([GoalPathCell, TypeCell, CountCell]).

    % Create a display_report structure for a call_site_static_dump report.
    %
:- pred display_report_call_site_static_dump(preferences::in,
    call_site_static_dump_info::in, display::out) is det.

display_report_call_site_static_dump(Prefs, CallSiteStaticDumpInfo, Display) :-
    CallSiteStaticDumpInfo = call_site_static_dump_info(CSSPtr,
        ContainingPSPtr, SlotNumber, LineNumber, GoalPath, CallSiteKind),
    CSSPtr = call_site_static_ptr(CSSI),
    string.format("Dump of call_site_static %d", [i(CSSI)], Title),
    ContainingPSPtr = proc_static_ptr(ContainingPSI),

    ContainingProcStaticLink = deep_link(
        deep_cmd_dump_proc_static(ContainingPSPtr), yes(Prefs),
        attr_str([], string.int_to_string(ContainingPSI)), link_class_link),

    (
        CallSiteKind = normal_call_and_callee(CalleePSPtr, TypeSpecDesc),
        CalleePSPtr = proc_static_ptr(CalleePSI),
        CalleeDesc0 = "normal, callee " ++ string.int_to_string(CalleePSI),
        ( if TypeSpecDesc = "" then
            CalleeDesc = CalleeDesc0
        else
            CalleeDesc = CalleeDesc0 ++ " typespec " ++ TypeSpecDesc
        ),
        CalleeProcStaticLink = deep_link(
            deep_cmd_dump_proc_static(CalleePSPtr), yes(Prefs),
            attr_str([], CalleeDesc), link_class_link),
        CallSiteKindData = td_l(CalleeProcStaticLink)
    ;
        CallSiteKind = special_call_and_no_callee,
        CallSiteKindData = td_s("special_call")
    ;
        CallSiteKind = higher_order_call_and_no_callee,
        CallSiteKindData = td_s("higher_order_call")
    ;
        CallSiteKind = method_call_and_no_callee,
        CallSiteKindData = td_s("method_call")
    ;
        CallSiteKind = callback_and_no_callee,
        CallSiteKindData = td_s("callback")
    ),

    Values =
        [("Containing proc_static:" - td_l(ContainingProcStaticLink)),
        ("Slot number:"             - td_i(SlotNumber)),
        ("Line number:"             - td_i(LineNumber)),
        ("Goal path:"               - td_s(rev_goal_path_to_string(GoalPath))),
        ("Call site kind:"          - CallSiteKindData)],

    Rows = list.map(make_labelled_table_row, Values),
    Table = table(table_class_do_not_box, 2, no, Rows),
    Display = display(yes(Title), [display_table(Table)]).

    % Create a display_report structure for a call_site_dynamic_dump report.
    %
:- pred display_report_call_site_dynamic_dump(preferences::in,
    call_site_dynamic_dump_info::in, display::out) is det.

display_report_call_site_dynamic_dump(Prefs, CallSiteDynamiccDumpInfo,
        Display) :-
    CallSiteDynamiccDumpInfo = call_site_dynamic_dump_info(CSDPtr,
        CallerPDPtr, CalleePDPtr, RowData),
    CSDPtr = call_site_dynamic_ptr(CSDI),
    string.format("Dump of call_site_dynamic %d", [i(CSDI)], Title),

    CallerPDPtr = proc_dynamic_ptr(CallerPDI),
    CallerProcDynamicLink = deep_link(deep_cmd_dump_proc_dynamic(CallerPDPtr),
        yes(Prefs), attr_str([], string.int_to_string(CallerPDI)),
        link_class_link),

    CalleePDPtr = proc_dynamic_ptr(CalleePDI),
    CalleeProcDynamicLink = deep_link(deep_cmd_dump_proc_dynamic(CalleePDPtr),
        yes(Prefs), attr_str([], string.int_to_string(CalleePDI)),
        link_class_link),

    FirstValues =
        [("Caller proc_dynamic:"    - td_l(CallerProcDynamicLink)),
        ("Callee proc_dynamic:"     - td_l(CalleeProcDynamicLink))],

    FirstRows = list.map(make_labelled_table_row, FirstValues),
    FirstTable = table(table_class_do_not_box, 2, no, FirstRows),

    MakeHeaderData = dummy_order_criteria_header_data,
    maybe_ranked_proc_table_header(Prefs, non_ranked, MakeHeaderData,
        NumColumns, Header),
    maybe_ranked_subject_perf_table_row(Prefs, non_ranked,
        total_columns_meaningful,
        call_site_desc_to_name_path_slot_cell(no, module_qual_always),
        RowData, PerfRow, 1, _),
    PerfTable = table(table_class_box, NumColumns, yes(Header), [PerfRow]),

    Display = display(yes(Title),
        [display_table(FirstTable), display_table(PerfTable)]).

    % Create a display_report structure for a clique_dump report.
    %
:- pred display_report_clique_dump(preferences::in, clique_dump_info::in,
    display::out) is det.

display_report_clique_dump(Prefs, CliqueDumpInfo, Display) :-
    CliqueDumpInfo = clique_dump_info(CliqueDesc, EntryCSDPtr, MemberPDPtrs),
    CliquePtr = CliqueDesc ^ cdesc_clique_ptr,
    CliquePtr = clique_ptr(CliqueNum),
    string.format("Dump of clique %d", [i(CliqueNum)], Title),

    EntryCSDPtr = call_site_dynamic_ptr(EntryCSDI),
    EntryLink = deep_link(deep_cmd_dump_call_site_dynamic(EntryCSDPtr),
        yes(Prefs), attr_str([], string.int_to_string(EntryCSDI)),
        link_class_link),

    list.map_foldl(display_report_clique_dump_member(Prefs), MemberPDPtrs,
        MemberPDLinks, "Member proc_dynamics:", _),

    Values =
        [("Caller call_site_dynamic:" - td_l(EntryLink))] ++
        MemberPDLinks,

    Rows = list.map(make_labelled_table_row, Values),
    Table = table(table_class_do_not_box, 2, no, Rows),

    Display = display(yes(Title), [display_table(Table)]).

:- pred display_report_clique_dump_member(preferences::in,
    proc_dynamic_ptr::in, pair(string, table_data)::out,
    string::in, string::out) is det.

display_report_clique_dump_member(Prefs, PDPtr, Pair, !Label) :-
    PDLabel = !.Label,
    !:Label = "",
    PDPtr = proc_dynamic_ptr(PDI),
    Link = deep_link(deep_cmd_dump_proc_dynamic(PDPtr),
        yes(Prefs), attr_str([], string.int_to_string(PDI)),
        link_class_link),
    PDData = td_l(Link),
    Pair = PDLabel - PDData.

:- pred display_report_call_site_dynamic_var_use(preferences::in,
    call_site_dynamic_var_use_info::in, display::out) is det.

display_report_call_site_dynamic_var_use(_Prefs, CSDVarUseInfo, Display) :-
    CSDVarUseInfo = call_site_dynamic_var_use_info(AverageCost, VarUses),
    AverageCostItem = display_text(
        format("Average Cost: %f", [f(AverageCost)])),

    format_var_uses(VarUses, 1, VarUseRows),
    TextClassHdrGroupFunc =
        ( func({Text, Class}) = table_header_group(
            table_header_group_single(td_s(Text)),
            Class, column_do_not_colour)
        ),
    TextsClasses =
        [{"Argument", table_column_class_ordinal_rank},
        {"Name", table_column_class_no_class},
        {"Type", table_column_class_no_class},
        {"Percent into proc", table_column_class_no_class},
        {"Time into proc (Callseqs)", table_column_class_callseqs}],
    Header = table_header(list.map(TextClassHdrGroupFunc, TextsClasses)),
    Table = table(table_class_box_if_pref, 5, yes(Header), VarUseRows),

    Title = "Dump of var use info",
    Display = display(yes(Title), [AverageCostItem, display_table(Table)]).

:- pred format_var_uses(list(var_use_and_name)::in, int::in,
    list(table_row)::out) is det.

format_var_uses([], _, []).
format_var_uses([VarUse | VarUses], RowNum, [Row | Rows]) :-
    HeaderCell = table_cell(td_s(format("Argument: %i", [i(RowNum)]))),
    VarUse = var_use_and_name(Name,
        var_use_info(CostUntilUse, ProcCost, UseType)),
    NameCell = table_cell(td_s(Name)),
    (
        UseType = var_use_production,
        TypeText = "production"
    ;
        UseType = var_use_consumption,
        TypeText = "consumption"
    ;
        UseType = var_use_other,
        TypeText = "other/unknown"
    ),
    TypeCell = table_cell(td_s(TypeText)),
    PercentCell = table_cell(td_p(percent(CostUntilUse / ProcCost))),
    TimeCell = table_cell(td_f(CostUntilUse)),
    Row = table_row([HeaderCell, NameCell, TypeCell, PercentCell, TimeCell]),
    format_var_uses(VarUses, RowNum + 1, Rows).

%---------------------------------------------------------------------------%
%
% Common column header strings.
%

:- func percent_label = table_data.

percent_label = td_s("%").

:- func override_order_criteria_header_data(cmd, preferences, order_criteria,
    string) = table_data.

override_order_criteria_header_data(Cmd, Prefs0, Criteria, Label)
        = TableData :-
    Criteria0 = Prefs0 ^ pref_criteria,
    Prefs = Prefs0 ^ pref_criteria := Criteria,
    ( if Criteria = Criteria0 then
        % Should we display a simple string to indicate that this link
        % leads back to the same page, or would that be confusing?
        Link = deep_link(Cmd, yes(Prefs), attr_str([], Label),
            link_class_link),
        TableData = td_l(Link)
    else
        Link = deep_link(Cmd, yes(Prefs), attr_str([], Label),
            link_class_link),
        TableData = td_l(Link)
    ).

:- func dummy_order_criteria_header_data(preferences, order_criteria, string)
    = table_data.

dummy_order_criteria_header_data(_, _, Label) = td_s(Label).

%---------------------------------------------------------------------------%
%
% The predicates in this section build
%
% (a) the headers of table columns that describe each aspect of performance,
%     and
% (b) the entries in those columns.
%
% Each pair of predicates should follow the exact same logic when selecting
% what columns to display, and in what order.
%

:- type total_columns_meaning
    --->    total_columns_meaningful
    ;       total_columns_not_meaningful.

    % Convert the performance information in a row data into the cells
    % of a table row according to the preferences.
    %
:- pred perf_table_row(total_columns_meaning::in, fields::in,
    perf_row_data(Subject)::in, list(table_cell)::out) is det.

perf_table_row(TotalsMeaningful, Fields, RowData, PerfCells) :-
    perf_table_row_ports(Fields, RowData, PortCells),
    perf_table_row_time(TotalsMeaningful, Fields, RowData, TimeCells),
    perf_table_row_callseqs(TotalsMeaningful, Fields, RowData, CallSeqsCells),
    perf_table_row_allocs(TotalsMeaningful, Fields, RowData, AllocCells),
    perf_table_row_memory(TotalsMeaningful, Fields, RowData, MemoryCells),
    PerfCells =
        PortCells ++ TimeCells ++ CallSeqsCells ++
        AllocCells ++ MemoryCells.

    % Build the performance group of table headers
    % according to the preferences.
    %
:- pred perf_table_header(total_columns_meaning::in, preferences::in,
    (func(preferences, order_criteria, string) = table_data)::in,
    list(table_header_group)::out) is det.

perf_table_header(TotalsMeaningful, Prefs, MakeHeaderData, HeaderGroups) :-
    perf_table_header_ports(Prefs, MakeHeaderData, PortHeaderGroups),
    perf_table_header_time(TotalsMeaningful, Prefs, MakeHeaderData,
        TimeHeaderGroups),
    perf_table_header_callseqs(TotalsMeaningful, Prefs, MakeHeaderData,
        CallSeqsHeaderGroups),
    perf_table_header_allocs(TotalsMeaningful, Prefs, MakeHeaderData,
        AllocHeaderGroups),
    perf_table_header_memory(TotalsMeaningful, Prefs, MakeHeaderData,
        MemoryHeaderGroups),
    HeaderGroups =
        PortHeaderGroups ++ TimeHeaderGroups ++ CallSeqsHeaderGroups ++
        AllocHeaderGroups ++ MemoryHeaderGroups.

    % Convert the port information in a row data into the cells
    % of a table row according to the preferences.
    %
:- pred perf_table_row_ports(fields::in, perf_row_data(Subject)::in,
    list(table_cell)::out) is det.

perf_table_row_ports(Fields, RowData, PortCells) :-
    PortFields = Fields ^ port_fields,
    (
        PortFields = no_port,
        PortCells = []
    ;
        PortFields = port,
        Calls = RowData ^ perf_row_calls,
        Exits = RowData ^ perf_row_exits,
        Fails = RowData ^ perf_row_fails,
        Redos = RowData ^ perf_row_redos,
        Excps = RowData ^ perf_row_excps,

        CallsCell = table_cell(td_i(Calls)),
        ExitsCell = table_cell(td_i(Exits)),
        FailsCell = table_cell(td_i(Fails)),
        RedosCell = table_cell(td_i(Redos)),
        ExcpsCell = table_cell(td_i(Excps)),

        PortCells = [CallsCell, ExitsCell, FailsCell, RedosCell, ExcpsCell]
    ).

    % Build the ports group of table headers according to the preferences.
    %
:- pred perf_table_header_ports(preferences::in,
    (func(preferences, order_criteria, string) = table_data)::in,
    list(table_header_group)::out) is det.

perf_table_header_ports(Prefs, MakeHeaderData, HeaderGroups) :-
    Fields = Prefs ^ pref_fields,
    PortFields = Fields ^ port_fields,
    (
        PortFields = no_port,
        HeaderGroups = []
    ;
        PortFields = port,

        CallsCrit = by_cost(cost_calls, self, overall),
        RedosCrit = by_cost(cost_redos, self, overall),

        CallsData = MakeHeaderData(Prefs, CallsCrit, "Calls"),
        ExitsData = td_s("Exits"),
        FailsData = td_s("Fails"),
        RedosData = MakeHeaderData(Prefs, RedosCrit, "Redos"),
        ExcpsData = td_s("Excps"),

        SubHeaders = [CallsData, ExitsData, FailsData, RedosData, ExcpsData],

        Title = "Port counts",
        HeaderGroup = make_multi_table_header_group(Title, SubHeaders,
            table_column_class_port_counts, column_colour_if_pref),
        HeaderGroups = [HeaderGroup]
    ).

    % Convert the time information in a row data into the cells
    % of a table row according to the preferences.
    %
:- pred perf_table_row_time(total_columns_meaning::in, fields::in,
    perf_row_data(Subject)::in, list(table_cell)::out) is det.

perf_table_row_time(TotalsMeaningful, Fields, RowData, TimeCells) :-
    TimeFields = Fields ^ time_fields,
    (
        TimeFields = no_time,
        TimeCells = []
    ;
        ( TimeFields = ticks
        ; TimeFields = time
        ; TimeFields = ticks_and_time
        ; TimeFields = time_and_percall
        ; TimeFields = ticks_and_time_and_percall
        ),

        Self = RowData ^ perf_row_self,
        SelfTicks =             Self ^ perf_row_ticks,
        SelfTime =              Self ^ perf_row_time,
        SelfTimePercent =       Self ^ perf_row_time_percent,
        SelfTimePerCall =       Self ^ perf_row_time_percall,
        SelfTicksCell =         table_cell(td_i(SelfTicks)),
        SelfTimeCell =          table_cell(td_t(SelfTime)),
        SelfTimePercentCell =   table_cell(td_p(SelfTimePercent)),
        SelfTimePerCallCell =   table_cell(td_t(SelfTimePerCall)),
        (
            TotalsMeaningful = total_columns_not_meaningful,
            (
                TimeFields = ticks,
                TimeCells =
                    [SelfTicksCell, SelfTimePercentCell]
            ;
                TimeFields = time,
                TimeCells =
                    [SelfTimeCell, SelfTimePercentCell]
            ;
                TimeFields = ticks_and_time,
                TotalsMeaningful = total_columns_not_meaningful,
                TimeCells =
                    [SelfTicksCell, SelfTimeCell, SelfTimePercentCell]
            ;
                TimeFields = time_and_percall,
                TimeCells =
                    [SelfTimeCell, SelfTimePercentCell, SelfTimePerCallCell]
            ;
                TimeFields = ticks_and_time_and_percall,
                TimeCells =
                    [SelfTicksCell, SelfTimeCell,
                    SelfTimePercentCell, SelfTimePerCallCell]
            )
        ;
            TotalsMeaningful = total_columns_meaningful,
            MaybeTotal = RowData ^ perf_row_maybe_total,
            (
                MaybeTotal = yes(Total)
            ;
                MaybeTotal = no,
                unexpected($pred, "no total")
            ),
            TotalTicks =            Total ^ perf_row_ticks,
            TotalTime =             Total ^ perf_row_time,
            TotalTimePercent =      Total ^ perf_row_time_percent,
            TotalTimePerCall =      Total ^ perf_row_time_percall,
            TotalTicksCell =        table_cell(td_i(TotalTicks)),
            TotalTimeCell =         table_cell(td_t(TotalTime)),
            TotalTimePercentCell =  table_cell(td_p(TotalTimePercent)),
            TotalTimePerCallCell =  table_cell(td_t(TotalTimePerCall)),
            (
                TimeFields = ticks,
                TimeCells =
                    [SelfTicksCell, SelfTimePercentCell,
                    TotalTicksCell, TotalTimePercentCell]
            ;
                TimeFields = time,
                TotalsMeaningful = total_columns_meaningful,
                TimeCells =
                    [SelfTimeCell, SelfTimePercentCell,
                    TotalTimeCell, TotalTimePercentCell]
            ;
                TimeFields = ticks_and_time,
                TimeCells =
                    [SelfTicksCell, SelfTimeCell, SelfTimePercentCell,
                    TotalTicksCell, TotalTimeCell, TotalTimePercentCell]
            ;
                TimeFields = time_and_percall,
                TimeCells =
                    [SelfTimeCell, SelfTimePercentCell, SelfTimePerCallCell,
                    TotalTimeCell, TotalTimePercentCell, TotalTimePerCallCell]
            ;
                TimeFields = ticks_and_time_and_percall,
                TimeCells =
                    [SelfTicksCell, SelfTimeCell,
                    SelfTimePercentCell, SelfTimePerCallCell,
                    TotalTicksCell, TotalTimeCell,
                    TotalTimePercentCell, TotalTimePerCallCell]
            )
        )
    ).

    % Build the time group of table headers according to the preferences.
    %
:- pred perf_table_header_time(total_columns_meaning::in, preferences::in,
    (func(preferences, order_criteria, string) = table_data)::in,
    list(table_header_group)::out) is det.

perf_table_header_time(TotalsMeaningful, Prefs, MakeHeaderData,
        HeaderGroups) :-
    Fields = Prefs ^ pref_fields,
    TimeFields = Fields ^ time_fields,

    (
        TimeFields = no_time,
        HeaderGroups = []
    ;
        ( TimeFields = ticks
        ; TimeFields = time
        ; TimeFields = ticks_and_time
        ; TimeFields = time_and_percall
        ; TimeFields = ticks_and_time_and_percall
        ),

        % Ordering by clock ticks is equivalent to ordering by time.
        SelfTicksCrit =         by_cost(cost_time, self, overall),
        SelfTimeCrit =          by_cost(cost_time, self, overall),
        SelfTimePerCallCrit =   by_cost(cost_time, self, per_call),
        TotalTicksCrit =        by_cost(cost_time, self_and_desc, overall),
        TotalTimeCrit =         by_cost(cost_time, self_and_desc, overall),
        TotalTimePerCallCrit =  by_cost(cost_time, self_and_desc, per_call),

        SelfTicksData =         MakeHeaderData(Prefs, SelfTicksCrit,
                                    "Self"),
        SelfTimeData =          MakeHeaderData(Prefs, SelfTimeCrit,
                                    "Time"),
        SelfTimePercentData =   percent_label,
        SelfTimePerCallData =   MakeHeaderData(Prefs, SelfTimePerCallCrit,
                                    "/call"),
        TotalTicksData =        MakeHeaderData(Prefs,
                                    TotalTicksCrit, "Total"),
        TotalTimeData =         MakeHeaderData(Prefs, TotalTimeCrit,
                                    "Time"),
        TotalTimePercentData =  percent_label,
        TotalTimePerCallData =  MakeHeaderData(Prefs, TotalTimePerCallCrit,
                                    "/call"),

        (
            TotalsMeaningful = total_columns_not_meaningful,
            (
                TimeFields = ticks,
                Title = "Clock ticks",
                SubHeaders =
                    [SelfTicksData, SelfTimePercentData]
            ;
                TimeFields = time,
                Title = "Time",
                SubHeaders =
                    [SelfTimeData, SelfTimePercentData]
            ;
                TimeFields = ticks_and_time,
                Title = "Clock ticks and times",
                SubHeaders =
                    [SelfTicksData, SelfTimeData, SelfTimePercentData]
            ;
                TimeFields = time_and_percall,
                Title = "Time",
                SubHeaders =
                    [SelfTimeData, SelfTimePercentData, SelfTimePerCallData]
            ;
                TimeFields = ticks_and_time_and_percall,
                Title = "Clock ticks and times",
                SubHeaders =
                    [SelfTicksData, SelfTimeData,
                    SelfTimePercentData, SelfTimePerCallData]
            )
        ;
            TotalsMeaningful = total_columns_meaningful,
            (
                TimeFields = ticks,
                Title = "Clock ticks",
                SubHeaders =
                    [SelfTicksData, SelfTimePercentData,
                    TotalTicksData, TotalTimePercentData]
            ;
                TimeFields = time,
                Title = "Time",
                SubHeaders =
                    [SelfTimeData, SelfTimePercentData,
                    TotalTimeData, TotalTimePercentData]
            ;
                TimeFields = ticks_and_time,
                Title = "Clock ticks and times",
                SubHeaders =
                    [SelfTicksData, SelfTimeData, SelfTimePercentData,
                    TotalTicksData, TotalTimeData, TotalTimePercentData]
            ;
                TimeFields = time_and_percall,
                Title = "Time",
                SubHeaders =
                    [SelfTimeData, SelfTimePercentData, SelfTimePerCallData,
                    TotalTimeData, TotalTimePercentData, TotalTimePerCallData]
            ;
                TimeFields = ticks_and_time_and_percall,
                Title = "Clock ticks and times",
                SubHeaders =
                    [SelfTicksData, SelfTimeData,
                    SelfTimePercentData, SelfTimePerCallData,
                    TotalTicksData, TotalTimeData,
                    TotalTimePercentData, TotalTimePerCallData]
            )
        ),

        HeaderGroup = make_multi_table_header_group(Title, SubHeaders,
            table_column_class_ticks_and_times, column_colour_if_pref),
        HeaderGroups = [HeaderGroup]
    ).

    % Convert the callseqs information in a row data into the cells
    % of a table row according to the preferences.
    %
:- pred perf_table_row_callseqs(total_columns_meaning::in, fields::in,
    perf_row_data(Subject)::in, list(table_cell)::out) is det.

perf_table_row_callseqs(TotalsMeaningful, Fields, RowData, CallSeqsCells) :-
    CallSeqsFields = Fields ^ callseqs_fields,
    (
        CallSeqsFields = no_callseqs,
        CallSeqsCells = []
    ;
        ( CallSeqsFields = callseqs
        ; CallSeqsFields = callseqs_and_percall
        ),
        Self = RowData ^ perf_row_self,
        SelfCallSeqs =              Self ^ perf_row_callseqs,
        SelfCallSeqsPercent =       Self ^ perf_row_callseqs_percent,
        SelfCallSeqsPerCall =       Self ^ perf_row_callseqs_percall,
        SelfCallSeqsCell =          table_cell(td_i(SelfCallSeqs)),
        SelfCallSeqsPercentCell =   table_cell(td_p(SelfCallSeqsPercent)),
        SelfCallSeqsPerCallCell =   table_cell(td_f(SelfCallSeqsPerCall)),
        (
            TotalsMeaningful = total_columns_not_meaningful,
            (
                CallSeqsFields = callseqs,
                CallSeqsCells =
                    [SelfCallSeqsCell, SelfCallSeqsPercentCell]
            ;
                CallSeqsFields = callseqs_and_percall,
                CallSeqsCells =
                    [SelfCallSeqsCell, SelfCallSeqsPercentCell,
                    SelfCallSeqsPerCallCell]
            )
        ;
            TotalsMeaningful = total_columns_meaningful,
            MaybeTotal = RowData ^ perf_row_maybe_total,
            (
                MaybeTotal = yes(Total)
            ;
                MaybeTotal = no,
                unexpected($pred, "no total")
            ),
            TotalCallSeqs =             Total ^ perf_row_callseqs,
            TotalCallSeqsPercent =      Total ^ perf_row_callseqs_percent,
            TotalCallSeqsPerCall =      Total ^ perf_row_callseqs_percall,
            TotalCallSeqsCell =         table_cell(td_i(TotalCallSeqs)),
            TotalCallSeqsPercentCell =  table_cell(td_p(TotalCallSeqsPercent)),
            TotalCallSeqsPerCallCell =  table_cell(td_f(TotalCallSeqsPerCall)),
            (
                CallSeqsFields = callseqs,
                CallSeqsCells =
                    [SelfCallSeqsCell, SelfCallSeqsPercentCell,
                    TotalCallSeqsCell, TotalCallSeqsPercentCell]
            ;
                CallSeqsFields = callseqs_and_percall,
                CallSeqsCells =
                    [SelfCallSeqsCell, SelfCallSeqsPercentCell,
                    SelfCallSeqsPerCallCell,
                    TotalCallSeqsCell, TotalCallSeqsPercentCell,
                    TotalCallSeqsPerCallCell]
            )
        )
    ).

    % Build the callseqs group of table headers according to the preferences.
    %
:- pred perf_table_header_callseqs(total_columns_meaning::in, preferences::in,
    (func(preferences, order_criteria, string) = table_data)::in,
    list(table_header_group)::out) is det.

perf_table_header_callseqs(TotalsMeaningful, Prefs, MakeHeaderData,
        HeaderGroups) :-
    Fields = Prefs ^ pref_fields,
    CallSeqsFields = Fields ^ callseqs_fields,
    (
        CallSeqsFields = no_callseqs,
        HeaderGroups = []
    ;
        ( CallSeqsFields = callseqs
        ; CallSeqsFields = callseqs_and_percall
        ),

        SelfCrit =          by_cost(cost_callseqs, self, overall),
        SelfPerCallCrit =   by_cost(cost_callseqs, self, per_call),
        TotalCrit =         by_cost(cost_callseqs, self_and_desc, overall),
        TotalPerCallCrit =  by_cost(cost_callseqs, self_and_desc, per_call),

        SelfData =          MakeHeaderData(Prefs, SelfCrit, "Self"),
        SelfPercentData =   percent_label,
        SelfPerCallData =   MakeHeaderData(Prefs, SelfPerCallCrit, "/call"),
        TotalData =         MakeHeaderData(Prefs, TotalCrit, "Total"),
        TotalPercentData =  percent_label,
        TotalPerCallData =  MakeHeaderData(Prefs, TotalPerCallCrit, "/call"),

        (
            TotalsMeaningful = total_columns_not_meaningful,
            (
                CallSeqsFields = callseqs,
                SubHeaders =
                    [SelfData, SelfPercentData]
            ;
                CallSeqsFields = callseqs_and_percall,
                SubHeaders =
                    [SelfData, SelfPercentData, SelfPerCallData]
            )
        ;
            TotalsMeaningful = total_columns_meaningful,
            (
                CallSeqsFields = callseqs,
                SubHeaders =
                    [SelfData, SelfPercentData,
                    TotalData, TotalPercentData]
            ;
                CallSeqsFields = callseqs_and_percall,
                SubHeaders =
                    [SelfData, SelfPercentData, SelfPerCallData,
                    TotalData, TotalPercentData, TotalPerCallData]
            )
        ),
        Title = "Call sequence numbers",
        HeaderGroup = make_multi_table_header_group(Title, SubHeaders,
            table_column_class_callseqs, column_colour_if_pref),
        HeaderGroups = [HeaderGroup]
    ).

    % Convert the allocation information in a row data into the cells
    % of a table row according to the preferences.
    %
:- pred perf_table_row_allocs(total_columns_meaning::in, fields::in,
    perf_row_data(Subject)::in, list(table_cell)::out) is det.

perf_table_row_allocs(TotalsMeaningful, Fields, RowData, AllocCells) :-
    AllocFields = Fields ^ alloc_fields,
    (
        AllocFields = no_alloc,
        AllocCells = []
    ;
        ( AllocFields = alloc
        ; AllocFields = alloc_and_percall
        ),

        Self = RowData ^ perf_row_self,
        SelfAllocs =                Self ^ perf_row_allocs,
        SelfAllocsPercent =         Self ^ perf_row_allocs_percent,
        SelfAllocsPerCall =         Self ^ perf_row_allocs_percall,
        SelfAllocsCell =            table_cell(td_i(SelfAllocs)),
        SelfAllocsPercentCell =     table_cell(td_p(SelfAllocsPercent)),
        SelfAllocsPerCallCell =     table_cell(td_f(SelfAllocsPerCall)),
        (
            TotalsMeaningful = total_columns_not_meaningful,
            (
                AllocFields = alloc,
                AllocCells =
                    [SelfAllocsCell, SelfAllocsPercentCell]
            ;
                AllocFields = alloc_and_percall,
                AllocCells =
                    [SelfAllocsCell, SelfAllocsPercentCell,
                    SelfAllocsPerCallCell]
            )
        ;
            TotalsMeaningful = total_columns_meaningful,
            MaybeTotal = RowData ^ perf_row_maybe_total,
            (
                MaybeTotal = yes(Total)
            ;
                MaybeTotal = no,
                unexpected($pred, "no total")
            ),
            TotalAllocs =               Total ^ perf_row_allocs,
            TotalAllocsPercent =        Total ^ perf_row_allocs_percent,
            TotalAllocsPerCall =        Total ^ perf_row_allocs_percall,
            TotalAllocsCell =           table_cell(td_i(TotalAllocs)),
            TotalAllocsPercentCell =    table_cell(td_p(TotalAllocsPercent)),
            TotalAllocsPerCallCell =    table_cell(td_f(TotalAllocsPerCall)),
            (
                AllocFields = alloc,
                AllocCells =
                    [SelfAllocsCell, SelfAllocsPercentCell,
                    TotalAllocsCell, TotalAllocsPercentCell]
            ;
                AllocFields = alloc_and_percall,
                AllocCells =
                    [SelfAllocsCell, SelfAllocsPercentCell,
                    SelfAllocsPerCallCell,
                    TotalAllocsCell, TotalAllocsPercentCell,
                    TotalAllocsPerCallCell]
            )
        )
    ).

    % Build the allocs group of table headers according to the preferences.
    %
:- pred perf_table_header_allocs(total_columns_meaning::in, preferences::in,
    (func(preferences, order_criteria, string) = table_data)::in,
    list(table_header_group)::out) is det.

perf_table_header_allocs(TotalsMeaningful, Prefs, MakeHeaderData,
        HeaderGroups) :-
    Fields = Prefs ^ pref_fields,
    AllocFields = Fields ^ alloc_fields,
    (
        AllocFields = no_alloc,
        HeaderGroups = []
    ;
        ( AllocFields = alloc
        ; AllocFields = alloc_and_percall
        ),

        SelfCrit =          by_cost(cost_allocs, self, overall),
        SelfPerCallCrit =   by_cost(cost_allocs, self, per_call),
        TotalCrit =         by_cost(cost_allocs, self_and_desc, overall),
        TotalPerCallCrit =  by_cost(cost_allocs, self_and_desc, per_call),

        SelfData =          MakeHeaderData(Prefs, SelfCrit, "Self"),
        SelfPercentData =   percent_label,
        SelfPerCallData =   MakeHeaderData(Prefs, SelfPerCallCrit, "/call"),
        TotalData =         MakeHeaderData(Prefs, TotalCrit, "Total"),
        TotalPercentData =  percent_label,
        TotalPerCallData =  MakeHeaderData(Prefs, TotalPerCallCrit, "/call"),

        (
            AllocFields = alloc,
            (
                TotalsMeaningful = total_columns_not_meaningful,
                SubHeaders =
                    [SelfData, SelfPercentData]
            ;
                TotalsMeaningful = total_columns_meaningful,
                SubHeaders =
                    [SelfData, SelfPercentData,
                    TotalData, TotalPercentData]
            )
        ;
            AllocFields = alloc_and_percall,
            (
                TotalsMeaningful = total_columns_not_meaningful,
                SubHeaders =
                    [SelfData, SelfPercentData, SelfPerCallData]
            ;
                TotalsMeaningful = total_columns_meaningful,
                SubHeaders =
                    [SelfData, SelfPercentData, SelfPerCallData,
                    TotalData, TotalPercentData, TotalPerCallData]
            )
        ),

        Title = "Memory allocations",
        HeaderGroup = make_multi_table_header_group(Title, SubHeaders,
            table_column_class_allocations, column_colour_if_pref),
        HeaderGroups = [HeaderGroup]
    ).

    % Convert the memory information in a row data into the cells
    % of a table row according to the preferences.
    %
:- pred perf_table_row_memory(total_columns_meaning::in, fields::in,
    perf_row_data(Subject)::in, list(table_cell)::out) is det.

perf_table_row_memory(TotalsMeaningful, Fields, RowData, MemoryCells) :-
    MemoryFields = Fields ^ memory_fields,
    (
        MemoryFields = no_memory,
        MemoryCells = []
    ;
        ( MemoryFields = memory(Units)
        ; MemoryFields = memory_and_percall(Units)
        ),

        Self = RowData ^ perf_row_self,
        SelfMem =               Self ^ perf_row_mem,
        SelfMemPerCall =        Self ^ perf_row_mem_percall,
        SelfMemPercent =        Self ^ perf_row_mem_percent,
        SelfMemCell =           table_cell(td_m(SelfMem, Units, ndp_0)),
        SelfMemPerCallCell =    table_cell(td_m(SelfMemPerCall, Units, ndp_2)),
        SelfMemPercentCell =    table_cell(td_p(SelfMemPercent)),
        (
            TotalsMeaningful = total_columns_not_meaningful,
            (
                MemoryFields = memory(_),
                MemoryCells =
                    [SelfMemCell, SelfMemPercentCell]
            ;
                MemoryFields = memory_and_percall(_),
                MemoryCells =
                    [SelfMemCell, SelfMemPercentCell, SelfMemPerCallCell]
            )
        ;
            TotalsMeaningful = total_columns_meaningful,
            MaybeTotal = RowData ^ perf_row_maybe_total,
            (
                MaybeTotal = yes(Total)
            ;
                MaybeTotal = no,
                unexpected($pred, "no total")
            ),
            TotalMem =            Total ^ perf_row_mem,
            TotalMemPerCall =     Total ^ perf_row_mem_percall,
            TotalMemPercent =     Total ^ perf_row_mem_percent,
            TotalMemCell =        table_cell(td_m(TotalMem, Units, ndp_0)),
            TotalMemPerCallCell = table_cell(td_m(TotalMemPerCall, Units,
                                    ndp_2)),
            TotalMemPercentCell = table_cell(td_p(TotalMemPercent)),
            (
                MemoryFields = memory(_),
                MemoryCells =
                    [SelfMemCell, SelfMemPercentCell,
                    TotalMemCell, TotalMemPercentCell]
            ;
                MemoryFields = memory_and_percall(_),
                MemoryCells =
                    [SelfMemCell, SelfMemPercentCell, SelfMemPerCallCell,
                    TotalMemCell, TotalMemPercentCell, TotalMemPerCallCell]
            )
        )
    ).

    % Build the memory group of table headers according to the preferences.
    %
:- pred perf_table_header_memory(total_columns_meaning::in, preferences::in,
    (func(preferences, order_criteria, string) = table_data)::in,
    list(table_header_group)::out) is det.

perf_table_header_memory(TotalsMeaningful, Prefs, MakeHeaderData,
        HeaderGroups) :-
    Fields = Prefs ^ pref_fields,
    MemoryFields = Fields ^ memory_fields,
    (
        MemoryFields = no_memory,
        HeaderGroups = []
    ;
        ( MemoryFields = memory(_)
        ; MemoryFields = memory_and_percall(_)
        ),

        SelfCrit =          by_cost(cost_words, self, overall),
        SelfPerCallCrit =   by_cost(cost_words, self, per_call),
        TotalCrit =         by_cost(cost_words, self_and_desc, overall),
        TotalPerCallCrit =  by_cost(cost_words, self_and_desc, per_call),

        SelfData =          MakeHeaderData(Prefs, SelfCrit, "Self"),
        SelfPercentData =   percent_label,
        SelfPerCallData =   MakeHeaderData(Prefs, SelfPerCallCrit, "/call"),
        TotalData =         MakeHeaderData(Prefs, TotalCrit, "Total"),
        TotalPercentData =  percent_label,
        TotalPerCallData =  MakeHeaderData(Prefs, TotalPerCallCrit, "/call"),

        (
            TotalsMeaningful = total_columns_not_meaningful,
            (
                MemoryFields = memory(Units),
                SubHeaders =
                    [SelfData, SelfPercentData]
            ;
                MemoryFields = memory_and_percall(Units),
                SubHeaders =
                    [SelfData, SelfPercentData, SelfPerCallData]
            )
        ;
            TotalsMeaningful = total_columns_meaningful,
            (
                MemoryFields = memory(Units),
                SubHeaders =
                    [SelfData, SelfPercentData,
                    TotalData, TotalPercentData]
            ;
                MemoryFields = memory_and_percall(Units),
                SubHeaders =
                    [SelfData, SelfPercentData, SelfPerCallData,
                    TotalData, TotalPercentData, TotalPerCallData]
            )
        ),
        (
            Units = units_words,
            Title = "Memory words"
        ;
            Units = units_bytes,
            Title = "Memory bytes"
        ),
        HeaderGroup = make_multi_table_header_group(Title, SubHeaders,
            table_column_class_memory, column_colour_if_pref),
        HeaderGroups = [HeaderGroup]
    ).

%---------------------------------------------------------------------------%

    % Describes whether a table should be ranked or not. This means that
    % each item has an ordinal number associated with it in an initial column
    % labeled "Rank".
    %
:- type ranked
    --->    ranked
    ;       non_ranked.

    % Build a header for a table of procedures.
    %
:- pred maybe_ranked_proc_table_header(preferences::in, ranked::in,
    (func(preferences, order_criteria, string) = table_data)::in,
    int::out, table_header::out) is det.

maybe_ranked_proc_table_header(Prefs, Ranked, MakeHeaderData, NumColumns,
        Header) :-
    (
        Ranked = non_ranked,
        RankedHeaderGroups = []
    ;
        Ranked = ranked,
        RankedHeaderGroup =
            make_single_table_header_group(td_s("Rank"),
                table_column_class_ordinal_rank, column_do_not_colour),
        RankedHeaderGroups = [RankedHeaderGroup]
    ),

    ProcHeaderGroup =
        make_single_table_header_group(td_s("Procedure"),
            table_column_class_proc, column_do_not_colour),
    ProcHeaderGroups = [ProcHeaderGroup],

    perf_table_header(total_columns_meaningful, Prefs, MakeHeaderData,
        PerfHeaderGroups),

    AllHeaderGroups =
        RankedHeaderGroups ++ ProcHeaderGroups ++ PerfHeaderGroups,
    header_groups_to_header(AllHeaderGroups, NumColumns, Header).

    % Convert row data of procedures from the deep profiler into a table row
    % according to the preferences.
    %
:- pred maybe_ranked_subject_perf_table_row(preferences::in, ranked::in,
    total_columns_meaning::in, (func(preferences, Subject) = table_cell)::in,
    perf_row_data(Subject)::in, table_row::out, int::in, int::out) is det.

maybe_ranked_subject_perf_table_row(Prefs, Ranked, TotalsMeaningful,
        SubjectCellFunc, RowData, Row, Rank, Rank + 1) :-
    (
        Ranked = non_ranked,
        RankCells = []
    ;
        Ranked = ranked,
        RankCells = [table_cell(td_i(Rank))]
    ),

    % The name of the procedure,
    SubjectCells = [SubjectCellFunc(Prefs, RowData ^ perf_row_subject)],

    Fields = Prefs ^ pref_fields,
    perf_table_row(TotalsMeaningful, Fields, RowData, PerfCells),

    Cells = RankCells ++ SubjectCells ++ PerfCells,
    Row = table_row(Cells).

%---------------------------------------------------------------------------%
%
% Some utility procedures.
%

:- func call_site_desc_source_cell(call_site_desc) = table_cell.

call_site_desc_source_cell(CallSiteDesc) = Cell :-
    FileName = CallSiteDesc ^ csdesc_file_name,
    LineNumber = CallSiteDesc ^ csdesc_line_number,
    Context = string.format("%s:%d", [s(FileName), i(LineNumber)]),
    Cell = table_cell(td_s(Context)).

:- func call_site_desc_clique_proc_cell(maybe(string), module_qual,
    preferences, ancestor_desc) = table_cell.

call_site_desc_clique_proc_cell(MaybeCurModuleName, ModuleQual,
        Prefs, AncestorDesc) = Cell :-
    AncestorDesc = ancestor_desc(CallerCliquePtr, _CalleeCliquePtr,
        _CalleeProcDesc, CallSiteDesc),
    ContainingProcName =
        call_site_desc_get_caller_refined_id(MaybeCurModuleName, ModuleQual,
            CallSiteDesc),
    Cmd = deep_cmd_clique(CallerCliquePtr),
    Link = deep_link(Cmd, yes(Prefs), attr_str([], ContainingProcName),
        link_class_link),
    Cell = table_cell(td_l(Link)).

%---------------------------------------------------------------------------%
%
% The basic predicates for creating the controls at the bottoms of pages.
%

:- pred make_top_procs_cmd_items(preferences::in, bool::in, display_limit::in,
    cost_kind::in, include_descendants::in, measurement_scope::in,
    maybe(string)::in,
    assoc_list(string,
        (pred(display_limit, cost_kind, include_descendants,
            measurement_scope, cmd)))::
        in(list_skel(pair(ground, (pred(in, in, in, in, out) is det)))),
    display_item::out) is det.

make_top_procs_cmd_items(Prefs, SetPrefs, DisplayLimit, CostKind, InclDesc,
        Scope, MaybeLabel, LabelsCmdMakers, Item) :-
    list.map(
        make_top_procs_cmd_item(Prefs, SetPrefs, DisplayLimit, CostKind,
            InclDesc, Scope),
        LabelsCmdMakers, ControlItemLists),
    list.condense(ControlItemLists, ControlItems),
    Item = display_list(list_class_horizontal, MaybeLabel, ControlItems).

:- pred make_top_procs_cmd_item(preferences::in, bool::in, display_limit::in,
    cost_kind::in, include_descendants::in, measurement_scope::in,
    pair(string,
        (pred(display_limit, cost_kind, include_descendants, measurement_scope,
            cmd)))::
        in(pair(ground, (pred(in, in, in, in, out) is det))),
    list(display_item)::out) is det.

make_top_procs_cmd_item(Prefs0, SetPrefs, DisplayLimit, CostKind, InclDesc,
        Scope, Label - CmdMaker, Items) :-
    Cmd0 = deep_cmd_top_procs(DisplayLimit, CostKind, InclDesc, Scope),
    CmdMaker(DisplayLimit, CostKind, InclDesc, Scope, Cmd),
    ( if Cmd = Cmd0 then
        Items = []
        % We could use this code instead.
        % CurLabel = Label ++ " (current setting)",
        % PseudoLink = pseudo_link(CurLabel, link_class_control),
        % Item = display_pseudo_link(PseudoLink),
        % Items = [Item]
    else
        (
            SetPrefs = no,
            Prefs = Prefs0
        ;
            SetPrefs = yes,
            % By default, sort the top procedures by the same criteria
            % by which they were selected. Users can override this choice
            % later if they wish.
            ( if
                Cmd = deep_cmd_top_procs(_, CmdCostKind, CmdInclDesc,
                    CmdScope)
            then
                Prefs = Prefs0 ^ pref_criteria :=
                    by_cost(CmdCostKind, CmdInclDesc, CmdScope)
            else
                Prefs = Prefs0
            )
        ),
        Link = deep_link(Cmd, yes(Prefs), attr_str([], Label),
            link_class_control),
        Item = display_link(Link),
        Items = [Item]
    ).

:- pred make_first_proc_callers_cmds_item(preferences::in, cmd::in,
    proc_static_ptr::in, caller_groups::in, int::in, contour_exclusion::in,
    maybe(string)::in,
    assoc_list(string,
        (pred(proc_static_ptr, caller_groups, int, contour_exclusion, cmd)))::
        in(list_skel(pair(ground, (pred(in, in, in, in, out) is det)))),
    display_item::out) is det.

make_first_proc_callers_cmds_item(Prefs, Cmd, PSPtr, CallerGroups, BunchSize,
        ContourExcl, MaybeLabel, LabelsCmdMakers, Item) :-
    list.map(
        make_first_proc_callers_cmd_item(Prefs, Cmd, PSPtr, CallerGroups,
            BunchSize, ContourExcl),
        LabelsCmdMakers, ControlItemLists),
    list.condense(ControlItemLists, ControlItems),
    Item = display_list(list_class_horizontal, MaybeLabel, ControlItems).

:- pred make_first_proc_callers_cmd_item(preferences::in, cmd::in,
    proc_static_ptr::in, caller_groups::in, int::in, contour_exclusion::in,
    pair(string,
        (pred(proc_static_ptr, caller_groups, int, contour_exclusion, cmd)))::
        in(pair(ground, (pred(in, in, in, in, out) is det))),
    list(display_item)::out) is det.

make_first_proc_callers_cmd_item(Prefs, Cmd0, PSPtr, CallerGroups, BunchSize,
        ContourExcl, Label - CmdMaker, Items) :-
    % When we just to a different kind of grouping or toggle contour exclusion,
    % we always go to the first bunch of the resulting rows.
    CmdMaker(PSPtr, CallerGroups, BunchSize, ContourExcl, Cmd),
    ( if Cmd = Cmd0 then
        Items = []
        % We could use this code instead.
        % CurLabel = Label ++ " (current setting)",
        % PseudoLink = pseudo_link(CurLabel, link_class_control),
        % Item = display_pseudo_link(PseudoLink),
        % Items = [Item]
    else
        Link = deep_link(Cmd, yes(Prefs), attr_str([], Label),
            link_class_control),
        Item = display_link(Link),
        Items = [Item]
    ).

:- pred make_prefs_controls_item(preferences::in, cmd::in, maybe(string)::in,
    assoc_list(string, (pred(preferences, preferences)))::
        in(list_skel(pair(ground, (pred(in, out) is det)))),
    display_item::out) is det.

make_prefs_controls_item(Prefs0, Cmd, MaybeLabel, LabelsPrefMakers, Item) :-
    list.map(make_prefs_control_item(Prefs0, Cmd), LabelsPrefMakers,
        ControlItemLists),
    list.condense(ControlItemLists, ControlItems),
    Item = display_list(list_class_horizontal, MaybeLabel, ControlItems).

:- pred make_prefs_control_item(preferences::in, cmd::in,
    pair(string, (pred(preferences, preferences)))::
        in(pair(ground, (pred(in, out) is det))),
    list(display_item)::out) is det.

make_prefs_control_item(Prefs0, Cmd, Label - PrefMaker, Items) :-
    PrefMaker(Prefs0, Prefs),
    ( if Prefs = Prefs0 then
        Items = []
        % We could use this code instead.
        % CurLabel = Label ++ " (current setting)",
        % PseudoLink = pseudo_link(CurLabel, link_class_control),
        % Item = display_pseudo_link(PseudoLink),
        % Items = [Item]
    else
        Link = deep_link(Cmd, yes(Prefs), attr_str([], Label),
            link_class_control),
        Item = display_link(Link),
        Items = [Item]
    ).

%---------------------------------------------------------------------------%
%
% Control how the top_procs command selects what is "top".
%

:- func top_procs_controls(preferences, display_limit, cost_kind,
    include_descendants, measurement_scope) = display_item.

top_procs_controls(Prefs, DisplayLimit, CostKind, InclDesc, Scope) =
        ControlsItem :-
    make_top_procs_cmd_items(Prefs, no, DisplayLimit, CostKind, InclDesc,
        Scope, no, top_procs_limit_toggles, LimitControls),
    make_top_procs_cmd_items(Prefs, yes, DisplayLimit, CostKind, InclDesc,
        Scope, no, top_procs_sort_toggles, SortControls),
    make_top_procs_cmd_items(Prefs, no, DisplayLimit, CostKind, InclDesc,
        Scope, no, top_procs_incl_desc_toggles, InclDescControls),
    make_top_procs_cmd_items(Prefs, no, DisplayLimit, CostKind, InclDesc,
        Scope, no, top_procs_scope_toggles, ScopeControls),
    ControlsItem = display_list(list_class_vertical_no_bullets,
        yes("Toggle sorting criteria:"),
        [LimitControls, SortControls, InclDescControls, ScopeControls]).

:- func top_procs_limit_toggles =
    (assoc_list(string,
        (pred(display_limit, cost_kind, include_descendants, measurement_scope,
            cmd)))::
        out(list_skel(pair(ground, (pred(in, in, in, in, out) is det)))))
    is det.

top_procs_limit_toggles = [
    "Top 10" -
        set_top_procs_display_limit(rank_range(1, 10)),
    "Top 100" -
        set_top_procs_display_limit(rank_range(1, 100)),
    "Top 1000" -
        set_top_procs_display_limit(rank_range(1, 1000)),
    "Top 0.01%" -
        set_top_procs_display_limit(threshold_percent(0.01)),
    "Top 0.1%" -
        set_top_procs_display_limit(threshold_percent(0.1)),
    "Top 1%" -
        set_top_procs_display_limit(threshold_percent(1.0))
].

:- func top_procs_sort_toggles =
    (assoc_list(string,
        (pred(display_limit, cost_kind, include_descendants, measurement_scope,
            cmd)))::
        out(list_skel(pair(ground, (pred(in, in, in, in, out) is det)))))
    is det.

top_procs_sort_toggles = [
    "Sort by calls" -
        set_top_procs_sort_criteria(cost_calls),
    "Sort by redos" -
        set_top_procs_sort_criteria(cost_redos),
    "Sort by time" -
        set_top_procs_sort_criteria(cost_time),
    "Sort by call sequence numbers" -
        set_top_procs_sort_criteria(cost_callseqs),
    "Sort by allocs" -
        set_top_procs_sort_criteria(cost_allocs),
    "Sort by words" -
        set_top_procs_sort_criteria(cost_words)
].

:- func top_procs_incl_desc_toggles =
    (assoc_list(string,
        (pred(display_limit, cost_kind, include_descendants, measurement_scope,
            cmd)))::
        out(list_skel(pair(ground, (pred(in, in, in, in, out) is det)))))
    is det.

top_procs_incl_desc_toggles = [
    "Exclude descendants" -
        set_top_procs_incl_desc(self),
    "Include descendants" -
        set_top_procs_incl_desc(self_and_desc)
].

:- func top_procs_scope_toggles =
    (assoc_list(string,
        (pred(display_limit, cost_kind, include_descendants, measurement_scope,
            cmd)))::
        out(list_skel(pair(ground, (pred(in, in, in, in, out) is det)))))
    is det.

top_procs_scope_toggles = [
    "Overall" -
        set_top_procs_scope(overall),
    "Per call" -
        set_top_procs_scope(per_call)
].

:- pred set_top_procs_display_limit(display_limit::in, display_limit::in,
    cost_kind::in, include_descendants::in, measurement_scope::in, cmd::out)
    is det.

set_top_procs_display_limit(DisplayLimit, _DisplayLimit, CostKind, InclDesc,
        Scope, Cmd) :-
    Cmd = deep_cmd_top_procs(DisplayLimit, CostKind, InclDesc, Scope).

:- pred set_top_procs_sort_criteria(cost_kind::in, display_limit::in,
    cost_kind::in, include_descendants::in, measurement_scope::in, cmd::out)
    is det.

set_top_procs_sort_criteria(CostKind, DisplayLimit, _CostKind, InclDesc, Scope,
        Cmd) :-
    Cmd = deep_cmd_top_procs(DisplayLimit, CostKind, InclDesc, Scope).

:- pred set_top_procs_incl_desc(include_descendants::in, display_limit::in,
    cost_kind::in, include_descendants::in, measurement_scope::in, cmd::out)
    is det.

set_top_procs_incl_desc(InclDesc, DisplayLimit, CostKind, _InclDesc, Scope,
        Cmd) :-
    Cmd = deep_cmd_top_procs(DisplayLimit, CostKind, InclDesc, Scope).

:- pred set_top_procs_scope(measurement_scope::in, display_limit::in,
    cost_kind::in, include_descendants::in, measurement_scope::in, cmd::out)
    is det.

set_top_procs_scope(Scope, DisplayLimit, CostKind, InclDesc, _Scope, Cmd) :-
    Cmd = deep_cmd_top_procs(DisplayLimit, CostKind, InclDesc, Scope).

%---------------------------------------------------------------------------%
%
% Control how the proc command displays the procedure's callers.
%

:- func proc_callers_group_controls(deep, preferences, cmd, proc_static_ptr,
    caller_groups, int, contour_exclusion) = display_item.

proc_callers_group_controls(Deep, Prefs, Cmd, PSPtr, CallerGroups,
        BunchSize, ContourExcl) = ControlsItem :-
    make_first_proc_callers_cmds_item(Prefs, Cmd, PSPtr, CallerGroups,
        BunchSize, ContourExcl, no, proc_callers_group_toggles, GroupControls),
    ExcludeFile = Deep ^ exclude_contour_file,
    ( if Cmd = deep_cmd_proc_callers(_, _, _, _, _) then
        ExcludeFile = exclude_file(ExcludeFileName, ExcludeContents),
        (
            ExcludeContents = no_exclude_file,
            List = [GroupControls]
        ;
            ExcludeContents = unreadable_exclude_file(ErrorMsg),
            ContourExclMsg = "Cannot apply contour exclusion:\n"
                ++ ExcludeFileName ++ ": " ++ ErrorMsg,
            ContourExclItem = display_text(ContourExclMsg),
            List = [GroupControls, ContourExclItem]
        ;
            ExcludeContents = readable_exclude_file(_, _),
            make_first_proc_callers_cmds_item(Prefs, Cmd, PSPtr, CallerGroups,
                BunchSize, ContourExcl, no, proc_callers_contour_excl_toggles,
                ContourExclControls),
            List = [GroupControls, ContourExclControls]
        )
    else
        List = [GroupControls]
    ),
    ControlsItem = display_list(list_class_vertical_no_bullets,
        yes("The procedure's callers:"), List).

:- func proc_callers_group_toggles =
    (assoc_list(string,
        (pred(proc_static_ptr, caller_groups, int, contour_exclusion, cmd)))::
        out(list_skel(pair(ground, (pred(in, in, in, in, out) is det)))))
    is det.

proc_callers_group_toggles = [
    "Group by call site" -
        set_proc_callers_caller_groups(group_by_call_site),
    "Group by procedure" -
        set_proc_callers_caller_groups(group_by_proc),
    "Group by module" -
        set_proc_callers_caller_groups(group_by_module),
    "Group by clique" -
        set_proc_callers_caller_groups(group_by_clique)
].

:- func proc_callers_contour_excl_toggles =
    (assoc_list(string,
        (pred(proc_static_ptr, caller_groups, int, contour_exclusion, cmd)))::
        out(list_skel(pair(ground, (pred(in, in, in, in, out) is det)))))
    is det.

proc_callers_contour_excl_toggles = [
    "Apply contour exclusion" -
        set_proc_callers_contour_excl(apply_contour_exclusion),
    "Do not apply contour exclusion" -
        set_proc_callers_contour_excl(do_not_apply_contour_exclusion)
].

:- pred set_proc_callers_caller_groups(caller_groups::in, proc_static_ptr::in,
    caller_groups::in, int::in, contour_exclusion::in, cmd::out) is det.

set_proc_callers_caller_groups(CallerGroups, PSPtr, _CallerGroups, BunchSize,
        ContourExcl, Cmd) :-
    Cmd = deep_cmd_proc_callers(PSPtr, CallerGroups, 1, BunchSize,
        ContourExcl).

:- pred set_proc_callers_contour_excl(contour_exclusion::in,
    proc_static_ptr::in, caller_groups::in, int::in, contour_exclusion::in,
    cmd::out) is det.

set_proc_callers_contour_excl(ContourExcl, PSPtr, CallerGroups, BunchSize,
        _ContourExcl, Cmd) :-
    Cmd = deep_cmd_proc_callers(PSPtr, CallerGroups, 1, BunchSize,
        ContourExcl).

%---------------------------------------------------------------------------%
%
% Control how the rows in procedure displays are sorted.
%

:- func sort_controls(preferences, cmd) = display_item.

sort_controls(Prefs, Cmd) = ControlsItem :-
    make_prefs_controls_item(Prefs, Cmd, no,
        sort_main_toggles, SortMainControls),
    make_prefs_controls_item(Prefs, Cmd, no,
        sort_time_toggles, SortTimeControls),
    make_prefs_controls_item(Prefs, Cmd, no,
        sort_callseqs_toggles, SortCallSeqsControls),
    make_prefs_controls_item(Prefs, Cmd, no,
        sort_allocs_toggles, SortAllocsControls),
    make_prefs_controls_item(Prefs, Cmd, no,
        sort_memory_toggles, SortMemoryControls),
    ControlsItem = display_list(list_class_vertical_no_bullets,
        yes("Toggle sort options:"),
        [SortMainControls, SortTimeControls, SortCallSeqsControls,
        SortAllocsControls, SortMemoryControls]).

:- func sort_main_toggles =
    (assoc_list(string, (pred(preferences, preferences)))::
    out(list_skel(pair(ground, (pred(in, out) is det))))) is det.

sort_main_toggles = [
    "Sort by context" -
        set_sort_criteria(by_context),
    "Sort by name" -
        set_sort_criteria(by_name),
    % For cost_calls and cost_redos, the second and third fields do not matter.
    "Sort by calls" -
        set_sort_criteria(by_cost(cost_calls, self, overall)),
    "Sort by redos" -
        set_sort_criteria(by_cost(cost_redos, self, overall))
].

:- func sort_time_toggles =
    (assoc_list(string, (pred(preferences, preferences)))::
    out(list_skel(pair(ground, (pred(in, out) is det))))) is det.

sort_time_toggles = [
    "Sort by self time" -
        set_sort_criteria(by_cost(cost_time, self, overall)),
    "Sort by total time" -
        set_sort_criteria(by_cost(cost_time, self_and_desc, overall)),
    "Sort by self time per call" -
        set_sort_criteria(by_cost(cost_time, self, per_call)),
    "Sort by total time per call" -
        set_sort_criteria(by_cost(cost_time, self_and_desc, per_call))
].

:- func sort_callseqs_toggles =
    (assoc_list(string, (pred(preferences, preferences)))::
    out(list_skel(pair(ground, (pred(in, out) is det))))) is det.

sort_callseqs_toggles = [
    "Sort by self callseqs" -
        set_sort_criteria(by_cost(cost_callseqs, self, overall)),
    "Sort by total callseqs" -
        set_sort_criteria(by_cost(cost_callseqs, self_and_desc, overall)),
    "Sort by self callseqs per call" -
        set_sort_criteria(by_cost(cost_callseqs, self, per_call)),
    "Sort by total callseqs per call" -
        set_sort_criteria(by_cost(cost_callseqs, self_and_desc, per_call))
].

:- func sort_allocs_toggles =
    (assoc_list(string, (pred(preferences, preferences)))::
    out(list_skel(pair(ground, (pred(in, out) is det))))) is det.

sort_allocs_toggles = [
    "Sort by self allocs" -
        set_sort_criteria(by_cost(cost_allocs, self, overall)),
    "Sort by total allocs" -
        set_sort_criteria(by_cost(cost_allocs, self_and_desc, overall)),
    "Sort by self allocs per call" -
        set_sort_criteria(by_cost(cost_allocs, self, per_call)),
    "Sort by total allocs per call" -
        set_sort_criteria(by_cost(cost_allocs, self_and_desc, per_call))
].

:- func sort_memory_toggles =
    (assoc_list(string, (pred(preferences, preferences)))::
    out(list_skel(pair(ground, (pred(in, out) is det))))) is det.

sort_memory_toggles = [
    "Sort by self memory" -
        set_sort_criteria(by_cost(cost_words, self, overall)),
    "Sort by total memory" -
        set_sort_criteria(by_cost(cost_words, self_and_desc, overall)),
    "Sort by self memory per call" -
        set_sort_criteria(by_cost(cost_words, self, per_call)),
    "Sort by total memory per call" -
        set_sort_criteria(by_cost(cost_words, self_and_desc, per_call))
].

:- pred set_sort_criteria(order_criteria::in,
    preferences::in, preferences::out) is det.

set_sort_criteria(SortCriteria, !Prefs) :-
    !Prefs ^ pref_criteria := SortCriteria.

%---------------------------------------------------------------------------%
%
% Control whether we summarize the info from callees at multi call sites.
%

:- func summarize_controls(preferences, cmd) = display_item.

summarize_controls(Prefs, Cmd) = ControlsItem :-
    make_prefs_controls_item(Prefs, Cmd, no,
        summarize_toggles, SummarizeControls),
    ControlsItem = display_list(list_class_vertical_no_bullets,
        yes("Toggle summarize options:"), [SummarizeControls]).

:- func summarize_toggles =
    (assoc_list(string, (pred(preferences, preferences)))::
    out(list_skel(pair(ground, (pred(in, out) is det))))) is det.

summarize_toggles = [
    "Do not summarize higher order calls" -
        set_summarize(do_not_summarize_ho_call_sites),
    "Summarize higher order calls" -
        set_summarize(summarize_ho_call_sites)
].

:- pred set_summarize(summarize_ho_call_sites::in,
    preferences::in, preferences::out) is det.

set_summarize(Summarize, !Prefs) :-
    !Prefs ^ pref_summarize := Summarize.

%---------------------------------------------------------------------------%
%
% Control whether we display call sites that do not get any calls.
%

:- func module_qual_controls(preferences, cmd) = display_item.

module_qual_controls(Prefs, Cmd) = ControlsItem :-
    make_prefs_controls_item(Prefs, Cmd, no,
        module_qual_toggles, ShowNoCallSitesControls),
    ControlsItem = display_list(list_class_vertical_no_bullets,
        yes("Toggle show no calls site options:"), [ShowNoCallSitesControls]).

:- func module_qual_toggles =
    (assoc_list(string, (pred(preferences, preferences)))::
    out(list_skel(pair(ground, (pred(in, out) is det))))) is det.

module_qual_toggles = [
    "Never module qualify predicate and function names" -
        set_module_qual(module_qual_never),
    "Module qualify predicate and function names only when needed" -
        set_module_qual(module_qual_when_diff),
    "Always module qualify predicate and function names" -
        set_module_qual(module_qual_always)
].

:- pred set_module_qual(module_qual::in,
    preferences::in, preferences::out) is det.

set_module_qual(ModuleQual, !Prefs) :-
    !Prefs ^ pref_module_qual := ModuleQual.

%---------------------------------------------------------------------------%
%
% Control how pages are displayed.
%

:- func format_controls(preferences, cmd) = display_item.

format_controls(Prefs, Cmd) = ControlsItem :-
    make_prefs_controls_item(Prefs, Cmd, no,
        format_toggles, FormatControls),
    ControlsItem = display_list(list_class_vertical_no_bullets,
        yes("Toggle format options:"), [FormatControls]).

:- func format_toggles =
    (assoc_list(string, (pred(preferences, preferences)))::
    out(list_skel(pair(ground, (pred(in, out) is det))))) is det.

format_toggles = [
    "Fade column groups" -
        set_colour_column_groups(do_not_colour_column_groups),
    "Colour column groups" -
        set_colour_column_groups(colour_column_groups),
    "Unbox" -
        set_box_tables(do_not_box_tables),
    "Box" -
        set_box_tables(box_tables)
].

:- pred set_colour_column_groups(colour_column_groups::in,
    preferences::in, preferences::out) is det.

set_colour_column_groups(Colour, !Prefs) :-
    !Prefs ^ pref_colour := Colour.

:- pred set_box_tables(box_tables::in,
    preferences::in, preferences::out) is det.

set_box_tables(Box, !Prefs) :-
    !Prefs ^ pref_box := Box.

%---------------------------------------------------------------------------%
%
% Controls for related procedure and clique reports.
%

:- func proc_reports_controls(preferences, proc_static_ptr, cmd)
    = display_item.

proc_reports_controls(Prefs, Proc, NotCmd) = ControlsItem :-
    Makers = [
        control_maker(deep_cmd_proc(Proc),
            "Procedure", no),
        % XXX: Should procrep_coverage be a developer-only option?
        control_maker(deep_cmd_static_procrep_coverage(Proc),
            "Coverage annotated procedure representation", no),
        control_maker(deep_cmd_dump_proc_static(Proc),
            "Unprocessed proc static data", yes)
    ],
    make_controls(Prefs, NotCmd, Makers, ProcReportControls),
    ControlsItem = display_list(list_class_vertical_no_bullets,
        yes("Related procedure reports:"), ProcReportControls).

:- func clique_reports_controls(preferences, clique_ptr, cmd) = display_item.

clique_reports_controls(Prefs, CliquePtr, NotCmd) = ControlsItem :-
    Makers = [
        control_maker(deep_cmd_clique(CliquePtr),
            "Clique", no),
        control_maker(deep_cmd_clique_recursive_costs(CliquePtr),
            "Clique's recursion information", yes),
        control_maker(deep_cmd_dump_clique(CliquePtr),
            "Unprocessed clique data", yes)
    ],
    make_controls(Prefs, NotCmd, Makers, CliqueReportControls),
    ControlsItem = display_list(list_class_vertical_no_bullets,
        yes("Related clique reports:"), CliqueReportControls).

:- type control_maker
    --->    control_maker(cmd, string, bool).

:- pred make_controls(preferences::in, cmd::in, list(control_maker)::in,
    list(display_item)::out) is det.

make_controls(_Prefs, _NotCmd, [], []).
make_controls(Prefs, NotCmd, [Maker | Makers], Items) :-
    make_controls(Prefs, NotCmd, Makers, TailItems),
    Maker = control_maker(Cmd, Label, Developer),
    ( if Cmd = NotCmd then
        Items = TailItems
    else
        make_control(yes(Prefs), Cmd, Label, Developer, Control),
        Items = [Control | TailItems]
    ).

%---------------------------------------------------------------------------%
%
% Control whether we display inactive modules.
%

:- func inactive_module_controls(preferences, cmd) = display_item.

inactive_module_controls(Prefs, Cmd) = ControlsItem :-
    make_prefs_controls_item(Prefs, Cmd, no,
        inactive_module_toggles, InactiveModuleControls),
    Controls = [InactiveModuleControls],
    ControlsItem = display_list(list_class_vertical_no_bullets,
        yes("Toggle display of inactive modules:"), Controls).

:- func inactive_module_toggles =
    (assoc_list(string, (pred(preferences, preferences)))::
    out(list_skel(pair(ground, (pred(in, out) is det))))) is det.

inactive_module_toggles = [
    "Do not display inactive modules" -
        set_inactive_modules(inactive_hide),
    "Display inactive modules" -
        set_inactive_modules(inactive_show)
].

:- pred set_inactive_modules(inactive_status::in,
    preferences::in, preferences::out) is det.

set_inactive_modules(Status, !Prefs) :-
    !Prefs ^ pref_inactive ^ inactive_modules := Status.

%---------------------------------------------------------------------------%
%
% Control whether we display inactive procedures.
%

:- func inactive_proc_controls(preferences, cmd) = display_item.

inactive_proc_controls(Prefs, Cmd) = ControlsItem :-
    make_prefs_controls_item(Prefs, Cmd, no,
        inactive_proc_toggles, InactiveModuleControls),
    Controls = [InactiveModuleControls],
    ControlsItem = display_list(list_class_vertical_no_bullets,
        yes("Toggle display of inactive procedures:"), Controls).

:- func inactive_proc_toggles =
    (assoc_list(string, (pred(preferences, preferences)))::
    out(list_skel(pair(ground, (pred(in, out) is det))))) is det.

inactive_proc_toggles = [
    "Do not display inactive procedures" -
        set_inactive_procs(inactive_hide),
    "Display inactive procedures" -
        set_inactive_procs(inactive_show)
].

:- pred set_inactive_procs(inactive_status::in,
    preferences::in, preferences::out) is det.

set_inactive_procs(Status, !Prefs) :-
    !Prefs ^ pref_inactive ^ inactive_procs := Status.

%---------------------------------------------------------------------------%
%
% Control whether we display inactive call sites.
%

:- func inactive_call_site_controls(preferences, cmd) = display_item.

inactive_call_site_controls(Prefs, Cmd) = ControlsItem :-
    make_prefs_controls_item(Prefs, Cmd, no,
        inactive_call_site_toggles, InactiveModuleControls),
    Controls = [InactiveModuleControls],
    ControlsItem = display_list(list_class_vertical_no_bullets,
        yes("Toggle display of inactive call_sites:"), Controls).

:- func inactive_call_site_toggles =
    (assoc_list(string, (pred(preferences, preferences)))::
    out(list_skel(pair(ground, (pred(in, out) is det))))) is det.

inactive_call_site_toggles = [
    "Do not display inactive call sites" -
        set_inactive_call_sites(inactive_hide),
    "Display inactive call sites" -
        set_inactive_call_sites(inactive_show)
].

:- pred set_inactive_call_sites(inactive_status::in,
    preferences::in, preferences::out) is det.

set_inactive_call_sites(Status, !Prefs) :-
    !Prefs ^ pref_inactive ^ inactive_call_sites := Status.

%---------------------------------------------------------------------------%
%
% Control how many ancestors we display.
%

:- func ancestor_controls(preferences, cmd) = display_item.

ancestor_controls(Prefs, Cmd) = ControlsItem :-
    MaybeAncestorLimit = Prefs ^ pref_anc,
    (
        MaybeAncestorLimit = no,
        make_prefs_controls_item(Prefs, Cmd, no,
            ancestor_toggles_no, AncestorControls)
    ;
        MaybeAncestorLimit = yes(AncestorLimit),
        make_prefs_controls_item(Prefs, Cmd, no,
            ancestor_toggles_yes(AncestorLimit), AncestorControls)
    ),
    Controls = [AncestorControls],
    ControlsItem = display_list(list_class_vertical_no_bullets,
        yes("Toggle display of ancestors:"), Controls).

:- func ancestor_toggles_no =
    (assoc_list(string, (pred(preferences, preferences)))::
    out(list_skel(pair(ground, (pred(in, out) is det))))) is det.

ancestor_toggles_no = [
    "One ancestor" -
        set_ancestor_limit(yes(1)),
    "Two ancestors" -
        set_ancestor_limit(yes(2)),
    "Three ancestors" -
        set_ancestor_limit(yes(3)),
    "Five ancestors" -
        set_ancestor_limit(yes(5)),
    "Ten ancestors" -
        set_ancestor_limit(yes(10))
].

:- func ancestor_toggles_yes(int::in) =
    (assoc_list(string, (pred(preferences, preferences)))::
    out(list_skel(pair(ground, (pred(in, out) is det))))) is det.

ancestor_toggles_yes(CurrentLimit) = [
    "Halve ancestors" -
        set_ancestor_limit(yes(CurrentLimit // 2)),
    "Remove an ancestor" -
        set_ancestor_limit(yes(CurrentLimit - 1)),
    "Add an ancestor" -
        set_ancestor_limit(yes(CurrentLimit + 1)),
    "Double ancestors" -
        set_ancestor_limit(yes(CurrentLimit * 2)),
    "Unlimited ancestors" -
        set_ancestor_limit(no)
].

:- pred set_ancestor_limit(maybe(int)::in,
    preferences::in, preferences::out) is det.

set_ancestor_limit(MaybeAncestorLimit, !Prefs) :-
    !Prefs ^ pref_anc := MaybeAncestorLimit.

%---------------------------------------------------------------------------%
%
% Control the set of displayed fields.
%

:- func field_controls(preferences, cmd) = display_item.

field_controls(Prefs, Cmd) = ControlsItem :-
    make_prefs_controls_item(Prefs, Cmd, no,
        port_field_toggles, PortControls),
    make_prefs_controls_item(Prefs, Cmd, no,
        time_field_toggles, TimeControls),
    make_prefs_controls_item(Prefs, Cmd, no,
        callseqs_field_toggles, CallSeqsControls),
    make_prefs_controls_item(Prefs, Cmd, no,
        alloc_field_toggles, AllocControls),
    make_prefs_controls_item(Prefs, Cmd, no,
        memory_field_toggles, MemoryControls),
    Controls = [PortControls, TimeControls, CallSeqsControls,
        AllocControls, MemoryControls],
    ControlsItem = display_list(list_class_vertical_no_bullets,
        yes("Toggle displayed fields:"), Controls).

:- func port_field_toggles =
    (assoc_list(string, (pred(preferences, preferences)))::
    out(list_skel(pair(ground, (pred(in, out) is det))))) is det.

port_field_toggles = [
    "No port counts" -
        set_port_fields(no_port),
    "Port counts" -
        set_port_fields(port)
].

:- func time_field_toggles =
    (assoc_list(string, (pred(preferences, preferences)))::
    out(list_skel(pair(ground, (pred(in, out) is det))))) is det.

time_field_toggles = [
    "No time info" -
        set_time_fields(no_time),
    "Ticks" -
        set_time_fields(ticks),
    "Times" -
        set_time_fields(time),
    "Ticks and times" -
        set_time_fields(ticks_and_time),
    "Times and per-call-times" -
        set_time_fields(time_and_percall),
    "Ticks and times and per-call times" -
        set_time_fields(ticks_and_time_and_percall)
].

:- func callseqs_field_toggles =
    (assoc_list(string, (pred(preferences, preferences)))::
    out(list_skel(pair(ground, (pred(in, out) is det))))) is det.

callseqs_field_toggles = [
    "No call sequence numbers" -
        set_callseqs_fields(no_callseqs),
    "Call sequence numbers" -
        set_callseqs_fields(callseqs),
    "Call sequence numbers including per-call" -
        set_callseqs_fields(callseqs_and_percall)
].

:- func alloc_field_toggles =
    (assoc_list(string, (pred(preferences, preferences)))::
    out(list_skel(pair(ground, (pred(in, out) is det))))) is det.

alloc_field_toggles = [
    "No allocation info" -
        set_alloc_fields(no_alloc),
    "Allocations" -
        set_alloc_fields(alloc),
    "Allocations and per-call allocations" -
        set_alloc_fields(alloc_and_percall)
].

:- func memory_field_toggles =
    (assoc_list(string, (pred(preferences, preferences)))::
    out(list_skel(pair(ground, (pred(in, out) is det))))) is det.

memory_field_toggles = [
    "No memory info" -
        set_memory_fields(no_memory),
    "Words" -
        set_memory_fields(memory(units_words)),
    "Bytes" -
        set_memory_fields(memory(units_bytes)),
    "Words and per-call words" -
        set_memory_fields(memory_and_percall(units_words)),
    "Bytes and per-call bytes" -
        set_memory_fields(memory_and_percall(units_bytes))
].

:- pred set_port_fields(port_fields::in,
    preferences::in, preferences::out) is det.

set_port_fields(PortFields, !Prefs) :-
    Fields0 = !.Prefs ^ pref_fields,
    Fields = Fields0 ^ port_fields := PortFields,
    !Prefs ^ pref_fields := Fields.

:- pred set_time_fields(time_fields::in,
    preferences::in, preferences::out) is det.

set_time_fields(TimeFields, !Prefs) :-
    Fields0 = !.Prefs ^ pref_fields,
    Fields = Fields0 ^ time_fields := TimeFields,
    !Prefs ^ pref_fields := Fields.

:- pred set_callseqs_fields(callseqs_fields::in,
    preferences::in, preferences::out) is det.

set_callseqs_fields(CallSeqsFields, !Prefs) :-
    Fields0 = !.Prefs ^ pref_fields,
    Fields = Fields0 ^ callseqs_fields := CallSeqsFields,
    !Prefs ^ pref_fields := Fields.

:- pred set_alloc_fields(alloc_fields::in,
    preferences::in, preferences::out) is det.

set_alloc_fields(AllocFields, !Prefs) :-
    Fields0 = !.Prefs ^ pref_fields,
    Fields = Fields0 ^ alloc_fields := AllocFields,
    !Prefs ^ pref_fields := Fields.

:- pred set_memory_fields(memory_fields::in,
    preferences::in, preferences::out) is det.

set_memory_fields(MemoryFields, !Prefs) :-
    Fields0 = !.Prefs ^ pref_fields,
    Fields = Fields0 ^ memory_fields := MemoryFields,
    !Prefs ^ pref_fields := Fields.

%---------------------------------------------------------------------------%
%
% Links to the basic commands.
%

    % Give the common list of commands seen at the bottom of all deep-profiler
    % displayed reports.
    %
:- func cmds_menu_restart_quit(maybe(preferences)) = display_item.

cmds_menu_restart_quit(MaybePrefs) = ControlsItem :-
    Menu = display_link(deep_link(deep_cmd_menu, MaybePrefs,
        attr_str([], "Menu"), link_class_control)),
    Restart = display_link(deep_link(deep_cmd_restart, MaybePrefs,
        attr_str([], "Restart"), link_class_control)),
    Quit = display_link(deep_link(deep_cmd_quit, MaybePrefs,
        attr_str([], "Quit"), link_class_control)),
    List = [Menu, Restart, Quit],
    ControlsItem = display_list(list_class_horizontal_except_title,
        yes("General commands:"), List).

:- func general_options_controls(cmd, preferences) = display_item.

general_options_controls(Cmd, Prefs) = ControlsItem :-
    DeveloperMode = Prefs ^ pref_developer_mode,
    (
        DeveloperMode = developer_options_visible,
        DeveloperText = "Disable developer options",
        DeveloperPrefs =
            Prefs ^ pref_developer_mode := developer_options_invisible
    ;
        DeveloperMode = developer_options_invisible,
        DeveloperText = "Enable developer options",
        DeveloperPrefs =
            Prefs ^ pref_developer_mode := developer_options_visible
    ),
    DeveloperControl = display_link(deep_link(Cmd, yes(DeveloperPrefs),
        attr_str([], DeveloperText), link_class_control)),
    List = [DeveloperControl],
    ControlsItem = display_list(list_class_horizontal_except_title,
        yes("General options:"), List).

%---------------------------------------------------------------------------%
%
% Convert procedure, call site and clique descriptions into table cells.
%

:- func module_active_to_module_name_cell(preferences, module_active) =
    table_cell.

module_active_to_module_name_cell(Prefs, ModuleActive) = Cell :-
    ModuleName = ModuleActive ^ ma_module_name,
    Cmd = deep_cmd_module(ModuleName),
    Link = deep_link(Cmd, yes(Prefs), attr_str([], ModuleName),
        link_class_link),
    Cell = table_cell(td_l(Link)).

:- func proc_active_to_proc_name_cell(maybe(string), module_qual,
    preferences, proc_active) = table_cell.

proc_active_to_proc_name_cell(MaybeCurModuleName, ModuleQual,
        Prefs, ProcActive) =
    proc_desc_to_proc_name_cell(MaybeCurModuleName, ModuleQual,
        Prefs, ProcActive ^ pa_proc_desc).

:- func proc_desc_to_source_cell(proc_desc) = table_cell.

proc_desc_to_source_cell(ProcDesc) = Cell :-
    FileName = ProcDesc ^ pdesc_file_name,
    LineNumber = ProcDesc ^ pdesc_line_number,
    Source = string.format("%s:%d", [s(FileName), i(LineNumber)]),
    Cell = table_cell(td_s(Source)).

:- func proc_desc_to_proc_name_cell(maybe(string), module_qual,
    preferences, proc_desc) = table_cell.

proc_desc_to_proc_name_cell(MaybeCurModuleName, ModuleQual, Prefs, ProcDesc)
        = Cell :-
    PSPtr = ProcDesc ^ pdesc_ps_ptr,
    RefinedName = proc_desc_get_refined_id(MaybeCurModuleName, ModuleQual,
        ProcDesc),
    Cmd = deep_cmd_proc(PSPtr),
    Link = deep_link(Cmd, yes(Prefs), attr_str([], RefinedName),
        link_class_link),
    Cell = table_cell(td_l(Link)).

:- func proc_desc_to_proc_name_cell_span(maybe(string), module_qual,
    preferences, list(str_attr), proc_desc, int) = table_cell.

proc_desc_to_proc_name_cell_span(MaybeCurModuleName, ModuleQual,
        Prefs, Attrs, ProcDesc, Span) = Cell :-
    PSPtr = ProcDesc ^ pdesc_ps_ptr,
    RefinedName = proc_desc_get_refined_id(MaybeCurModuleName, ModuleQual,
        ProcDesc),
    Cmd = deep_cmd_proc(PSPtr),
    Link = deep_link(Cmd, yes(Prefs), attr_str(Attrs, RefinedName),
        link_class_link),
    Cell = table_multi_cell(td_l(Link), Span).

:- func proc_desc_to_prefix_proc_name_cell(maybe(string), module_qual,
    preferences, list(str_attr), proc_desc, string) = table_cell.

proc_desc_to_prefix_proc_name_cell(MaybeCurModuleName, ModuleQual,
        Prefs, Attrs, ProcDesc, Prefix) = Cell :-
    PSPtr = ProcDesc ^ pdesc_ps_ptr,
    RefinedName = proc_desc_get_refined_id(MaybeCurModuleName, ModuleQual,
        ProcDesc),
    Cmd = deep_cmd_proc(PSPtr),
    Link = deep_link(Cmd, yes(Prefs), attr_str(Attrs, Prefix ++ RefinedName),
        link_class_link),
    Cell = table_cell(td_l(Link)).

:- func call_site_desc_to_name_path_slot_cell(maybe(string), module_qual,
    preferences, call_site_desc) = table_cell.

call_site_desc_to_name_path_slot_cell(MaybeCurModuleName, ModuleQual,
        Prefs, CallSiteDesc) = Cell :-
    CallSiteDesc = call_site_desc(CSSPtr, _ContainerPSPtr,
        _FileName, _LineNumber, _ModuleName,
        _UnQualRefinedName, _QualRefinedName,
        SlotNumber, RevGoalPath, _MaybeCallee),
    RefinedName = call_site_desc_get_caller_refined_id(MaybeCurModuleName,
        ModuleQual, CallSiteDesc),
    GoalPathStr = rev_goal_path_to_string(RevGoalPath),
    string.format("%s @ %s #%d",
        [s(RefinedName), s(GoalPathStr), i(SlotNumber)], Name),
    Cmd = deep_cmd_dump_call_site_static(CSSPtr),
    Link = deep_link(Cmd, yes(Prefs), attr_str([], Name), link_class_link),
    Cell = table_cell(td_l(Link)).

:- func call_site_desc_to_source_cell(call_site_desc) = table_cell.

call_site_desc_to_source_cell(CallSiteDesc) = Cell :-
    FileName = CallSiteDesc ^ csdesc_file_name,
    LineNumber = CallSiteDesc ^ csdesc_line_number,
    Source = string.format("%s:%d", [s(FileName), i(LineNumber)]),
    Cell = table_cell(td_s(Source)).

:- func call_site_desc_to_caller_proc_name_cell(maybe(string), module_qual,
    preferences, call_site_desc) = table_cell.

call_site_desc_to_caller_proc_name_cell(MaybeCurModuleName, ModuleQual,
        Prefs, CallSiteDesc) = Cell :-
    PSPtr = CallSiteDesc ^ csdesc_container,
    CallerRefinedName =
    call_site_desc_get_caller_refined_id(MaybeCurModuleName, ModuleQual,
        CallSiteDesc),
    Cmd = deep_cmd_proc(PSPtr),
    Link = deep_link(Cmd, yes(Prefs), attr_str([], CallerRefinedName),
        link_class_link),
    Cell = table_cell(td_l(Link)).

:- func clique_desc_to_non_self_link_proc_name_cell(maybe(string), module_qual,
    preferences, clique_desc, clique_ptr) = table_cell.

clique_desc_to_non_self_link_proc_name_cell(MaybeCurModuleName, ModuleQual,
        Prefs, CliqueDesc, SelfCliquePtr) = Cell :-
    CliqueDesc = clique_desc(CliquePtr, EntryProcDesc, _OtherProcDescs),
    EntryProcName = proc_desc_get_refined_id(MaybeCurModuleName, ModuleQual,
        EntryProcDesc),
    ( if CliquePtr = SelfCliquePtr then
        Cell = table_cell(td_s(EntryProcName))
    else
        Cmd = deep_cmd_clique(CliquePtr),
        Link = deep_link(Cmd, yes(Prefs), attr_str([], EntryProcName),
            link_class_link),
        Cell = table_cell(td_l(Link))
    ).

%---------------------------------------------------------------------------%
%
% Utility predicates.
%

    % Make a table row with two columns: a label and a value.
    %
:- func make_labelled_table_row(pair(string, table_data)) = table_row.

make_labelled_table_row(Label - Value) =
    table_row([table_cell(td_s(Label)), table_cell(Value)]).

:- type link_base
    --->    link_base(cmd, maybe(preferences), string).

    % Make a link from a command, preferences and a label.
    %
:- pred make_link(link_base::in, display_item::out) is det.

make_link(link_base(Cmd, MaybePrefs, Label), Item) :-
    Item = display_link(deep_link(Cmd, MaybePrefs, attr_str([], Label),
        link_class_link)).

    % Make a control from a command and label and optional preferences
    % structure.
    %
:- pred make_control(maybe(preferences)::in, cmd::in, string::in, bool::in,
    display_item::out) is det.

make_control(MaybePrefs, Cmd, Label, Developer, Item) :-
    Item0 = display_link(deep_link(Cmd, MaybePrefs, attr_str([], Label),
        link_class_control)),
    (
        Developer = yes,
        Item = display_developer(Item0)
    ;
        Developer = no,
        Item = Item0
    ).

%---------------------------------------------------------------------------%
%
% Sort procedures in a clique by the preferred criteria of performance.
%

:- pred sort_clique_procs_by_preferences(maybe(string)::in, module_qual::in,
    preferences::in,
    list(clique_proc_report)::in, list(clique_proc_report)::out) is det.

sort_clique_procs_by_preferences(MaybeCurModuleName, ModuleQual, Prefs,
        !CliqueProcs) :-
    OrderCriteria = Prefs ^ pref_criteria,
    (
        OrderCriteria = by_context,
        list.sort(compare_clique_procs_by_context, !CliqueProcs)
    ;
        OrderCriteria = by_name,
        list.sort(
            compare_clique_procs_by_name(MaybeCurModuleName, ModuleQual),
            !CliqueProcs)
    ;
        OrderCriteria = by_cost(CostKind, InclDesc, Scope),
        list.sort(compare_clique_procs_by_cost(CostKind, InclDesc, Scope),
            !CliqueProcs),
        % We want the most expensive procedures to appear first.
        list.reverse(!CliqueProcs)
    ).

:- pred compare_clique_procs_by_context(
    clique_proc_report::in, clique_proc_report::in, comparison_result::out)
    is det.

compare_clique_procs_by_context(CliqueProcReportA, CliqueProcReportB,
        Result) :-
    CliqueProcDescA = CliqueProcReportA ^ cpr_proc_summary ^ perf_row_subject,
    CliqueProcDescB = CliqueProcReportB ^ cpr_proc_summary ^ perf_row_subject,
    compare_proc_descs_by_context(CliqueProcDescA, CliqueProcDescB, Result).

:- pred compare_clique_procs_by_name(maybe(string)::in, module_qual::in,
    clique_proc_report::in, clique_proc_report::in, comparison_result::out)
    is det.

compare_clique_procs_by_name(MaybeCurModuleName, ModuleQual,
        CliqueProcReportA, CliqueProcReportB, Result) :-
    CliqueProcDescA = CliqueProcReportA ^ cpr_proc_summary ^ perf_row_subject,
    CliqueProcDescB = CliqueProcReportB ^ cpr_proc_summary ^ perf_row_subject,
    compare_proc_descs_by_name(MaybeCurModuleName, ModuleQual,
        CliqueProcDescA, CliqueProcDescB, Result).

:- pred compare_clique_procs_by_cost(
    cost_kind::in, include_descendants::in, measurement_scope::in,
    clique_proc_report::in, clique_proc_report::in, comparison_result::out)
    is det.

compare_clique_procs_by_cost(CostKind, InclDesc, Scope,
        CliqueProcReportA, CliqueProcReportB, Result) :-
    ProcRowDataA = CliqueProcReportA ^ cpr_proc_summary,
    ProcRowDataB = CliqueProcReportB ^ cpr_proc_summary,
    compare_perf_row_datas_by_cost(CostKind, InclDesc, Scope,
        ProcRowDataA, ProcRowDataB, Result).

%---------------------------------------------------------------------------%
%
% Sort proc_dynamics in a clique by the preferred criteria of performance.
%

:- pred sort_clique_proc_dynamics_by_preferences(preferences::in,
    list(clique_proc_dynamic_report)::in,
    list(clique_proc_dynamic_report)::out) is det.

sort_clique_proc_dynamics_by_preferences(Prefs, !CliqueProcDynamics) :-
    OrderCriteria = Prefs ^ pref_criteria,
    (
        ( OrderCriteria = by_context
        ; OrderCriteria = by_name
        ),
        % All the proc_dynamics we want to sort have the same name and context,
        % so it does not make sense to sort on these criteria. Instead, we sort
        % on the default performance criteria.
        CostKind = default_cost_kind,
        InclDesc = default_incl_desc,
        Scope = default_scope
    ;
        OrderCriteria = by_cost(CostKind, InclDesc, Scope)
    ),
    list.sort(compare_clique_proc_dynamics_by_cost(CostKind, InclDesc, Scope),
        !CliqueProcDynamics),
    % We want the most expensive procedures to appear first.
    list.reverse(!CliqueProcDynamics).

:- pred compare_clique_proc_dynamics_by_cost(
    cost_kind::in, include_descendants::in, measurement_scope::in,
    clique_proc_dynamic_report::in, clique_proc_dynamic_report::in,
    comparison_result::out) is det.

compare_clique_proc_dynamics_by_cost(CostKind, InclDesc, Scope,
        CliqueProcDynamicReportA, CliqueProcDynamicReportB, Result) :-
    ProcDynamicRowDataA = CliqueProcDynamicReportA ^ cpdr_proc_summary,
    ProcDynamicRowDataB = CliqueProcDynamicReportB ^ cpdr_proc_summary,
    compare_perf_row_datas_by_cost(CostKind, InclDesc, Scope,
        ProcDynamicRowDataA, ProcDynamicRowDataB, Result).

%---------------------------------------------------------------------------%
%
% Sort clique_call_site_reports by the preferred criteria of performance.
%

:- pred sort_clique_call_site_reports_by_preferences(maybe(string)::in,
    module_qual::in, preferences::in,
    list(clique_call_site_report)::in, list(clique_call_site_report)::out)
    is det.

sort_clique_call_site_reports_by_preferences(MaybeCurModuleName, ModuleQual,
        Prefs, !CallSiteReports) :-
    OrderCriteria = Prefs ^ pref_criteria,
    (
        OrderCriteria = by_context,
        list.sort(compare_clique_call_site_reports_by_context,
            !CallSiteReports)
    ;
        OrderCriteria = by_name,
        list.sort(
            compare_clique_call_site_reports_by_name(MaybeCurModuleName,
                ModuleQual),
            !CallSiteReports)
    ;
        OrderCriteria = by_cost(CostKind, InclDesc, Scope),
        list.sort(compare_clique_call_site_reports_by_cost(CostKind,
            InclDesc, Scope), !CallSiteReports),
        % We want the most expensive call sites to appear first.
        list.reverse(!CallSiteReports)
    ).

:- pred compare_clique_call_site_reports_by_context(
    clique_call_site_report::in, clique_call_site_report::in,
    comparison_result::out) is det.

compare_clique_call_site_reports_by_context(CallSiteReportA,
        CallSiteReportB, Result) :-
    CallSiteDescA =
        CallSiteReportA ^ ccsr_call_site_summary ^ perf_row_subject,
    CallSiteDescB =
        CallSiteReportB ^ ccsr_call_site_summary ^ perf_row_subject,
    compare_call_site_descs_by_context(CallSiteDescA, CallSiteDescB, Result).

:- pred compare_clique_call_site_reports_by_name(maybe(string)::in,
    module_qual::in, clique_call_site_report::in, clique_call_site_report::in,
    comparison_result::out) is det.

compare_clique_call_site_reports_by_name(MaybeCurModuleName, ModuleQual,
        CallSiteReportA, CallSiteReportB, Result) :-
    CallSiteDescA =
        CallSiteReportA ^ ccsr_call_site_summary ^ perf_row_subject,
    CallSiteDescB =
        CallSiteReportB ^ ccsr_call_site_summary ^ perf_row_subject,
    compare_call_site_descs_by_name(MaybeCurModuleName, ModuleQual,
        CallSiteDescA, CallSiteDescB, Result).

:- pred compare_clique_call_site_reports_by_cost(
    cost_kind::in, include_descendants::in, measurement_scope::in,
    clique_call_site_report::in, clique_call_site_report::in,
    comparison_result::out) is det.

compare_clique_call_site_reports_by_cost(CostKind, InclDesc, Scope,
        CliqueCallSiteReportA, CliqueCallSiteReportB, Result) :-
    PerfA = CliqueCallSiteReportA ^ ccsr_call_site_summary,
    PerfB = CliqueCallSiteReportB ^ ccsr_call_site_summary,
    compare_perf_row_datas_by_cost(CostKind, InclDesc, Scope, PerfA, PerfB,
        Result).

%---------------------------------------------------------------------------%
%
% Sort call_site_perfs by the preferred criteria of performance.
%

:- pred sort_call_sites_by_preferences(maybe(string)::in, module_qual::in,
    preferences::in,
    list(call_site_perf)::in, list(call_site_perf)::out) is det.

sort_call_sites_by_preferences(MaybeCurModuleName, ModuleQual, Prefs,
        !CallSitePerfs) :-
    OrderCriteria = Prefs ^ pref_criteria,
    (
        OrderCriteria = by_context,
        list.sort(compare_call_site_perfs_by_context, !CallSitePerfs)
    ;
        OrderCriteria = by_name,
        list.sort(
            compare_call_site_perfs_by_callee_name(MaybeCurModuleName,
                ModuleQual),
            !CallSitePerfs)
    ;
        OrderCriteria = by_cost(CostKind, InclDesc, Scope),
        list.sort(compare_call_site_perfs_by_cost(CostKind, InclDesc, Scope),
            !CallSitePerfs),
        % We want the most expensive call sites to appear first.
        list.reverse(!CallSitePerfs)
    ).

:- pred compare_call_site_perfs_by_context(
    call_site_perf::in, call_site_perf::in, comparison_result::out) is det.

compare_call_site_perfs_by_context(CallSitePerfA, CallSitePerfB, Result) :-
    CallSiteDescA = CallSitePerfA ^ csf_summary_perf ^ perf_row_subject,
    CallSiteDescB = CallSitePerfB ^ csf_summary_perf ^ perf_row_subject,
    compare_call_site_descs_by_context(CallSiteDescA, CallSiteDescB, Result).

:- pred compare_call_site_perfs_by_callee_name(maybe(string)::in,
    module_qual::in,
    call_site_perf::in, call_site_perf::in, comparison_result::out) is det.

compare_call_site_perfs_by_callee_name(MaybeCurModuleName, ModuleQual,
        CallSitePerfA, CallSitePerfB, Result) :-
    CallSiteDescA = CallSitePerfA ^ csf_summary_perf ^ perf_row_subject,
    CallSiteDescB = CallSitePerfB ^ csf_summary_perf ^ perf_row_subject,
    compare_call_site_descs_by_callee_name(MaybeCurModuleName, ModuleQual,
        CallSiteDescA, CallSiteDescB, Result).

:- pred compare_call_site_perfs_by_cost(
    cost_kind::in, include_descendants::in, measurement_scope::in,
    call_site_perf::in, call_site_perf::in, comparison_result::out) is det.

compare_call_site_perfs_by_cost(CostKind, InclDesc, Scope,
        CallSitePerfA, CallSitePerfB, Result) :-
    PerfA = CallSitePerfA ^ csf_summary_perf,
    PerfB = CallSitePerfB ^ csf_summary_perf,
    compare_perf_row_datas_by_cost(CostKind, InclDesc, Scope, PerfA, PerfB,
        Result).

%---------------------------------------------------------------------------%
%
% Sort perf_data_rows of call_site_descs by the preferred criteria.
%

:- pred sort_call_site_desc_rows_by_preferences(maybe(string)::in,
    module_qual::in, preferences::in,
    list(perf_row_data(call_site_desc))::in,
    list(perf_row_data(call_site_desc))::out) is det.

sort_call_site_desc_rows_by_preferences(MaybeCurModuleName, ModuleQual, Prefs,
        !CallSiteRowDatas) :-
    OrderCriteria = Prefs ^ pref_criteria,
    (
        OrderCriteria = by_context,
        list.sort(compare_call_site_desc_rows_by_context, !CallSiteRowDatas)
    ;
        OrderCriteria = by_name,
        list.sort(
            compare_call_site_desc_rows_by_name(MaybeCurModuleName,
                ModuleQual),
            !CallSiteRowDatas)
    ;
        OrderCriteria = by_cost(CostKind, InclDesc, Scope),
        list.sort(compare_perf_row_datas_by_cost(CostKind, InclDesc, Scope),
            !CallSiteRowDatas),
        % We want the most expensive rows to appear first.
        list.reverse(!CallSiteRowDatas)
    ).

:- pred compare_call_site_desc_rows_by_context(
    perf_row_data(call_site_desc)::in, perf_row_data(call_site_desc)::in,
    comparison_result::out) is det.

compare_call_site_desc_rows_by_context(
        CallSiteDescRowDataA, CallSiteDescRowDataB, Result) :-
    CallSiteDescA = CallSiteDescRowDataA ^ perf_row_subject,
    CallSiteDescB = CallSiteDescRowDataB ^ perf_row_subject,
    compare_call_site_descs_by_context(CallSiteDescA, CallSiteDescB, Result).

:- pred compare_call_site_desc_rows_by_name(maybe(string)::in, module_qual::in,
    perf_row_data(call_site_desc)::in, perf_row_data(call_site_desc)::in,
    comparison_result::out) is det.

compare_call_site_desc_rows_by_name(MaybeCurModuleName, ModuleQual,
        CallSiteDescRowDataA, CallSiteDescRowDataB, Result) :-
    CallSiteDescA = CallSiteDescRowDataA ^ perf_row_subject,
    CallSiteDescB = CallSiteDescRowDataB ^ perf_row_subject,
    compare_call_site_descs_by_name(MaybeCurModuleName, ModuleQual,
        CallSiteDescA, CallSiteDescB, Result).

%---------------------------------------------------------------------------%
%
% Sort perf_data_rows of proc_descs by the preferred criteria.
%

:- pred sort_proc_desc_rows_by_preferences(maybe(string)::in, module_qual::in,
    preferences::in,
    list(perf_row_data(proc_desc))::in, list(perf_row_data(proc_desc))::out)
    is det.

sort_proc_desc_rows_by_preferences(MaybeCurModuleName, ModuleQual, Prefs,
        !ProcDescRowDatas) :-
    OrderCriteria = Prefs ^ pref_criteria,
    (
        OrderCriteria = by_context,
        list.sort(compare_proc_desc_rows_by_context, !ProcDescRowDatas)
    ;
        OrderCriteria = by_name,
        list.sort(
            compare_proc_desc_rows_by_name(MaybeCurModuleName, ModuleQual),
            !ProcDescRowDatas)
    ;
        OrderCriteria = by_cost(CostKind, InclDesc, Scope),
        list.sort(compare_perf_row_datas_by_cost(CostKind, InclDesc, Scope),
            !ProcDescRowDatas),
        % We want the most expensive rows to appear first.
        list.reverse(!ProcDescRowDatas)
    ).

:- pred compare_proc_desc_rows_by_context(
    perf_row_data(proc_desc)::in, perf_row_data(proc_desc)::in,
    comparison_result::out) is det.

compare_proc_desc_rows_by_context(ProcDescRowDataA, ProcDescRowDataB,
            Result) :-
    ProcDescA = ProcDescRowDataA ^ perf_row_subject,
    ProcDescB = ProcDescRowDataB ^ perf_row_subject,
    compare_proc_descs_by_context(ProcDescA, ProcDescB, Result).

:- pred compare_proc_desc_rows_by_name(maybe(string)::in, module_qual::in,
    perf_row_data(proc_desc)::in, perf_row_data(proc_desc)::in,
    comparison_result::out) is det.

compare_proc_desc_rows_by_name(MaybeCurModuleName, ModuleQual,
        ProcDescRowDataA, ProcDescRowDataB, Result) :-
    ProcDescA = ProcDescRowDataA ^ perf_row_subject,
    ProcDescB = ProcDescRowDataB ^ perf_row_subject,
    compare_proc_descs_by_name(MaybeCurModuleName, ModuleQual,
        ProcDescA, ProcDescB, Result).

%---------------------------------------------------------------------------%
%
% Sort perf_data_rows of proc_actives by the preferred criteria.
%

:- pred sort_proc_active_rows_by_preferences(maybe(string)::in,
    module_qual::in, preferences::in,
    list(perf_row_data(proc_active))::in,
    list(perf_row_data(proc_active))::out) is det.

sort_proc_active_rows_by_preferences(MaybeCurModuleName, ModuleQual, Prefs,
        !ProcRowDatas) :-
    OrderCriteria = Prefs ^ pref_criteria,
    (
        OrderCriteria = by_context,
        list.sort(compare_proc_active_rows_by_context, !ProcRowDatas)
    ;
        OrderCriteria = by_name,
        list.sort(
            compare_proc_active_rows_by_name(MaybeCurModuleName, ModuleQual),
            !ProcRowDatas)
    ;
        OrderCriteria = by_cost(CostKind, InclDesc, Scope),
        list.sort(compare_perf_row_datas_by_cost(CostKind, InclDesc, Scope),
            !ProcRowDatas),
        % We want the most expensive rows to appear first.
        list.reverse(!ProcRowDatas)
    ).

:- pred compare_proc_active_rows_by_context(
    perf_row_data(proc_active)::in, perf_row_data(proc_active)::in,
    comparison_result::out) is det.

compare_proc_active_rows_by_context(ProcRowDataA, ProcRowDataB, Result) :-
    ProcDescA = ProcRowDataA ^ perf_row_subject ^ pa_proc_desc,
    ProcDescB = ProcRowDataB ^ perf_row_subject ^ pa_proc_desc,
    compare_proc_descs_by_context(ProcDescA, ProcDescB, Result).

:- pred compare_proc_active_rows_by_name(maybe(string)::in, module_qual::in,
    perf_row_data(proc_active)::in, perf_row_data(proc_active)::in,
    comparison_result::out) is det.

compare_proc_active_rows_by_name(MaybeCurModuleName, ModuleQual,
        ModuleRowDataA, ModuleRowDataB, Result) :-
    ProcDescA = ModuleRowDataA ^ perf_row_subject ^ pa_proc_desc,
    ProcDescB = ModuleRowDataB ^ perf_row_subject ^ pa_proc_desc,
    compare_proc_descs_by_name(MaybeCurModuleName, ModuleQual,
        ProcDescA, ProcDescB, Result).

%---------------------------------------------------------------------------%
%
% Sort perf_data_rows of module_getter_setters by the preferred criteria.
%

:- pred sort_getter_setter_fields(preferences::in,
    assoc_list(field_name, gs_field_info)::in,
    assoc_list(field_name, gs_field_info)::out) is det.

sort_getter_setter_fields(Prefs, !FieldPairs) :-
    OrderCriteria = Prefs ^ pref_criteria,
    (
        % In the common case, each FieldPair has two contexts, one each from
        % the getter and the setter. Neither context is all that useful to sort
        % by, so we sort by the field name instead.
        ( OrderCriteria = by_context
        ; OrderCriteria = by_name
        ),
        list.sort(compare_getter_setters_by_name, !FieldPairs)
    ;
        OrderCriteria = by_cost(CostKind, InclDesc, Scope),
        list.sort(compare_getter_setters_by_cost(CostKind, InclDesc, Scope),
            !FieldPairs),
        % We want the most expensive fields to appear first.
        list.reverse(!FieldPairs)
    ).

:- pred compare_getter_setters_by_name(
    pair(field_name, gs_field_info)::in, pair(field_name, gs_field_info)::in,
    comparison_result::out) is det.

compare_getter_setters_by_name(PairA, PairB, Result) :-
    PairA = FieldNameA - _,
    PairB = FieldNameB - _,
    compare(Result, FieldNameA, FieldNameB).

:- pred compare_getter_setters_by_cost(
    cost_kind::in, include_descendants::in, measurement_scope::in,
    pair(field_name, gs_field_info)::in, pair(field_name, gs_field_info)::in,
    comparison_result::out) is det.

compare_getter_setters_by_cost(CostKind, InclDesc, Scope, PairA, PairB,
        Result) :-
    PairA = FieldNameA - FieldInfoA,
    PairB = FieldNameB - FieldInfoB,
    PerfA = representative_field_perf_row(FieldInfoA),
    PerfB = representative_field_perf_row(FieldInfoB),
    compare_perf_row_datas_by_cost(CostKind, InclDesc, Scope, PerfA, PerfB,
        PerfResult),
    (
        ( PerfResult = (<)
        ; PerfResult = (>)
        ),
        Result = PerfResult
    ;
        PerfResult = (=),
        % We switch the field names to rank in reverse alphabetical order.
        % We do this because our caller will reverse the final sorted list,
        % and *this* reversal is needed to undo the effects of *that* reversal.
        compare(Result, FieldNameB, FieldNameA)
    ).

:- func representative_field_perf_row(gs_field_info) = perf_row_data(unit).

representative_field_perf_row(FieldInfo) = Perf :-
    (
        FieldInfo = gs_field_getter(Perf0),
        Perf = Perf0 ^ perf_row_subject := unit
    ;
        FieldInfo = gs_field_setter(Perf0),
        Perf = Perf0 ^ perf_row_subject := unit
    ;
        FieldInfo = gs_field_both(_, _, Perf)
    ).

%---------------------------------------------------------------------------%
%
% Sort perf_data_rows of module_actives by the preferred criteria.
%

:- pred sort_module_active_rows_by_preferences(preferences::in,
    list(perf_row_data(module_active))::in,
    list(perf_row_data(module_active))::out) is det.

sort_module_active_rows_by_preferences(Prefs, !ModuleRowDatas) :-
    OrderCriteria = Prefs ^ pref_criteria,
    (
        % A context is a filename and line number. The filename is derived
        % from the module name, and for modules, the line number part of the
        % context isn't relevant. Therefore sorting by context is equivalent to
        % sorting by name.
        ( OrderCriteria = by_context
        ; OrderCriteria = by_name
        ),
        list.sort(compare_module_active_rows_by_name, !ModuleRowDatas)
    ;
        OrderCriteria = by_cost(CostKind, InclDesc, Scope),
        list.sort(compare_perf_row_datas_by_cost(CostKind, InclDesc, Scope),
            !ModuleRowDatas),
        % We want the most expensive rows to appear first.
        list.reverse(!ModuleRowDatas)
    ).

:- pred compare_module_active_rows_by_name(
    perf_row_data(module_active)::in, perf_row_data(module_active)::in,
    comparison_result::out) is det.

compare_module_active_rows_by_name(ModuleRowDataA, ModuleRowDataB, Result) :-
    ModuleDescA = ModuleRowDataA ^ perf_row_subject,
    ModuleDescB = ModuleRowDataB ^ perf_row_subject,
    ModuleNameA = ModuleDescA ^ ma_module_name,
    ModuleNameB = ModuleDescB ^ ma_module_name,
    compare(Result, ModuleNameA, ModuleNameB).

%---------------------------------------------------------------------------%
%
% Sort perf_data_rows of module names by the preferred criteria.
%

:- pred sort_module_name_rows_by_preferences(preferences::in,
    list(perf_row_data(string))::in, list(perf_row_data(string))::out) is det.

sort_module_name_rows_by_preferences(Prefs, !ModuleRowDatas) :-
    OrderCriteria = Prefs ^ pref_criteria,
    (
        % A context is a filename and line number. The filename is derived
        % from the module name, and for modules, the line number part of the
        % context isn't relevant. Therefore sorting by context is equivalent to
        % sorting by name.
        ( OrderCriteria = by_context
        ; OrderCriteria = by_name
        ),
        list.sort(compare_module_name_rows_by_name, !ModuleRowDatas)
    ;
        OrderCriteria = by_cost(CostKind, InclDesc, Scope),
        list.sort(compare_perf_row_datas_by_cost(CostKind, InclDesc, Scope),
            !ModuleRowDatas),
        % We want the most expensive rows to appear first.
        list.reverse(!ModuleRowDatas)
    ).

:- pred compare_module_name_rows_by_name(
    perf_row_data(string)::in, perf_row_data(string)::in,
    comparison_result::out) is det.

compare_module_name_rows_by_name(ModuleRowDataA, ModuleRowDataB, Result) :-
    ModuleNameA = ModuleRowDataA ^ perf_row_subject,
    ModuleNameB = ModuleRowDataB ^ perf_row_subject,
    compare(Result, ModuleNameA, ModuleNameB).

%---------------------------------------------------------------------------%
%
% Sort perf_data_rows of cliques by the preferred criteria.
%

:- pred sort_clique_rows_by_preferences(preferences::in,
    list(perf_row_data(clique_desc))::in,
    list(perf_row_data(clique_desc))::out) is det.

sort_clique_rows_by_preferences(Prefs, !CliqueRowDatas) :-
    OrderCriteria = Prefs ^ pref_criteria,
    (
        OrderCriteria = by_context,
        % For cliques, we do not have a single context. We could use the
        % contexts of the procedures in the clique, but using the clique number
        % seems more useful.
        list.sort(compare_clique_rows_by_number, !CliqueRowDatas)
    ;
        OrderCriteria = by_name,
        list.sort(compare_clique_rows_by_first_proc_name, !CliqueRowDatas)
    ;
        OrderCriteria = by_cost(CostKind, InclDesc, Scope),
        list.sort(compare_perf_row_datas_by_cost(CostKind, InclDesc, Scope),
            !CliqueRowDatas),
        % We want the most expensive rows to appear first.
        list.reverse(!CliqueRowDatas)
    ).

:- pred compare_clique_rows_by_number(
    perf_row_data(clique_desc)::in, perf_row_data(clique_desc)::in,
    comparison_result::out) is det.

compare_clique_rows_by_number(CliqueRowDataA, CliqueRowDataB, Result) :-
    CliqueDescA = CliqueRowDataA ^ perf_row_subject,
    CliqueDescB = CliqueRowDataB ^ perf_row_subject,
    CliqueDescA ^ cdesc_clique_ptr = clique_ptr(CliqueNumA),
    CliqueDescB ^ cdesc_clique_ptr = clique_ptr(CliqueNumB),
    compare(Result, CliqueNumA, CliqueNumB).

:- pred compare_clique_rows_by_first_proc_name(
    perf_row_data(clique_desc)::in, perf_row_data(clique_desc)::in,
    comparison_result::out) is det.

compare_clique_rows_by_first_proc_name(CliqueRowDataA, CliqueRowDataB,
        Result) :-
    CliqueDescA = CliqueRowDataA ^ perf_row_subject,
    CliqueDescB = CliqueRowDataB ^ perf_row_subject,
    EntryProcDescA = CliqueDescA ^ cdesc_entry_member,
    EntryProcDescB = CliqueDescB ^ cdesc_entry_member,
    compare_proc_descs_by_context(EntryProcDescA, EntryProcDescB, Result).

%---------------------------------------------------------------------------%
%
% Sort call_site_descs and proc_descs by context and by name.
%

:- pred compare_call_site_descs_by_context(
    call_site_desc::in, call_site_desc::in, comparison_result::out) is det.

compare_call_site_descs_by_context(CallSiteDescA, CallSiteDescB, Result) :-
    FileNameA = CallSiteDescA ^ csdesc_file_name,
    FileNameB = CallSiteDescB ^ csdesc_file_name,
    compare(FileNameResult, FileNameA, FileNameB),
    (
        ( FileNameResult = (<)
        ; FileNameResult = (>)
        ),
        Result = FileNameResult
    ;
        FileNameResult = (=),
        LineNumberA = CallSiteDescA ^ csdesc_line_number,
        LineNumberB = CallSiteDescB ^ csdesc_line_number,
        compare(Result, LineNumberA, LineNumberB)
    ).

:- pred compare_proc_descs_by_context(proc_desc::in, proc_desc::in,
    comparison_result::out) is det.

compare_proc_descs_by_context(ProcDescA, ProcDescB, Result) :-
    FileNameA = ProcDescA ^ pdesc_file_name,
    FileNameB = ProcDescB ^ pdesc_file_name,
    compare(FileNameResult, FileNameA, FileNameB),
    (
        ( FileNameResult = (<)
        ; FileNameResult = (>)
        ),
        Result = FileNameResult
    ;
        FileNameResult = (=),
        LineNumberA = ProcDescA ^ pdesc_line_number,
        LineNumberB = ProcDescB ^ pdesc_line_number,
        compare(Result, LineNumberA, LineNumberB)
    ).

:- pred compare_call_site_descs_by_name(maybe(string)::in, module_qual::in,
    call_site_desc::in, call_site_desc::in, comparison_result::out) is det.

compare_call_site_descs_by_name(MaybeCurModuleName, ModuleQual,
        CallSiteDescA, CallSiteDescB, Result) :-
    NameA = call_site_desc_get_caller_refined_id(MaybeCurModuleName,
        ModuleQual, CallSiteDescA),
    NameB = call_site_desc_get_caller_refined_id(MaybeCurModuleName,
        ModuleQual, CallSiteDescB),
    compare(Result, NameA, NameB).

:- pred compare_call_site_descs_by_callee_name(maybe(string)::in,
    module_qual::in, call_site_desc::in, call_site_desc::in,
    comparison_result::out) is det.

compare_call_site_descs_by_callee_name(MaybeCurModuleName, ModuleQual,
        CallSiteDescA, CallSiteDescB, Result) :-
    MaybeCalleeA = CallSiteDescA ^ csdesc_maybe_callee,
    MaybeCalleeB = CallSiteDescB ^ csdesc_maybe_callee,
    (
        MaybeCalleeA = no,
        MaybeCalleeB = no,
        Result = (=)
    ;
        MaybeCalleeA = no,
        MaybeCalleeB = yes(_),
        Result = (<)
    ;
        MaybeCalleeA = yes(_),
        MaybeCalleeB = no,
        Result = (>)
    ;
        MaybeCalleeA = yes(CalleeNameA),
        MaybeCalleeB = yes(CalleeNameB),
        compare_proc_descs_by_name(MaybeCurModuleName, ModuleQual,
            CalleeNameA, CalleeNameB, Result)
    ).

:- pred compare_proc_descs_by_name(maybe(string)::in, module_qual::in,
    proc_desc::in, proc_desc::in, comparison_result::out) is det.

compare_proc_descs_by_name(MaybeCurModuleName, ModuleQual,
        ProcDescA, ProcDescB, Result) :-
    NameA =
        proc_desc_get_refined_id(MaybeCurModuleName, ModuleQual, ProcDescA),
    NameB =
        proc_desc_get_refined_id(MaybeCurModuleName, ModuleQual, ProcDescB),
    compare(Result, NameA, NameB).

%---------------------------------------------------------------------------%
%
% Sort perf_row_datas by a criterion of performance.
%

:- pred compare_perf_row_datas_by_cost(
    cost_kind::in, include_descendants::in, measurement_scope::in,
    perf_row_data(T)::in, perf_row_data(T)::in, comparison_result::out) is det.

compare_perf_row_datas_by_cost(CostKind, InclDesc, Scope, PerfA, PerfB,
        Result) :-
    (
        CostKind = cost_calls,
        CallsA = PerfA ^ perf_row_calls,
        CallsB = PerfB ^ perf_row_calls,
        compare(Result, CallsA, CallsB)
    ;
        CostKind = cost_redos,
        RedosA = PerfA ^ perf_row_redos,
        RedosB = PerfB ^ perf_row_redos,
        compare(Result, RedosA, RedosB)
    ;
        CostKind = cost_time,
        compare_perf_row_datas_by_time(InclDesc, Scope, PerfA, PerfB,
            TimeResult),
        (
            TimeResult = (=),
            compare_perf_row_datas_by_callseqs(InclDesc, Scope, PerfA, PerfB,
                Result)
        ;
            ( TimeResult = (<)
            ; TimeResult = (>)
            ),
            Result = TimeResult
        )
    ;
        CostKind = cost_callseqs,
        compare_perf_row_datas_by_callseqs(InclDesc, Scope, PerfA, PerfB,
            Result)
    ;
        CostKind = cost_allocs,
        compare_perf_row_datas_by_allocs(InclDesc, Scope, PerfA, PerfB, Result)
    ;
        CostKind = cost_words,
        compare_perf_row_datas_by_words(InclDesc, Scope, PerfA, PerfB, Result)
    ).

:- pred compare_perf_row_datas_by_time(
    include_descendants::in, measurement_scope::in,
    perf_row_data(T)::in, perf_row_data(T)::in, comparison_result::out) is det.

compare_perf_row_datas_by_time(InclDesc, Scope, PerfA, PerfB, Result) :-
    (
        InclDesc = self,
        SelfA = PerfA ^ perf_row_self,
        SelfB = PerfB ^ perf_row_self,
        (
            Scope = overall,
            TimeA = SelfA ^ perf_row_time,
            TimeB = SelfB ^ perf_row_time,
            compare(Result, TimeA, TimeB)
        ;
            Scope = per_call,
            TimeA = SelfA ^ perf_row_time_percall,
            TimeB = SelfB ^ perf_row_time_percall,
            compare(Result, TimeA, TimeB)
        )
    ;
        InclDesc = self_and_desc,
        MaybeTotalA = PerfA ^ perf_row_maybe_total,
        MaybeTotalB = PerfB ^ perf_row_maybe_total,
        ( if
            MaybeTotalA = yes(TotalA),
            MaybeTotalB = yes(TotalB)
        then
            (
                Scope = overall,
                TimeA = TotalA ^ perf_row_time,
                TimeB = TotalB ^ perf_row_time,
                compare(Result, TimeA, TimeB)
            ;
                Scope = per_call,
                TimeA = TotalA ^ perf_row_time_percall,
                TimeB = TotalB ^ perf_row_time_percall,
                compare(Result, TimeA, TimeB)
            )
        else
            unexpected($pred, "self_and_desc")
        )
    ).

:- pred compare_perf_row_datas_by_callseqs(
    include_descendants::in, measurement_scope::in,
    perf_row_data(T)::in, perf_row_data(T)::in, comparison_result::out) is det.

compare_perf_row_datas_by_callseqs(InclDesc, Scope, PerfA, PerfB, Result) :-
    (
        InclDesc = self,
        SelfA = PerfA ^ perf_row_self,
        SelfB = PerfB ^ perf_row_self,
        (
            Scope = overall,
            CallSeqsA = SelfA ^ perf_row_callseqs,
            CallSeqsB = SelfB ^ perf_row_callseqs,
            compare(Result, CallSeqsA, CallSeqsB)
        ;
            Scope = per_call,
            CallSeqsA = SelfA ^ perf_row_callseqs_percall,
            CallSeqsB = SelfB ^ perf_row_callseqs_percall,
            compare(Result, CallSeqsA, CallSeqsB)
        )
    ;
        InclDesc = self_and_desc,
        MaybeTotalA = PerfA ^ perf_row_maybe_total,
        MaybeTotalB = PerfB ^ perf_row_maybe_total,
        ( if
            MaybeTotalA = yes(TotalA),
            MaybeTotalB = yes(TotalB)
        then
            (
                Scope = overall,
                CallSeqsA = TotalA ^ perf_row_callseqs,
                CallSeqsB = TotalB ^ perf_row_callseqs,
                compare(Result, CallSeqsA, CallSeqsB)
            ;
                Scope = per_call,
                CallSeqsA = TotalA ^ perf_row_callseqs_percall,
                CallSeqsB = TotalB ^ perf_row_callseqs_percall,
                compare(Result, CallSeqsA, CallSeqsB)
            )
        else
            unexpected($pred, "self_and_desc")
        )
    ).

:- pred compare_perf_row_datas_by_allocs(
    include_descendants::in, measurement_scope::in,
    perf_row_data(T)::in, perf_row_data(T)::in, comparison_result::out) is det.

compare_perf_row_datas_by_allocs(InclDesc, Scope, PerfA, PerfB, Result) :-
    (
        InclDesc = self,
        SelfA = PerfA ^ perf_row_self,
        SelfB = PerfB ^ perf_row_self,
        (
            Scope = overall,
            AllocsA = SelfA ^ perf_row_allocs,
            AllocsB = SelfB ^ perf_row_allocs,
            compare(Result, AllocsA, AllocsB)
        ;
            Scope = per_call,
            AllocsA = SelfA ^ perf_row_allocs_percall,
            AllocsB = SelfB ^ perf_row_allocs_percall,
            compare(Result, AllocsA, AllocsB)
        )
    ;
        InclDesc = self_and_desc,
        MaybeTotalA = PerfA ^ perf_row_maybe_total,
        MaybeTotalB = PerfB ^ perf_row_maybe_total,
        ( if
            MaybeTotalA = yes(TotalA),
            MaybeTotalB = yes(TotalB)
        then
            (
                Scope = overall,
                AllocsA = TotalA ^ perf_row_allocs,
                AllocsB = TotalB ^ perf_row_allocs,
                compare(Result, AllocsA, AllocsB)
            ;
                Scope = per_call,
                AllocsA = TotalA ^ perf_row_allocs_percall,
                AllocsB = TotalB ^ perf_row_allocs_percall,
                compare(Result, AllocsA, AllocsB)
            )
        else
            unexpected($pred, "missing total")
        )
    ).

:- pred compare_perf_row_datas_by_words(
    include_descendants::in, measurement_scope::in,
    perf_row_data(T)::in, perf_row_data(T)::in, comparison_result::out) is det.

compare_perf_row_datas_by_words(InclDesc, Scope, PerfA, PerfB, Result) :-
    (
        InclDesc = self,
        SelfA = PerfA ^ perf_row_self,
        SelfB = PerfB ^ perf_row_self,
        (
            Scope = overall,
            MemoryA = SelfA ^ perf_row_mem,
            MemoryB = SelfB ^ perf_row_mem,
            compare_memory(MemoryA, MemoryB, Result)
        ;
            Scope = per_call,
            MemoryA = SelfA ^ perf_row_mem_percall,
            MemoryB = SelfB ^ perf_row_mem_percall,
            compare_memory(MemoryA, MemoryB, Result)
        )
    ;
        InclDesc = self_and_desc,
        MaybeTotalA = PerfA ^ perf_row_maybe_total,
        MaybeTotalB = PerfB ^ perf_row_maybe_total,
        ( if
            MaybeTotalA = yes(TotalA),
            MaybeTotalB = yes(TotalB)
        then
            (
                Scope = overall,
                MemoryA = TotalA ^ perf_row_mem,
                MemoryB = TotalB ^ perf_row_mem,
                compare_memory(MemoryA, MemoryB, Result)
            ;
                Scope = per_call,
                MemoryA = TotalA ^ perf_row_mem_percall,
                MemoryB = TotalB ^ perf_row_mem_percall,
                compare_memory(MemoryA, MemoryB, Result)
            )
        else
            unexpected($pred, "missing total")
        )
    ).

%---------------------------------------------------------------------------%

:- func proc_desc_get_refined_id(maybe(string), module_qual, proc_desc)
    = string.

proc_desc_get_refined_id(MaybeCurModuleName, ModuleQual, ProcDesc) = Name :-
    (
        ModuleQual = module_qual_never,
        Name = ProcDesc ^ pdesc_uq_refined_name
    ;
        ModuleQual = module_qual_when_diff,
        (
            MaybeCurModuleName = no,
            Name = ProcDesc ^ pdesc_q_refined_name
        ;
            MaybeCurModuleName = yes(CurModuleName),
            ModuleName = ProcDesc ^ pdesc_module_name,
            ( if ModuleName = CurModuleName then
                Name = ProcDesc ^ pdesc_uq_refined_name
            else
                Name = ProcDesc ^ pdesc_q_refined_name
            )
        )
    ;
        ModuleQual = module_qual_always,
        Name = ProcDesc ^ pdesc_q_refined_name
    ).

:- func call_site_desc_get_caller_refined_id(maybe(string), module_qual,
    call_site_desc) = string.

call_site_desc_get_caller_refined_id(MaybeCurModuleName, ModuleQual,
        CallSiteDesc) = Name :-
    (
        ModuleQual = module_qual_never,
        Name = CallSiteDesc ^ csdesc_caller_uq_refined_name
    ;
        ModuleQual = module_qual_when_diff,
        (
            MaybeCurModuleName = no,
            Name = CallSiteDesc ^ csdesc_caller_q_refined_name
        ;
            MaybeCurModuleName = yes(CurModuleName),
            ModuleName = CallSiteDesc ^ csdesc_caller_module_name,
            ( if ModuleName = CurModuleName then
                Name = CallSiteDesc ^ csdesc_caller_uq_refined_name
            else
                Name = CallSiteDesc ^ csdesc_caller_q_refined_name
            )
        )
    ;
        ModuleQual = module_qual_always,
        Name = CallSiteDesc ^ csdesc_caller_q_refined_name
    ).

%---------------------------------------------------------------------------%
:- end_module display_report.
%---------------------------------------------------------------------------%
