mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-15 17:33:38 +00:00
1377 lines
53 KiB
Mathematica
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.
|
|
%---------------------------------------------------------------------------%
|