Files
mercury/compiler/analysis.operations.m
Julien Fischer 7d4f4d8e1d Fix a broken link.
ocmpiler/analysis.operations.m:
   As above.
2025-12-26 16:57:12 +11:00

1377 lines
53 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2003-2004, 2006-2011 The University of Melbourne.
% Copyright (C) 2024 The Mercury team.
% This file may only be copied under the terms of the GNU Library General
% Public License - see the file COPYING.LIB in the Mercury distribution.
%---------------------------------------------------------------------------%
%
% File: analysis.operations.m.
% Main authors: stayl, wangp.
%
%---------------------------------------------------------------------------%
:- module analysis.operations.
:- interface.
:- import_module analysis.framework.
:- import_module hlds.
:- import_module hlds.hlds_module.
:- import_module libs.
:- import_module libs.globals.
:- import_module mdbcomp.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.
:- import_module parse_tree.error_spec.
:- import_module bool.
:- import_module io.
:- import_module list.
:- import_module maybe.
:- import_module set.
%---------------------------------------------------------------------------%
%
% These two types are used by both this module and by analysis.file.m.
% Strangely, values of these types do NOT pass between them as arguments
% or results of function or predicate calls.
%
:- type analysis_request
---> some [FuncInfo, Call]
analysis_request(
req_call :: Call,
req_caller :: module_name
)
=> call_pattern(FuncInfo, Call).
:- type imdg_arc
---> some [FuncInfo, Call]
imdg_arc(
% Call pattern of the analysis result being depended on.
imdg_call :: Call,
% The module that _depends on_ this function's result.
imdg_caller :: module_name
)
=> call_pattern(FuncInfo, Call).
%---------------------------------------------------------------------------%
:- type no_func_info
---> no_func_info.
% A call pattern that can be used by analyses that do not need
% finer granularity.
%
:- type any_call
---> any_call.
:- instance call_pattern(no_func_info, any_call).
:- instance partial_order(no_func_info, any_call).
:- instance to_term(any_call).
%---------------------------------------------------------------------------%
:- type analysis_info.
:- func init_analysis_info(Compiler, module_name, bool) = analysis_info
<= compiler(Compiler).
:- some [Compiler] func analysis_info_get_compiler(analysis_info) = Compiler
=> compiler(Compiler).
%---------------------------------------------------------------------------%
% Record the dependency of the module being analysed on the analysis
% result of another module.
% Does nothing if not making the analysis registry or if the result
% that is depended upon comes from a non-local module.
% Automatically makes a request if the call pattern hasn't been seen
% before for that function.
%
:- pred record_dependency(module_name::in, func_id::in, FuncInfo::in,
Call::in, Answer::unused, analysis_info::in, analysis_info::out) is det
<= analysis(FuncInfo, Call, Answer).
% Record an analysis result for a function.
% Abort if the function is not from the module being analysed.
% Does nothing if not making the analysis registry.
%
:- pred record_result(module_name::in, func_id::in, Call::in, Answer::in,
analysis_status::in, analysis_info::in, analysis_info::out) is det
<= analysis(FuncInfo, Call, Answer).
% Record a request from the module being analysed on a function defined
% in an imported module.
% Does nothing if not making the analysis registry or if the function is
% defined in a non-local module.
%
:- pred record_request(analysis_name::in, module_name::in,
func_id::in, Call::in, analysis_info::in, analysis_info::out) is det
<= call_pattern(FuncInfo, Call).
%---------------------------------------------------------------------------%
% Look up all results for a given function.
% If the module is `invalid' then the result list will be empty.
%
% N.B. Newly recorded results will NOT be found. This is intended
% for looking up results from _other_ modules.
%
:- pred lookup_results(analysis_info::in, module_name::in, func_id::in,
list(analysis_result(Call, Answer))::out) is det
<= analysis(FuncInfo, Call, Answer).
% Look up all results for a given function and call pattern CP such
% that the results have call patterns CP' that are equivalent to CP
% or less specific than CP.
%
% N.B. Newly recorded results will NOT be found. This is intended
% for looking up results from _other_ modules.
%
:- pred lookup_matching_results(analysis_info::in, module_name::in,
func_id::in, FuncInfo::in, Call::in,
list(analysis_result(Call, Answer))::out) is det
<= analysis(FuncInfo, Call, Answer).
% Look up the best result matching a given call.
%
% N.B. Newly recorded results will NOT be found. This is intended
% for looking up results from _other_ modules.
%
% If the returned best result has a call pattern that is different
% from the given call pattern, then it is the analysis writer's
% responsibility to request a more precise analysis from the called module,
% using `record_request'.
%
:- pred lookup_best_result(analysis_info::in, module_name::in, func_id::in,
FuncInfo::in, Call::in, maybe(analysis_result(Call, Answer))::out) is det
<= analysis(FuncInfo, Call, Answer).
% Look up call patterns for all results for a given function.
% Even if the module is `invalid' the call patterns will be returned.
%
% You should use this when you want to know which call patterns were
% produced for a procedure defined in the current module in previous
% passes.
%
:- pred lookup_existing_call_patterns(analysis_info::in, analysis_name::in,
module_name::in, func_id::in, list(Call)::out) is det
<= call_pattern(FuncInfo, Call).
% Lookup all the requests for a given function.
% Abort if the function is not from the module being analysed.
%
:- pred lookup_requests(analysis_info::in, analysis_name::in, module_name::in,
func_id::in, list(Call)::out) is det
<= call_pattern(FuncInfo, Call).
%---------------------------------------------------------------------------%
% prepare_intermodule_analysis(ProgressStream, Globals,
% ImportedModuleNames, LocalModuleNames, !Info, !IO)
%
% This predicate should be called before any pass begins to use the
% analysis framework. It ensures that all the analysis files
% are loaded so that lookups can be satisfied. ImportedModuleNames is the
% set of all modules that are directly or indirectly imported by the
% module being analysed. LocalModuleNames is the set of non-"library"
% modules.
%
:- pred prepare_intermodule_analysis(io.text_output_stream::in, globals::in,
set(module_name)::in, set(module_name)::in, list(error_spec)::out,
analysis_info::in, analysis_info::out, io::di, io::uo) is det.
% module_is_local(Info, ModuleName, IsLocal).
%
% IsLocal is `yes' if the module is not a "library" module, i.e. we are
% able to reanalyse the module. The set of local modules is set in
% `prepare_intermodule_analysis'.
%
:- pred module_is_local(analysis_info::in, module_name::in, bool::out) is det.
% Should be called after all analysis is completed to write the
% requests and results for the current compilation to the
% analysis files.
%
:- pred write_analysis_files(io.text_output_stream::in, Compiler::in,
module_info::in, set(module_name)::in, analysis_info::in,
list(error_spec)::out, io::di, io::uo) is det <= compiler(Compiler).
% do_read_module_overall_status(Compiler, Globals, ModuleName,
% MaybeModuleStatus, !IO)
%
% Attempt to read the overall status from a module `.analysis' file.
%
:- pred do_read_module_overall_status(Compiler::in, globals::in,
module_name::in, analysis_status::out, io::di, io::uo) is det
<= compiler(Compiler).
%---------------------------------------------------------------------------%
:- pred get_analysis_debug_stream(maybe(io.text_output_stream)::out,
io::di, io::uo) is det.
:- pred set_analysis_debug_stream(maybe(io.text_output_stream)::in,
io::di, io::uo) is det.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module analysis.file.
:- import_module parse_tree.file_names.
:- import_module parse_tree.module_cmds. % XXX unwanted dependency
:- import_module map.
:- import_module require.
:- import_module string.
:- import_module term.
:- import_module term_context.
:- import_module type_desc.
:- import_module univ.
%---------------------------------------------------------------------------%
:- type analysis_info
---> some [Compiler]
analysis_info(
compiler :: Compiler,
% The module being analysed.
this_module :: module_name,
% Whether we are making the analysis registry or just using
% results for .analysis files.
make_analysis_registry :: make_analysis_registry,
% The set of local modules, i.e. for which we can issue
% requests.
local_module_names :: set(module_name),
% Holds outstanding requests for more specialised variants
% of procedures. Requests are added to this map as analyses
% proceed and written out to disk at the end of the
% compilation of this module.
analysis_requests_map :: analysis_map(analysis_request),
% The overall status of each module.
module_status_map :: map(module_name, analysis_status),
% The "old" map stores analysis results read in from disk.
% New results generated while analysing the current module
% are added to the "new" map. After all the analyses
% the two maps are compared to see which analysis results
% have changed. Other modules may need to be marked or
% invalidated as a result. Then "new" results are moved
% into the "old" map, from where they can be written to disk.
old_analysis_results :: analysis_map(some_analysis_result),
new_analysis_results :: module_analysis_map(
some_analysis_result),
% The Inter-module Dependency Graph records dependencies
% of an entire module's analysis results on another module's
% answer patterns. e.g. assume module M1 contains function F1
% that has an analysis result that used the answer F2:CP2->AP2
% from module M2. If AP2 changes then all of M1 will either be
% marked `suboptimal' or `invalid'. Finer-grained dependency
% tracking would allow only F1 to be recompiled, instead of
% all of M1, but we don't do that.
%
% IMDGs are loaded from disk into the old map. During analysis,
% any dependences of the current module on other modules
% are added into the new map. At the end of analysis, all the
% arcs which terminate at the current module are cleared
% from the old map, and replaced by those in the new map.
%
% XXX: Check if we really need two maps.
old_imdg_map :: analysis_map(imdg_arc),
new_imdg_map :: analysis_map(imdg_arc)
)
=> compiler(Compiler).
:- type make_analysis_registry
---> make_analysis_registry
; use_analysis_registry_only.
%---------------------------------------------------------------------------%
%
% The "any" call pattern
%
:- instance call_pattern(no_func_info, any_call) where [].
:- instance partial_order(no_func_info, any_call) where [
( more_precise_than(_, _, _) :-
semidet_fail
),
( equivalent(no_func_info, _, _) :-
semidet_succeed
)
].
:- instance to_term(any_call) where [
( to_term(any_call) = Term :-
Term = term.functor(atom("any"), [], dummy_context)
),
( from_term(Term, any_call) :-
Term = term.functor(atom("any"), [], _)
)
].
%---------------------------------------------------------------------------%
init_analysis_info(Compiler, ThisModuleName, MakeAnalysisRegBool) = Info :-
(
MakeAnalysisRegBool = yes,
MakeAnalysisReg = make_analysis_registry
;
MakeAnalysisRegBool = no,
MakeAnalysisReg = use_analysis_registry_only
),
Info = 'new analysis_info'(Compiler, ThisModuleName, MakeAnalysisReg,
set.init, map.init, map.init, map.init, map.init, map.init, map.init).
analysis_info_get_compiler(Info) = Info ^ compiler.
%---------------------------------------------------------------------------%
record_dependency(CalleeModuleName, FuncId, FuncInfo, Call, DummyAnswer,
!Info) :-
ThisModule = !.Info ^ this_module,
( if ThisModule = CalleeModuleName then
unexpected($pred, "dependency on self")
else
true
),
MakeAnalysisReg = !.Info ^ make_analysis_registry,
module_is_local(!.Info, CalleeModuleName, IsLocal),
( if
MakeAnalysisReg = make_analysis_registry,
IsLocal = yes
then
AnalysisName = analysis_name(Call, DummyAnswer),
record_dependency_2(ThisModule, AnalysisName, CalleeModuleName, FuncId,
Call, !Info),
% If the call pattern that is being depended on hasn't been analysed
% before, make a request for it.
lookup_exactly_matching_result_even_from_invalid_modules(!.Info,
CalleeModuleName, FuncId, FuncInfo, Call, MaybeResult),
(
MaybeResult = no,
record_request(AnalysisName, CalleeModuleName, FuncId, Call, !Info)
;
MaybeResult = yes(Result),
same_type(Result, analysis_result(Call, DummyAnswer, _))
)
else
true
).
:- pred record_dependency_2(module_name::in, analysis_name::in,
module_name::in, func_id::in, Call::in,
analysis_info::in, analysis_info::out) is det
<= call_pattern(FuncInfo, Call).
record_dependency_2(CallerModuleName, AnalysisName, CalleeModuleName, FuncId,
Call, !Info) :-
NewIMDGMap0 = !.Info ^ new_imdg_map,
( if map.search(NewIMDGMap0, CalleeModuleName, Analyses0) then
Analyses1 = Analyses0
else
Analyses1 = map.init
),
( if map.search(Analyses1, AnalysisName, Funcs0) then
Funcs1 = Funcs0
else
Funcs1 = map.init
),
( if map.search(Funcs1, FuncId, FuncArcs0) then
FuncArcs1 = FuncArcs0
else
FuncArcs1 = []
),
Dep = 'new imdg_arc'(Call, CallerModuleName),
% XXX This should really be a set to begin with.
( if list.member(Dep, FuncArcs1) then
true
else
FuncArcs = [Dep | FuncArcs1],
map.set(FuncId, FuncArcs, Funcs1, Funcs),
map.set(AnalysisName, Funcs, Analyses1, Analyses),
map.set(CalleeModuleName, Analyses, NewIMDGMap0, NewIMDGMap),
!Info ^ new_imdg_map := NewIMDGMap
).
:- pred lookup_exactly_matching_result_even_from_invalid_modules(
analysis_info::in, module_name::in, func_id::in, FuncInfo::in, Call::in,
maybe(analysis_result(Call, Answer))::out) is det
<= analysis(FuncInfo, Call, Answer).
lookup_exactly_matching_result_even_from_invalid_modules(Info, ModuleName,
FuncId, FuncInfo, Call, MaybeResult) :-
AllowInvalidModules = yes,
lookup_results_1(Info, ModuleName, FuncId, AllowInvalidModules,
AllResultsList),
ResultList = list.filter(
( pred(R::in) is semidet :-
equivalent(FuncInfo, Call, R ^ ar_call)
), AllResultsList),
(
ResultList = [],
MaybeResult = no
;
ResultList = [Result],
MaybeResult = yes(Result)
;
ResultList = [_, _ | _],
unexpected($pred, "zero or one exactly matching results expected")
).
%---------------------%
record_result(ModuleName, FuncId, CallPattern, AnswerPattern, Status, !Info) :-
( if ModuleName = !.Info ^ this_module then
true
else
unexpected($pred,
"recording result for procedure defined in another module")
),
MakeAnalysisReg = !.Info ^ make_analysis_registry,
(
MakeAnalysisReg = make_analysis_registry,
Map0 = !.Info ^ new_analysis_results,
record_result_in_analysis_map(FuncId, CallPattern, AnswerPattern,
Status, Map0, Map),
!Info ^ new_analysis_results := Map
;
MakeAnalysisReg = use_analysis_registry_only
).
:- pred record_result_in_analysis_map(func_id::in,
Call::in, Answer::in, analysis_status::in,
module_analysis_map(some_analysis_result)::in,
module_analysis_map(some_analysis_result)::out) is det
<= analysis(FuncInfo, Call, Answer).
record_result_in_analysis_map(FuncId, CallPattern, AnswerPattern, Status,
ModuleResults0, ModuleResults) :-
AnalysisName = analysis_name(CallPattern, AnswerPattern),
( if map.search(ModuleResults0, AnalysisName, AnalysisResults0) then
AnalysisResults1 = AnalysisResults0
else
AnalysisResults1 = map.init
),
( if map.search(AnalysisResults1, FuncId, FuncResults0) then
FuncResults1 = FuncResults0
else
FuncResults1 = []
),
Result = 'new some_analysis_result'(CallPattern, AnswerPattern, Status),
FuncResults = [Result | FuncResults1],
ModuleResults =
map.set(ModuleResults0, AnalysisName,
map.set(AnalysisResults1, FuncId, FuncResults)).
%---------------------%
record_request(AnalysisName, ModuleName, FuncId, CallPattern, !Info) :-
ThisModule = !.Info ^ this_module,
( if ThisModule = ModuleName then
unexpected($pred, "request on self")
else
true
),
MakeAnalysisReg = !.Info ^ make_analysis_registry,
module_is_local(!.Info, ModuleName, IsLocal),
( if
MakeAnalysisReg = make_analysis_registry,
IsLocal = yes
then
record_request_2(ThisModule, AnalysisName, ModuleName, FuncId,
CallPattern, !Info)
else
true
).
:- pred record_request_2(module_name::in, analysis_name::in, module_name::in,
func_id::in, Call::in, analysis_info::in, analysis_info::out) is det
<= call_pattern(FuncInfo, Call).
record_request_2(CallerModule, AnalysisName, ModuleName, FuncId, CallPattern,
!Info) :-
RequestsMap0 = !.Info ^ analysis_requests_map,
( if map.search(RequestsMap0, ModuleName, ModuleResults0) then
ModuleResults1 = ModuleResults0
else
ModuleResults1 = map.init
),
( if map.search(ModuleResults1, AnalysisName, AnalysisResults0) then
AnalysisResults1 = AnalysisResults0
else
AnalysisResults1 = map.init
),
( if map.search(AnalysisResults1, FuncId, FuncResults0) then
FuncResults1 = FuncResults0
else
FuncResults1 = []
),
Request = 'new analysis_request'(CallPattern, CallerModule),
FuncResults = [Request | FuncResults1],
map.set(FuncId, FuncResults, AnalysisResults1, AnalysisResults),
map.set(AnalysisName, AnalysisResults, ModuleResults1, ModuleResults),
map.set(ModuleName, ModuleResults, RequestsMap0, RequestsMap),
!Info ^ analysis_requests_map := RequestsMap.
%---------------------------------------------------------------------------%
lookup_results(Info, ModuleName, FuncId, ResultList) :-
AllowInvalidModules = no,
lookup_results_1(Info, ModuleName, FuncId, AllowInvalidModules,
ResultList).
:- pred lookup_results_1(analysis_info::in, module_name::in, func_id::in,
bool::in, list(analysis_result(Call, Answer))::out) is det
<= analysis(FuncInfo, Call, Answer).
lookup_results_1(Info, ModuleName, FuncId, AllowInvalidModules, ResultList) :-
trace [io(!IO)] (
get_debug_analysis_stream(MaybeDebugStream, !IO),
(
MaybeDebugStream = no
;
MaybeDebugStream = yes(DebugStream),
ModuleNameStr = sym_name_to_string(ModuleName),
FuncIdStr = string.string(FuncId),
io.format(DebugStream,
"%% Looking up analysis results for %s.%s\n",
[s(ModuleNameStr), s(FuncIdStr)], !IO)
)
),
( if
AllowInvalidModules = no,
map.search(Info ^ module_status_map, ModuleName, invalid)
then
ResultList = []
else
lookup_results_2(Info ^ old_analysis_results, ModuleName, FuncId,
ResultList),
trace [io(!IO)] (
get_debug_analysis_stream(MaybeDebugStream, !IO),
(
MaybeDebugStream = no
;
MaybeDebugStream = yes(DebugStream),
io.write_string(DebugStream, "% Found these results: ", !IO),
io.print_line(DebugStream, ResultList, !IO)
)
)
).
:- pred lookup_results_2(analysis_map(some_analysis_result)::in,
module_name::in, func_id::in, list(analysis_result(Call, Answer))::out)
is det <= analysis(FuncInfo, Call, Answer).
lookup_results_2(Map, ModuleName, FuncId, ResultList) :-
AnalysisName = analysis_name(_ : Call, _ : Answer),
( if
map.search(Map, ModuleName, ModuleResults),
map.search(ModuleResults, AnalysisName, AnalysisResults),
map.search(AnalysisResults, FuncId, Results)
then
% XXX we might have to discard results which are
% `invalid' or `fixpoint_invalid' if they are written at all
ResultList = list.map(
( func(Result) = analysis_result(Call, Answer, Status) :-
Result = some_analysis_result(Call0, Answer0, Status),
det_univ_to_type(univ(Call0), Call),
det_univ_to_type(univ(Answer0), Answer)
), Results)
else
ResultList = []
).
%---------------------%
lookup_matching_results(Info, ModuleName, FuncId, FuncInfo, Call,
ResultList) :-
lookup_results(Info, ModuleName, FuncId, AllResultsList),
ResultList = list.filter(
( pred(Result::in) is semidet :-
ResultCall = Result ^ ar_call,
( more_precise_than(FuncInfo, Call, ResultCall)
; equivalent(FuncInfo, Call, ResultCall)
)
), AllResultsList).
%---------------------%
lookup_best_result(Info, ModuleName, FuncId, FuncInfo, Call,
MaybeBestResult) :-
trace [io(!IO)] (
get_debug_analysis_stream(MaybeDebugStream, !IO),
(
MaybeDebugStream = no
;
MaybeDebugStream = yes(DebugStream),
ModuleNameStr = sym_name_to_string(ModuleName),
FuncIdStr = string.string(FuncId),
io.format(DebugStream,
"%% Looking up best analysis result for %s.%s\n",
[s(ModuleNameStr), s(FuncIdStr)], !IO)
)
),
lookup_matching_results(Info, ModuleName, FuncId, FuncInfo, Call,
MatchingResults),
(
MatchingResults = [],
MaybeBestResult = no
;
MatchingResults = [Result | Results],
list.foldl(more_precise_answer(FuncInfo), Results, Result, BestResult),
MaybeBestResult = yes(BestResult)
).
:- pred more_precise_answer(FuncInfo::in,
analysis_result(Call, Answer)::in, analysis_result(Call, Answer)::in,
analysis_result(Call, Answer)::out) is det
<= analysis(FuncInfo, Call, Answer).
more_precise_answer(FuncInfo, Result, Best0, Best) :-
ResultAnswer = Result ^ ar_answer,
BestAnswer0 = Best0 ^ ar_answer,
( if more_precise_than(FuncInfo, ResultAnswer, BestAnswer0) then
Best = Result
else
Best = Best0
).
%---------------------%
lookup_existing_call_patterns(Info, AnalysisName, ModuleName, FuncId, Calls) :-
( if ModuleName = Info ^ this_module then
true
else
unexpected($pred, "not this_module")
),
Map = Info ^ old_analysis_results,
( if
map.search(Map, ModuleName, ModuleResults),
map.search(ModuleResults, AnalysisName, AnalysisResults),
map.search(AnalysisResults, FuncId, Results)
then
Calls = list.map(
( func(Result) = Call :-
Result = some_analysis_result(Call0, _Answer, _Status),
det_univ_to_type(univ(Call0), Call)
), Results)
else
Calls = []
).
%---------------------%
lookup_requests(Info, AnalysisName, ModuleName, FuncId, CallPatterns) :-
( if ModuleName = Info ^ this_module then
true
else
unexpected($pred, "not this_module")
),
( if
map.search(Info ^ analysis_requests_map, ModuleName, ModuleRequests),
map.search(ModuleRequests, AnalysisName, AnalysisRequests),
map.search(AnalysisRequests, FuncId, CallPatterns0)
then
CallPatterns1 = list.filter_map(
( func(analysis_request(Call0, _)) = Call is semidet :-
univ(Call) = univ(Call0)
), CallPatterns0),
% Requests simply get appended to `.request' files so when we read them
% back in there may be duplicates.
list.sort_and_remove_dups(CallPatterns1, CallPatterns)
else
CallPatterns = []
).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
% The algorithm is from Nick's thesis, pp. 108-9.
% Or my corruption thereof.
% See the file `compiler/notes/analysis.html' for a reference.
%
% XXX The lack of indentation beyond the first level makes this hard to
% read: exactly what range of lines does the "we don't do that" comment
% refer to?
%
% For each new analysis result (P^M:DP --> Ans_new):
% Read in the registry of M if necessary
% If there is an existing analysis result (P^M:DP --> Ans_old):
% if Ans_new \= Ans_old:
% Replace the entry in the registry with P^M:DP --> Ans_new
% if Ans_new `more_precise_than` Ans_old
% Status = suboptimal
% else
% Status = invalid
% For each entry (Q^N:DQ --> P^M:DP) in the IMDG:
% % Mark Q^N:DQ --> _ (_) with Status
% Actually, we don't do that. We only mark the module N's _overall_
% status with the least upper bound of its old status and Status.
% Else (P:DP --> Ans_old) did not exist:
% Insert result (P:DP --> Ans_new) into the registry.
%
% Finally, clear out the "new" analysis results map. When we write out
% the analysis files we will do it from the "old" results map.
%
% In a similar way, any new results which satisfy a request cause the
% module that made the request to be marked suboptimal.
%
:- pred update_analysis_registry(module_info::in,
analysis_info::in, analysis_info::out, io::di, io::uo) is det.
update_analysis_registry(ModuleInfo, !Info, !IO) :-
get_debug_analysis_stream(MaybeDebugStream, !IO),
(
MaybeDebugStream = no
;
MaybeDebugStream = yes(DebugStream),
io.write_string(DebugStream, "% Updating analysis registry.\n", !IO)
),
NewResults = !.Info ^ new_analysis_results,
update_analysis_registry_2(ModuleInfo, !.Info ^ this_module, NewResults,
!Info, !IO),
!Info ^ new_analysis_results := map.init.
:- pred update_analysis_registry_2(module_info::in, module_name::in,
module_analysis_map(some_analysis_result)::in,
analysis_info::in, analysis_info::out, io::di, io::uo) is det.
update_analysis_registry_2(ModuleInfo, ModuleName, ModuleMap, !Info, !IO) :-
map.foldl2(update_analysis_registry_3(ModuleInfo, ModuleName), ModuleMap,
!Info, !IO).
:- pred update_analysis_registry_3(module_info::in, module_name::in,
analysis_name::in, func_analysis_map(some_analysis_result)::in,
analysis_info::in, analysis_info::out, io::di, io::uo) is det.
update_analysis_registry_3(ModuleInfo, ModuleName, AnalysisName, FuncMap,
!Info, !IO) :-
map.foldl2(
update_analysis_registry_4(ModuleInfo, ModuleName, AnalysisName),
FuncMap, !Info, !IO).
:- pred update_analysis_registry_4(module_info::in, module_name::in,
analysis_name::in, func_id::in, list(some_analysis_result)::in,
analysis_info::in, analysis_info::out, io::di, io::uo) is det.
update_analysis_registry_4(ModuleInfo, ModuleName, AnalysisName, FuncId,
NewResults, !Info, !IO) :-
% XXX Currently we do not prevent there being more than one recorded result
% for a given call pattern.
list.foldl2(
update_analysis_registry_5(ModuleInfo, ModuleName, AnalysisName,
FuncId),
NewResults, !Info, !IO).
:- pred update_analysis_registry_5(module_info::in, module_name::in,
analysis_name::in, func_id::in, some_analysis_result::in,
analysis_info::in, analysis_info::out, io::di, io::uo) is det.
update_analysis_registry_5(ModuleInfo, ModuleName, AnalysisName, FuncId,
NewResult, !Info, !IO) :-
NewResult = some_analysis_result(Call, NewAnswer, NewStatus),
get_func_info(ModuleInfo, ModuleName, FuncId, Call, NewAnswer, FuncInfo),
lookup_exactly_matching_result_even_from_invalid_modules(!.Info,
ModuleName, FuncId, FuncInfo, Call, MaybeResult),
module_info_get_globals(ModuleInfo, Globals),
(
MaybeResult = yes(OldResult),
% There was a previous answer for this call pattern.
OldResult = analysis_result(_OldCall, OldAnswer, OldStatus),
( if equivalent(FuncInfo, NewAnswer, OldAnswer) then
get_debug_analysis_stream(MaybeDebugStream, !IO),
(
MaybeDebugStream = no
;
MaybeDebugStream = yes(DebugStream),
write_no_change_in_result(DebugStream, ModuleName, FuncId,
Call, NewAnswer, !IO)
),
( if NewStatus = OldStatus then
true
else
OldMap0 = !.Info ^ old_analysis_results,
replace_result_in_analysis_map(ModuleName, FuncId, FuncInfo,
Call, NewAnswer, NewStatus, OldMap0, OldMap),
!Info ^ old_analysis_results := OldMap
)
else
% Answer has changed.
% Replace the old answer in the registry with the new answer.
OldMap0 = !.Info ^ old_analysis_results,
replace_result_in_analysis_map(ModuleName, FuncId, FuncInfo,
Call, NewAnswer, NewStatus, OldMap0, OldMap),
!Info ^ old_analysis_results := OldMap,
% If the answer is more precise than before then dependent modules
% should be marked suboptimal. Otherwise the answer is less precise
% than it was before, so dependent modules should be invalidated.
( if more_precise_than(FuncInfo, NewAnswer, OldAnswer) then
Status = suboptimal
else
Status = invalid
),
map.lookup(!.Info ^ old_imdg_map, ModuleName, OldArcs),
DepModules = imdg_dependent_modules(OldArcs, AnalysisName,
FuncId, FuncInfo, Call),
get_debug_analysis_stream(MaybeDebugStream, !IO),
(
MaybeDebugStream = no
;
MaybeDebugStream = yes(DebugStream),
write_changed_answer(DebugStream, OldAnswer, NewAnswer,
Status, DepModules, !IO)
),
set.fold2(taint_module_overall_status(Globals, Status), DepModules,
!Info, !IO)
)
;
MaybeResult = no,
% There was no previous answer for this call pattern.
% Just add this result to the registry.
OldAnalysisResults0 = !.Info ^ old_analysis_results,
map.lookup(OldAnalysisResults0, ModuleName, OldMap0),
record_result_in_analysis_map(FuncId, Call, NewAnswer, NewStatus,
OldMap0, OldMap),
map.det_update(ModuleName, OldMap,
OldAnalysisResults0, OldAnalysisResults),
!Info ^ old_analysis_results := OldAnalysisResults
),
% If this new result satisfies a request, then mark the requesting modules
% as suboptimal so they can be reanalysed.
%
% Ideally, we could compare the new answer with either a default answer
% that the calling module probably used, or each request could optionally
% record what answer the caller assumed. Then we could avoid reanalysing
% the calling module unnecessarily. (This only reason we don't implement
% the former is that the structure reuse analysis doesn't implement
% the `top' typeclass method.)
( if
map.search(!.Info ^ analysis_requests_map, ModuleName, ModuleRequests),
map.search(ModuleRequests, AnalysisName, ModuleAnalysisMap),
map.search(ModuleAnalysisMap, FuncId, Requests),
Requests = [_ | _]
then
Callers0 = list.filter_map(
( func(analysis_request(Call0, Caller)) = Caller is semidet :-
univ(Call0) = univ(Call0)
), Requests),
list.sort_and_remove_dups(Callers0, Callers),
list.foldl2(taint_module_overall_status(Globals, suboptimal), Callers,
!Info, !IO)
else
true
).
% Replace an analysis result for the given function/call pattern with a
% new result. A previous result _must_ already exist in the map with
% exactly the same call pattern.
%
:- pred replace_result_in_analysis_map(module_name::in, func_id::in,
FuncInfo::in, Call::in, Answer::in, analysis_status::in,
analysis_map(some_analysis_result)::in,
analysis_map(some_analysis_result)::out) is det
<= analysis(FuncInfo, Call, Answer).
replace_result_in_analysis_map(ModuleName, FuncId, FuncInfo,
CallPattern, AnswerPattern, Status, Map0, Map) :-
AnalysisName = analysis_name(CallPattern, AnswerPattern),
ModuleResults0 = map.lookup(Map0, ModuleName),
AnalysisResults0 = map.lookup(ModuleResults0, AnalysisName),
FuncResults0 = map.lookup(AnalysisResults0, FuncId),
replace_result_in_list(FuncInfo, CallPattern, AnswerPattern, Status,
FuncResults0, FuncResults),
Map = map.det_update(Map0, ModuleName,
map.det_update(ModuleResults0, AnalysisName,
map.det_update(AnalysisResults0, FuncId, FuncResults))).
:- pred replace_result_in_list(FuncInfo::in, Call::in, Answer::in,
analysis_status::in,
list(some_analysis_result)::in, list(some_analysis_result)::out) is det
<= analysis(FuncInfo, Call, Answer).
replace_result_in_list(FuncInfo, Call, Answer, Status, Results0, Results) :-
(
Results0 = [],
unexpected($pred, "found no result to replace")
;
Results0 = [H0 | T0],
det_univ_to_type(univ(H0 ^ some_ar_call), HCall),
( if equivalent(FuncInfo, Call, HCall) then
H = 'new some_analysis_result'(Call, Answer, Status),
T = T0
else
H = H0,
replace_result_in_list(FuncInfo, Call, Answer, Status, T0, T)
),
Results = [H | T]
).
:- func imdg_dependent_modules(module_analysis_map(imdg_arc), analysis_name,
func_id, FuncInfo, Call) = set(module_name)
<= call_pattern(FuncInfo, Call).
imdg_dependent_modules(ModuleMap, AnalysisName, FuncId, FuncInfo, Call)
= ModuleNameSet :-
( if
map.search(ModuleMap, AnalysisName, FuncAnalysisMap),
map.search(FuncAnalysisMap, FuncId, IMDGEntries)
then
list.filter_map(arc_module_name(FuncInfo, Call),
IMDGEntries, ModuleNames),
set.list_to_set(ModuleNames, ModuleNameSet)
else
set.init(ModuleNameSet)
).
:- pred arc_module_name(FuncInfo::in, Call::in, imdg_arc::in, module_name::out)
is semidet <= call_pattern(FuncInfo, Call).
arc_module_name(FuncInfo, CallA, imdg_arc(CallB0, ModuleName), ModuleName) :-
det_univ_to_type(univ(CallB0), CallB),
equivalent(FuncInfo, CallA, CallB).
:- pred taint_module_overall_status(globals::in, analysis_status::in,
module_name::in, analysis_info::in, analysis_info::out,
io::di, io::uo) is det.
taint_module_overall_status(Globals, Status, ModuleName, !Info, !IO) :-
(
Status = optimal
;
( Status = suboptimal
; Status = invalid
),
% We may not have read the overall status for this module yet.
% Even though we loaded all the analysis files of modules reachable
% from the initial module beforehand, a _caller_ of the initial module
% may not be part of that set.
ensure_module_status_loaded(Globals, ModuleName, !Info, !IO),
ModuleStatusMap0 = !.Info ^ module_status_map,
map.lookup(ModuleStatusMap0, ModuleName, ModuleStatus0),
ModuleStatus = lub(ModuleStatus0, Status),
get_debug_analysis_stream(MaybeDebugStream, !IO),
(
MaybeDebugStream = no
;
MaybeDebugStream = yes(DebugStream),
write_tainting_module(DebugStream, ModuleName, ModuleStatus, !IO)
),
map.set(ModuleName, ModuleStatus, ModuleStatusMap0, ModuleStatusMap),
!Info ^ module_status_map := ModuleStatusMap
).
:- pred ensure_module_status_loaded(globals::in, module_name::in,
analysis_info::in, analysis_info::out, io::di, io::uo) is det.
ensure_module_status_loaded(Globals, ModuleName, !Info, !IO) :-
ModuleStatusMap0 = !.Info ^ module_status_map,
( if map.contains(ModuleStatusMap0, ModuleName) then
true
else
do_read_module_overall_status(!.Info ^ compiler, Globals, ModuleName,
ModuleStatus, !IO),
map.det_insert(ModuleName, ModuleStatus,
ModuleStatusMap0, ModuleStatusMap),
!Info ^ module_status_map := ModuleStatusMap
).
:- pred write_no_change_in_result(io.text_output_stream::in,
module_name::in, func_id::in, Call::in, Answer::in, io::di, io::uo) is det.
write_no_change_in_result(OutStream, ModuleName, FuncId,
Call, NewAnswer, !IO) :-
ModuleNameStr = sym_name_to_string(ModuleName),
FuncIdStr = string.string(FuncId),
CallStr = string.string(Call),
NewAnswerStr = string.string(NewAnswer),
io.format(OutStream, "%% No change in the result %s.%s: %s --> %s\n",
[s(ModuleNameStr), s(FuncIdStr), s(CallStr), s(NewAnswerStr)], !IO).
:- pred write_changed_answer(io.text_output_stream::in, Answer::in, Answer::in,
analysis_status::in, set(module_name)::in, io::di, io::uo) is det.
write_changed_answer(OutStream, OldAnswer, NewAnswer, Status,
DepModules, !IO) :-
OldAnswerStr = string.string(OldAnswer),
NewAnswerStr = string.string(NewAnswer),
StatusStr = string.string(Status),
DepModulesStr = string.string(DepModules),
io.format(OutStream, "%% %s changed to %s\n",
[s(OldAnswerStr), s(NewAnswerStr)], !IO),
io.format(OutStream, "Mark dependent modules as %s\n",
[s(StatusStr)], !IO),
% XXX This will be hard to read.
io.format(OutStream, "The modules to mark are: %s\n",
[s(DepModulesStr)], !IO).
:- pred write_tainting_module(io.text_output_stream::in,
module_name::in, analysis_status::in, io::di, io::uo) is det.
write_tainting_module(OutStream, ModuleName, ModuleStatus, !IO) :-
ModuleNameStr = sym_name_to_string(ModuleName),
ModuleStatusStr = string.string(ModuleStatus),
io.format(OutStream,
"%% Tainting the overall module status of %s with %s\n",
[s(ModuleNameStr), s(ModuleStatusStr)], !IO).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
% For each module N imported by M:
% Delete all entries leading to module M from N's IMDG:
% For each P^M:DP in S (call patterns to analyse):
% add P^M:DP --> Q^N:DQ to N's IMDG
%
:- pred update_intermodule_dependencies(module_name::in, set(module_name)::in,
analysis_info::in, analysis_info::out) is det.
update_intermodule_dependencies(ModuleName, LocalImportedModules, !Info) :-
set.fold(update_intermodule_dependencies_2(ModuleName),
LocalImportedModules, !Info).
:- pred update_intermodule_dependencies_2(module_name::in, module_name::in,
analysis_info::in, analysis_info::out) is det.
update_intermodule_dependencies_2(ModuleName, ImportedModuleName, !Info) :-
map.lookup(!.Info ^ old_imdg_map, ImportedModuleName, IMDG0),
trace [io(!IO)] (
get_debug_analysis_stream(MaybeDebugStream, !IO),
(
MaybeDebugStream = no
;
MaybeDebugStream = yes(DebugStream),
write_clearing_entries(DebugStream,
ModuleName, ImportedModuleName, !IO)
)
),
clear_imdg_entries_pointing_at(ModuleName, IMDG0, IMDG1),
( if map.search(!.Info ^ new_imdg_map, ImportedModuleName, NewArcs) then
map.union(combine_func_imdg, IMDG1, NewArcs, IMDG)
else
IMDG = IMDG1
),
OldIMDGMap0 = !.Info ^ old_imdg_map,
NewIMDGMap0 = !.Info ^ new_imdg_map,
map.set(ImportedModuleName, IMDG, OldIMDGMap0, OldIMDGMap),
map.delete(ImportedModuleName, NewIMDGMap0, NewIMDGMap),
!Info ^ old_imdg_map := OldIMDGMap,
!Info ^ new_imdg_map := NewIMDGMap.
:- pred write_clearing_entries(io.text_output_stream::in,
module_name::in, module_name::in, io::di, io::uo) is det.
write_clearing_entries(OutStream, ModuleName, ImportedModuleName, !IO) :-
ModuleNameStr = sym_name_to_string(ModuleName),
ImportedModuleNameStr = sym_name_to_string(ImportedModuleName),
io.format(OutStream, "%% Clearing entries involving %s from %s's IMDG.",
[s(ModuleNameStr), s(ImportedModuleNameStr)], !IO).
:- pred clear_imdg_entries_pointing_at(module_name::in,
module_analysis_map(imdg_arc)::in,
module_analysis_map(imdg_arc)::out) is det.
clear_imdg_entries_pointing_at(ModuleName, Map0, Map) :-
map.map_values_only(clear_imdg_entries_pointing_at_2(ModuleName),
Map0, Map).
:- pred clear_imdg_entries_pointing_at_2(module_name::in,
func_analysis_map(imdg_arc)::in,
func_analysis_map(imdg_arc)::out) is det.
clear_imdg_entries_pointing_at_2(ModuleName, FuncMap0, FuncMap) :-
map.map_values_only(clear_imdg_entries_pointing_at_3(ModuleName),
FuncMap0, FuncMap).
:- pred clear_imdg_entries_pointing_at_3(module_name::in,
list(imdg_arc)::in, list(imdg_arc)::out) is det.
clear_imdg_entries_pointing_at_3(ModuleName, Arcs0, Arcs) :-
list.filter((pred(Arc::in) is semidet :- Arc ^ imdg_caller \= ModuleName),
Arcs0, Arcs).
:- pred combine_func_imdg(func_analysis_map(imdg_arc)::in,
func_analysis_map(imdg_arc)::in, func_analysis_map(imdg_arc)::out) is det.
combine_func_imdg(FuncImdgA, FuncImdgB, FuncImdg) :-
map.union(combine_imdg_lists, FuncImdgA, FuncImdgB, FuncImdg).
:- pred combine_imdg_lists(list(imdg_arc)::in, list(imdg_arc)::in,
list(imdg_arc)::out) is det.
combine_imdg_lists(ArcsA, ArcsB, ArcsA ++ ArcsB).
%---------------------------------------------------------------------------%
prepare_intermodule_analysis(ProgressStream, Globals,
ImportedModuleNames0, LocalModuleNames, Specs, !Info, !IO) :-
% NOTE The original code of the analysis package threw exceptions
% when it found version number mismatches or syntax errors in the files
% it tried to read. There was no recovery from these exceptions,
% so a single compiler invocation could "report" only one such problem.
% (If you can call a message immediately preceding a compiler abort
% a "report".)
%
% The current code of this package, when it finds those same problems,
% just creates an error_spec, adds it to the list, and continues.
% This can report an unbounded number of errors per compiler invocation,
% and unlike the old system, it also prints the context of each problem.
% However, the current system assumes that the problems whose error_specs
% we gather will not cause later computations to abort (e.g. by causing
% map.lookups to fail) by breaking what were assumed to be invariants.
% This is mostly because I (zs) *cannot find* any mention of any such
% invariants.
ThisModule = !.Info ^ this_module,
ImportedModuleNames = set.delete(ImportedModuleNames0, ThisModule),
!Info ^ local_module_names := LocalModuleNames,
% Read in results for imported modules.
set.fold3(load_module_analysis_results(ProgressStream, Globals),
ImportedModuleNames, !Info, [], Specs0, !IO),
% Read in results and requests for the module being analysed.
load_module_analysis_results(ProgressStream, Globals, ThisModule, !Info,
Specs0, Specs1, !IO),
read_module_analysis_requests(!.Info, Globals, ThisModule,
ThisModuleRequests, Specs1, Specs, !IO),
(
Specs = [],
RequestsMap0 = !.Info ^ analysis_requests_map,
map.set(ThisModule, ThisModuleRequests, RequestsMap0, RequestsMap),
!Info ^ analysis_requests_map := RequestsMap
;
Specs = [_ | _]
).
:- pred load_module_analysis_results(io.text_output_stream::in, globals::in,
module_name::in, analysis_info::in, analysis_info::out,
list(error_spec)::in, list(error_spec)::out, io::di, io::uo) is det.
load_module_analysis_results(ProgressStream, Globals, ModuleName,
!Info, !Specs, !IO) :-
OldResultsMap0 = !.Info ^ old_analysis_results,
ModuleStatusMap0 = !.Info ^ module_status_map,
( if
( map.contains(OldResultsMap0, ModuleName)
; map.contains(ModuleStatusMap0, ModuleName)
)
then
unexpected($pred, "ensure_old_module_analysis_results_loaded")
else
do_read_module_overall_status(!.Info ^ compiler, Globals, ModuleName,
ModuleStatus, !IO),
read_module_analysis_results(ProgressStream, !.Info, Globals,
ModuleName, ModuleResults, ModuleSpecs, !IO),
(
ModuleSpecs = [],
map.det_insert(ModuleName, ModuleStatus,
ModuleStatusMap0, ModuleStatusMap),
map.det_insert(ModuleName, ModuleResults,
OldResultsMap0, OldResultsMap),
!Info ^ module_status_map := ModuleStatusMap,
!Info ^ old_analysis_results := OldResultsMap
;
ModuleSpecs = [_ | _],
!:Specs = ModuleSpecs ++ !.Specs
)
).
module_is_local(Info, ModuleName, IsLocal) :-
( if set.contains(Info ^ local_module_names, ModuleName) then
IsLocal = yes
else
IsLocal = no
).
%---------------------------------------------------------------------------%
write_analysis_files(ProgressStream, Compiler, ModuleInfo, ImportedModules0,
!.Info, Specs, !IO) :-
% In this procedure we have just finished compiling module ModuleName
% and will write out data currently cached in the analysis_info structure
% out to disk.
% XXX Where exactly "on disk"?
ThisModule = !.Info ^ this_module,
ImportedModules = set.delete(ImportedModules0, ThisModule),
LocalModules = !.Info ^ local_module_names,
set.intersect(LocalModules, ImportedModules, LocalImportedModules),
% Load IMDG files for local modules.
module_info_get_globals(ModuleInfo, Globals),
set.fold3(load_module_imdg(Globals), LocalModules, !Info, [], Specs, !IO),
(
Specs = [_ | _]
% Do not go any further if we had any errors while reading IMDG
% information. Return Specs to be printed by one of our ancestors.
;
Specs = [],
update_analysis_registry(ModuleInfo, !Info, !IO),
% The current module was just compiled, so we set its status to the
% lub of all the new analysis results generated.
ModuleStatus = lub_result_statuses(!.Info ^ new_analysis_results),
ModuleStatusMap0 = !.Info ^ module_status_map,
map.set(ThisModule, ModuleStatus, ModuleStatusMap0, ModuleStatusMap),
!Info ^ module_status_map := ModuleStatusMap,
update_intermodule_dependencies(ThisModule, LocalImportedModules,
!Info),
( if map.is_empty(!.Info ^ new_analysis_results) then
true
else
unexpected($pred, "new_analysis_results is not empty")
),
% Write the module statuses for all local modules (not necessarily
% imported).
set.fold(maybe_write_module_overall_status(!.Info, Globals),
LocalModules, !IO),
% Write the analysis results for the current module.
map.lookup(!.Info ^ old_analysis_results, ThisModule, ModuleResults),
write_module_analysis_results(ProgressStream, !.Info, Globals,
ThisModule, ModuleResults, !IO),
% Write the requests for imported local modules.
set.fold(maybe_write_module_requests(!.Info, Globals),
LocalImportedModules, !IO),
% Remove the requests for the current module since we (should have)
% fulfilled them in this pass.
empty_request_file(!.Info, Globals, ThisModule, !IO),
% Write the intermodule dependency graphs.
set.fold(maybe_write_module_imdg(!.Info, Globals),
LocalImportedModules, !IO),
% Touch a timestamp file to indicate the last time that
% this module was analysed.
% XXX A grep for this extension shows that the compiler
% never actually even tries to look at the files we write here.
ExtDate = ext_cur_ngs_gs(ext_cur_ngs_gs_an_analysis_date),
module_name_to_write_file_name(Compiler, Globals, ExtDate,
ThisModule, TimestampFileName, !IO),
touch_file_datestamp(Globals, ProgressStream, TimestampFileName,
_Succeeded, !IO)
).
:- pred load_module_imdg(globals::in, module_name::in,
analysis_info::in, analysis_info::out,
list(error_spec)::in, list(error_spec)::out, io::di, io::uo) is det.
load_module_imdg(Globals, ModuleName, !Info, !Specs, !IO) :-
read_module_imdg(!.Info, Globals, ModuleName, IMDG, ModuleSpecs, !IO),
(
ModuleSpecs = [],
OldIMDGMap0 = !.Info ^ old_imdg_map,
map.det_insert(ModuleName, IMDG, OldIMDGMap0, OldIMDGMap),
!Info ^ old_imdg_map := OldIMDGMap
;
ModuleSpecs = [_ | _],
!:Specs = ModuleSpecs ++ !.Specs
).
:- pred maybe_write_module_overall_status(analysis_info::in, globals::in,
module_name::in, io::di, io::uo) is det.
maybe_write_module_overall_status(Info, Globals, ModuleName, !IO) :-
( if map.search(Info ^ module_status_map, ModuleName, Status) then
write_module_overall_status(Info, Globals, ModuleName, Status, !IO)
else
% We didn't have any reason to read in the status of this module
% so we have no reason to touch it either.
true
).
:- pred maybe_write_module_requests(analysis_info::in, globals::in,
module_name::in, io::di, io::uo) is det.
maybe_write_module_requests(Info, Globals, ModuleName, !IO) :-
( if map.search(Info ^ analysis_requests_map, ModuleName, Requests) then
write_module_analysis_requests(Info, Globals, ModuleName,
Requests, !IO)
else
true
).
:- pred maybe_write_module_imdg(analysis_info::in, globals::in,
module_name::in, io::di, io::uo) is det.
maybe_write_module_imdg(Info, Globals, ModuleName, !IO) :-
( if map.search(Info ^ old_imdg_map, ModuleName, ModuleEntries) then
write_module_imdg(Info, Globals, ModuleName, ModuleEntries, !IO)
else
true
).
%---------------------------------------------------------------------------%
do_read_module_overall_status(Compiler, Globals, ModuleName,
ModuleStatus, !IO) :-
read_module_overall_status(Compiler, Globals, ModuleName,
ModuleStatus, !IO).
%---------------------------------------------------------------------------%
:- func lub_result_statuses(module_analysis_map(some_analysis_result))
= analysis_status.
lub_result_statuses(ModuleMap) =
map.foldl(lub_result_statuses_2, ModuleMap, optimal).
:- func lub_result_statuses_2(analysis_name,
func_analysis_map(some_analysis_result), analysis_status) =
analysis_status.
lub_result_statuses_2(_AnalysisName, FuncMap, Acc) =
map.foldl(lub_result_statuses_3, FuncMap, Acc).
:- func lub_result_statuses_3(func_id, list(some_analysis_result),
analysis_status) = analysis_status.
lub_result_statuses_3(_FuncId, Results, Acc) =
list.foldl(lub_result_statuses_4, Results, Acc).
:- func lub_result_statuses_4(some_analysis_result, analysis_status)
= analysis_status.
lub_result_statuses_4(Result, Acc) = lub(Result ^ some_ar_status, Acc).
%---------------------------------------------------------------------------%
:- mutable(debug_analysis_stream, maybe(io.text_output_stream), no, ground,
[untrailed, attach_to_io_state]).
get_analysis_debug_stream(MaybeDebugStream, !IO) :-
get_debug_analysis_stream(MaybeDebugStream, !IO).
set_analysis_debug_stream(MaybeDebugStream, !IO) :-
set_debug_analysis_stream(MaybeDebugStream, !IO).
%---------------------------------------------------------------------------%
:- end_module analysis.operations.
%---------------------------------------------------------------------------%