Files
mercury/deep_profiler/message.m
Zoltan Somogyi c9e549996a Let switch detection handle deeper disjunctions.
compiler/switch_detection.m:
    The existing switch detection algorithm does a single forward
    traversal of the procedure body, doing only very limited lookahead.
    This prevents it from recognizing some switches. To fix this, add
    a new prepass that provides unlimited lookahead.

    Add one use of this lookahead information. Later diffs should add
    more uses.

compiler/print_help.m:
    Fix the code that served as motivation for this change.
    It was a block of unifications that was intended to supply
    the lookahead that the old algorithm needed but did not have,
    but it actually lied: it said that the the following disjunction
    covered cons_ids that it actually did not cover. The fact that
    the compiler did not diagnose this lie until now was a bug.
    (After this diff, the compiler now reports an error.)

deep_profiler/message.m:
    Avoid the limitation of the new algorithm that was discussed today
    on m-dev.

compiler/options.m:
    Add a way to test for the presence of this new capability
    in the installed compiler.

tests/warnings/help_text.err_exp:
    Expect the new option name.
2025-11-15 05:08:29 +11:00

431 lines
16 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2009-2011 The University of Melbourne.
% Copyright (C) 2014-2015, 2017-2018, 2023, 2025 The Mercury team.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%---------------------------------------------------------------------------%
%
% File: message.m.
% Author: pbone.
%
% This module contains types and predicates for building messages used by the
% mdprof_create_feedback tool. These messages can represent information such as
% warnings and errors. Code is also included here to print them out.
%
%---------------------------------------------------------------------------%
:- module message.
:- interface.
:- import_module mdbcomp.
:- import_module mdbcomp.goal_path.
:- import_module mdbcomp.program_representation.
:- import_module profile.
:- import_module cord.
:- import_module io.
%---------------------------------------------------------------------------%
% A message to be displayed to the user.
%
:- type message
---> message(
message_location :: program_location,
message_type :: message_type
).
% The 'importance' of a message. Debug messages are not covered here since
% they should be implemented via trace goals. Neither are critical messages
% since we use exceptions in that case.
%
:- type message_level
---> message_info
; message_notice
; message_warning
; message_error.
:- type program_location
---> pl_proc(string_proc_label)
; pl_goal(string_proc_label, reverse_goal_path)
; pl_clique(clique_ptr)
; pl_csd(call_site_dynamic_ptr).
%---------------------------------------------------------------------------%
:- func message_get_level(message) = message_level.
:- func message_level_to_int(message_level) = int.
:- pred message_to_string(message::in, string::out) is det.
% location_to_string(IndentLevel, Location, String).
%
% Pretty-print a location to a cord of strings.
%
:- pred location_to_string(int::in, program_location::in, cord(string)::out)
is det.
:- pred append_message(program_location::in, message_type::in,
cord(message)::in, cord(message)::out) is det.
%---------------------------------------------------------------------------%
:- type message_type
---> info_found_candidate_conjunction
% A candidate parallel conjunction has been found.
; info_found_conjs_above_callsite_threshold(int)
% There are a number of conjuncts containing calls above the
% configured call site threshold, we are considering them for
% parallelisation against one another.
; info_found_n_conjunctions_with_positive_speedup(int)
% There are N conjunctions whose speedup due to parallelisation
% is positive.
; info_split_conjunction_into_partitions(int)
% The conjunction being considered for parallelisation had to be
% split into several 'partitions' because it contains some
% nonatomic goals; this can limit the amount of parallelism
% available.
; info_found_pushed_conjs_above_callsite_threshold
% There are two of conjuncts containing calls above the
% configured call site threshold that can be pushed together,
% we are considering them for parallelisation against one another.
; notice_duplicate_instantiation(
% This occurs when a variable is instantiated twice in a
% procedure body (different instantiation states are used).
% We don't bother parallelising such procedures.
%
% The number of conjunctions that could have been
% parallelised.
int
)
; notice_callpair_has_more_than_one_dependant_var
% A pair of calls that could be parallelised have many
% dependent variables. We don't yet calculate the speedup in
% these situations.
; notice_partition_does_not_have_costly_calls(int, int)
% A partition does not enough costly calls (>1) and
% could not be parallelised, we could have parallelised them
% if we could parallelise over non-atomic code.
%
% The parameters are the partition number and the number of
% costly calls found.
; notice_candidate_conjunction_not_det(detism_rep)
% The candidate conjunction has goals that are not
% deterministic or cc_multi amongst the costly calls.
; warning_cannot_lookup_proc_defn
% Couldn't find the proc defn in the progrep data, maybe the
% procedure is built-in.
; warning_cannot_compute_procrep_coverage_fallback(string)
% Couldn't compute the coverage annotation for a procedure
% representation. A fallback method will be used but without
% this information it may be less accurate.
; warning_cannot_compute_cost_of_recursive_calls(string)
% Couldn't compute the cost of recursive calls.
%
% The parameter contains extra information about this error.
; warning_cannot_compute_first_use_time(string)
% Couldn't compute the time at which a variable is produced
% or consumed.
%
% The parameter contains extra information about this error.
; error_extra_proc_dynamics_in_clique_proc
% We don't yet handle clique_proc_reports with multiple proc
% dynamics.
; error_cannot_lookup_coverage_points
% An error in the generation of a coverage_procrep report.
; error_exception_thrown(string).
%---------------------------------------------------------------------------%
% Create an indentation of the appropriate amount. Indentation is two
% spaces per indentation level.
%
:- func indent(int) = cord(string).
% Create a new line proceeded by an indentation.
%
:- func nl_indent(int) = cord(string).
% A newline symbol.
:- func nl = cord(string).
% The size of an indentation level. 2 x the input.
%
:- func indent_size(int) = int.
%---------------------------------------------------------------------------%
% Write out messages.
%
:- pred write_out_messages(io.text_output_stream::in, cord(message)::in,
io::di, io::uo) is det.
% Set the verbosity level to use above. Higher levels print out more
% information. Levels are in the inclusive range 0..4.
%
:- pred set_verbosity_level(int::in, io::di, io::uo) is det.
% The default verbosity level if set_verbosity_level is never called.
%
:- func default_verbosity_level = int.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module program_representation_utils.
:- import_module int.
:- import_module list.
:- import_module require.
:- import_module string.
%---------------------------------------------------------------------------%
message_get_level(message(_, Type)) =
message_type_to_level(Type).
%---------------------------------------------------------------------------%
message_level_to_int(message_info) = 4.
message_level_to_int(message_notice) = 3.
message_level_to_int(message_warning) = 2.
message_level_to_int(message_error) = 1.
%---------------------------------------------------------------------------%
message_to_string(message(Location, MessageType), String) :-
location_to_string(1, Location, LocationString),
Level = message_type_to_level(MessageType),
LevelString = message_level_to_string(Level),
MessageStr = message_type_to_string(MessageType),
Cord = LevelString ++ singleton(":\n") ++ LocationString ++
indent(1) ++ MessageStr ++ singleton("\n"),
append_list(cord.list(Cord), String).
location_to_string(Level, Location, String) :-
(
Location = pl_proc(ProcLabel),
print_proc_label_to_string(ProcLabel, ProcLabelString),
String = indent(Level) ++ singleton("Proc: ") ++
singleton(ProcLabelString) ++ singleton("\n")
;
Location = pl_goal(ProcLabel, RevGoalPath),
location_to_string(Level, pl_proc(ProcLabel), FirstLine),
(
RevGoalPath = rgp_nil,
GoalPathString = singleton("Root goal")
;
RevGoalPath = rgp_cons(_, _),
GoalPathString = singleton("Goal: ") ++
singleton(rev_goal_path_to_string(RevGoalPath))
),
SecondLine = indent(Level) ++ GoalPathString ++ singleton("\n"),
String = FirstLine ++ SecondLine
;
Location = pl_clique(clique_ptr(Id)),
format("clique %d", [i(Id)], String0),
String = indent(Level) ++ singleton(String0)
;
Location = pl_csd(CSDPtr),
CSDPtr = call_site_dynamic_ptr(CSDNum),
format("call site dynamic %d", [i(CSDNum)], String0),
String = indent(Level) ++ singleton(String0)
).
%---------------------------------------------------------------------------%
append_message(Location, MessageType, !Messages) :-
Message = message(Location, MessageType),
!:Messages = cord.snoc(!.Messages, Message).
%---------------------------------------------------------------------------%
:- func message_level_to_string(message_level) = cord(string).
message_level_to_string(message_info) = singleton("Info").
message_level_to_string(message_notice) = singleton("Notice").
message_level_to_string(message_warning) = singleton("Warning").
message_level_to_string(message_error) = singleton("Error").
%---------------------------------------------------------------------------%
:- func message_type_to_level(message_type) = message_level.
message_type_to_level(MsgType) = MsgLevel :-
(
( MsgType = info_found_candidate_conjunction
; MsgType = info_found_conjs_above_callsite_threshold(_)
; MsgType = info_found_pushed_conjs_above_callsite_threshold
; MsgType = info_found_n_conjunctions_with_positive_speedup(_)
; MsgType = info_split_conjunction_into_partitions(_)
),
MsgLevel = message_info
;
( MsgType = notice_duplicate_instantiation(_)
; MsgType = notice_callpair_has_more_than_one_dependant_var
; MsgType = notice_partition_does_not_have_costly_calls(_, _)
; MsgType = notice_candidate_conjunction_not_det(_)
),
MsgLevel = message_notice
;
( MsgType = warning_cannot_lookup_proc_defn
; MsgType = warning_cannot_compute_procrep_coverage_fallback(_)
; MsgType = warning_cannot_compute_cost_of_recursive_calls(_)
; MsgType = warning_cannot_compute_first_use_time(_)
),
MsgLevel = message_warning
;
( MsgType = error_extra_proc_dynamics_in_clique_proc
; MsgType = error_exception_thrown(_)
; MsgType = error_cannot_lookup_coverage_points
),
MsgLevel = message_error
).
%---------------------------------------------------------------------------%
:- func message_type_to_string(message_type) = cord(string).
:- pragma require_switch_arms_in_type_order(func(message_type_to_string/1)).
message_type_to_string(MessageType) = Cord :-
(
MessageType = info_found_candidate_conjunction,
String = "Found candidate conjunction"
;
MessageType = info_found_conjs_above_callsite_threshold(Num),
MessageStr = "Found %d conjuncts above callsite threshold",
string.format(MessageStr, [i(Num)], String)
;
MessageType = info_found_n_conjunctions_with_positive_speedup(Num),
MessageStr = "Found %d conjunctions with a positive speedup due"
++ " to parallelisation",
string.format(MessageStr, [i(Num)], String)
;
MessageType = info_split_conjunction_into_partitions(Num),
MessageStr = "Split conjunction into %d partitions, "
++ "this may reduce parallelism",
string.format(MessageStr, [i(Num)], String)
;
MessageType = info_found_pushed_conjs_above_callsite_threshold,
String = "Found pushed conjuncts above callsite threshold"
;
MessageType = notice_duplicate_instantiation(CandidateConjuncts),
string.format(
"%d conjunctions not parallelised: Seen duplicate instantiations",
[i(CandidateConjuncts)], String)
;
MessageType = notice_callpair_has_more_than_one_dependant_var,
String = "Parallelising call pairs that have more than one "
++ "dependent variable is not yet supported."
;
MessageType = notice_partition_does_not_have_costly_calls(PartNum,
NumCalls),
string.format("Partition %d has only %d costly calls and cannot be"
++ " parallelised",
[i(PartNum), i(NumCalls)], String)
;
MessageType = notice_candidate_conjunction_not_det(Detism),
string.format("There are %s goals amongst goals"
++ " above the parallelisation overhead.",
[s(string(Detism))], String)
;
MessageType = warning_cannot_lookup_proc_defn,
String = "Could not look up proc defn, perhaps this procedure is"
++ " built-in"
;
MessageType = warning_cannot_compute_procrep_coverage_fallback(Msg),
String = "Cannot compute procrep coverage annotation: " ++ Msg
++ "\n falling back to some other method"
;
MessageType =
warning_cannot_compute_cost_of_recursive_calls(WarnStr),
Template = "Cannot compute cost of recursive calls: %s",
string.format(Template, [s(WarnStr)], String)
;
MessageType =
warning_cannot_compute_first_use_time(WarnStr),
Template = "Cannot compute the production or consumption time "
++ "of a variable: %s",
string.format(Template, [s(WarnStr)], String)
;
MessageType = error_extra_proc_dynamics_in_clique_proc,
String = "extra proc dynamics for a clique proc are not currently"
++ " handled."
;
MessageType = error_cannot_lookup_coverage_points,
String = "Cannot lookup coverage points"
;
MessageType = error_exception_thrown(ErrorStr),
Template = "Exception thrown: %s",
string.format(Template, [s(ErrorStr)], String)
),
Cord = singleton(String).
%---------------------------------------------------------------------------%
indent(N) = Indent :-
( if N < 0 then
error("automatic_parallelism: Negative indent")
else if N = 0 then
Indent = empty
else
Indent = snoc(indent(N - 1), " ")
).
nl_indent(N) = nl ++ indent(N).
nl = singleton("\n").
indent_size(N) = 2 * N.
%---------------------------------------------------------------------------%
:- mutable(verbosity_level_mut, int, default_verbosity_level, ground,
[attach_to_io_state, untrailed]).
write_out_messages(Stream, Messages, !IO) :-
cord.foldl_pred(write_out_message(Stream), Messages, !IO).
:- pred write_out_message(io.text_output_stream::in, message::in,
io::di, io::uo) is det.
write_out_message(Stream, Message, !IO) :-
Level = message_get_level(Message),
get_verbosity_level_mut(VerbosityLevel, !IO),
( if message_level_to_int(Level) =< VerbosityLevel then
message_to_string(Message, MessageStr),
io.write_string(Stream, MessageStr, !IO),
io.nl(Stream, !IO)
else
true
).
set_verbosity_level(VerbosityLevel, !IO) :-
set_verbosity_level_mut(VerbosityLevel, !IO).
default_verbosity_level = 2.
%---------------------------------------------------------------------------%
:- end_module message.
%---------------------------------------------------------------------------%