mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-16 14:25:56 +00:00
Branches: main
Change the argument order of many of the predicates in the map, bimap, and
multi_map modules so they are more conducive to the use of state variable
notation, i.e. make the order the same as in the sv* modules.
Prepare for the deprecation of the sv{bimap,map,multi_map} modules by
removing their use throughout the system.
library/bimap.m:
library/map.m:
library/multi_map.m:
As above.
NEWS:
Announce the change.
Separate out the "highlights" from the "detailed listing" for
the post-11.01 NEWS.
Reorganise the announcement of the Unicode support.
benchmarks/*/*.m:
browser/*.m:
compiler/*.m:
deep_profiler/*.m:
extras/*/*.m:
mdbcomp/*.m:
profiler/*.m:
tests/*/*.m:
ssdb/*.m:
samples/*/*.m
slice/*.m:
Conform to the above change.
Remove any dependencies on the sv{bimap,map,multi_map} modules.
1062 lines
38 KiB
Mathematica
1062 lines
38 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 2003, 2005-2011 The University of Melbourne.
|
|
% 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.file.m
|
|
% Main author: stayl, wangp.
|
|
%
|
|
% This module deals with the on-disk representations of the analysis registry
|
|
% and associated files.
|
|
%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- module analysis.file.
|
|
:- interface.
|
|
|
|
% read_module_overall_status(Compiler, Globals, ModuleName,
|
|
% MaybeModuleStatus, !IO)
|
|
%
|
|
% Read the overall status of a module from its `.analysis_status' file.
|
|
% If the module has outstanding requests, then an overall status of
|
|
% `optimal' is downgraded to `suboptimal'.
|
|
%
|
|
:- pred read_module_overall_status(Compiler::in, globals::in, module_name::in,
|
|
analysis_status::out, io::di, io::uo) is det <= compiler(Compiler).
|
|
|
|
% write_module_overall_status(AnalysisInfo, Globals, ModuleName,
|
|
% ModuleStatus, !IO)
|
|
%
|
|
% Write the status of a module to its `.analysis_status' file.
|
|
%
|
|
:- pred write_module_overall_status(analysis_info::in, globals::in,
|
|
module_name::in, analysis_status::in, io::di, io::uo) is det.
|
|
|
|
% read_module_analysis_results(AnalysisInfo, Globals, ModuleName,
|
|
% AnalysisResults, !IO)
|
|
%
|
|
% Read the analysis results from a `.analysis' file,
|
|
% or from the analysis file cache (if enabled, and the cache file is
|
|
% up-to-date).
|
|
%
|
|
:- pred read_module_analysis_results(analysis_info::in, globals::in,
|
|
module_name::in, module_analysis_map(some_analysis_result)::out,
|
|
io::di, io::uo) is det.
|
|
|
|
% write_module_analysis_results(AnalysisInfo, Globals, ModuleName,
|
|
% AnalysisResults, !IO)
|
|
%
|
|
% Write the analysis results for a module to its `.analysis' file.
|
|
% Optionally, also write the cache copy of the analysis file.
|
|
%
|
|
:- pred write_module_analysis_results(analysis_info::in, globals::in,
|
|
module_name::in, module_analysis_map(some_analysis_result)::in,
|
|
io::di, io::uo) is det.
|
|
|
|
% read_module_analysis_requests(AnalysisInfo, Globals, ModuleName,
|
|
% ModuleRequests, !IO)
|
|
%
|
|
% Read outstanding analysis requests to a module from disk.
|
|
%
|
|
:- pred read_module_analysis_requests(analysis_info::in, globals::in,
|
|
module_name::in, module_analysis_map(analysis_request)::out,
|
|
io::di, io::uo) is det.
|
|
|
|
% write_module_analysis_requests(AnalysisInfo, Globals, ModuleName,
|
|
% ModuleRequests, !IO)
|
|
%
|
|
% Write outstanding analysis requests for a module to disk.
|
|
%
|
|
:- pred write_module_analysis_requests(analysis_info::in, globals::in,
|
|
module_name::in, module_analysis_map(analysis_request)::in,
|
|
io::di, io::uo) is det.
|
|
|
|
% read_module_imdg(AnalysisInfo, Globals, ModuleName, ModuleEntries, !IO)
|
|
%
|
|
% Read the intermodule dependencies graph entries for a module from disk.
|
|
%
|
|
:- pred read_module_imdg(analysis_info::in, globals::in, module_name::in,
|
|
module_analysis_map(imdg_arc)::out, io::di, io::uo) is det.
|
|
|
|
% write_module_imdg(AnalysisInfo, Globals, ModuleName, ModuleEntries, !IO)
|
|
%
|
|
% Write the intermodule dependencies graph entries for a module to disk.
|
|
%
|
|
:- pred write_module_imdg(analysis_info::in, globals::in, module_name::in,
|
|
module_analysis_map(imdg_arc)::in, io::di, io::uo) is det.
|
|
|
|
% empty_request_file(AnalysisInfo, Globals, ModuleName, !IO)
|
|
%
|
|
% Delete the file containing outstanding analysis requests for a module.
|
|
% This means all the analysis requests should have been satisfied already.
|
|
%
|
|
:- pred empty_request_file(analysis_info::in, globals::in, module_name::in,
|
|
io::di, io::uo) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module libs.globals.
|
|
:- import_module libs.options.
|
|
:- import_module libs.pickle.
|
|
:- import_module parse_tree.
|
|
:- import_module parse_tree.module_cmds. % XXX unwanted dependency
|
|
:- import_module parse_tree.prog_io_sym_name.
|
|
:- import_module parse_tree.prog_out.
|
|
|
|
:- import_module bool.
|
|
:- import_module char.
|
|
:- import_module dir.
|
|
:- import_module exception.
|
|
:- import_module parser.
|
|
:- import_module require.
|
|
:- import_module term.
|
|
:- import_module term_io.
|
|
:- import_module type_desc.
|
|
:- import_module univ.
|
|
:- import_module varset.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% The format of an analysis result file is:
|
|
%
|
|
% version_number.
|
|
% analysis_name(analysis_version, func_id, call_pattern, answer_pattern,
|
|
% result_status).
|
|
%
|
|
% where func_id = p(name, arity, mode_id).
|
|
% or func_id = f(name, arity, mode_id).
|
|
|
|
% An .analysis_status file contains a single line, which is one of:
|
|
%
|
|
% optimal.
|
|
% suboptimal.
|
|
% invalid.
|
|
%
|
|
% A missing file is equivalent to `optimal'.
|
|
|
|
% The format of an IMDG file is:
|
|
%
|
|
% version_number.
|
|
% calling_module -> analysis_name(analysis_version, func_id, call_pattern).
|
|
|
|
% The format of an analysis request file is:
|
|
%
|
|
% version_number.
|
|
% calling_module -> analysis_name(analysis_version, func_id, call_pattern).
|
|
|
|
:- type invalid_analysis_file
|
|
---> invalid_analysis_file(string).
|
|
|
|
:- func version_number = int.
|
|
|
|
version_number = 6.
|
|
|
|
:- func analysis_registry_suffix = string.
|
|
|
|
analysis_registry_suffix = ".analysis".
|
|
|
|
:- func analysis_registry_status_suffix = string.
|
|
|
|
analysis_registry_status_suffix = ".analysis_status".
|
|
|
|
:- func imdg_suffix = string.
|
|
|
|
imdg_suffix = ".imdg".
|
|
|
|
:- func request_suffix = string.
|
|
|
|
request_suffix = ".request".
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred analysis_status_to_string(analysis_status, string).
|
|
:- mode analysis_status_to_string(in, out) is det.
|
|
:- mode analysis_status_to_string(out, in) is semidet.
|
|
|
|
analysis_status_to_string(invalid, "invalid").
|
|
analysis_status_to_string(suboptimal, "suboptimal").
|
|
analysis_status_to_string(optimal, "optimal").
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Reading.
|
|
%
|
|
|
|
:- type parse_entry(T) == pred(term, T, T).
|
|
:- inst parse_entry == (pred(in, in, out) is det).
|
|
|
|
read_module_overall_status(Compiler, Globals, ModuleName, ModuleStatus, !IO) :-
|
|
module_name_to_read_file_name(Compiler, Globals, ModuleName,
|
|
analysis_registry_status_suffix, MaybeFileName, !IO),
|
|
(
|
|
MaybeFileName = ok(FileName),
|
|
read_module_overall_status_2(FileName, ModuleStatus0, !IO)
|
|
;
|
|
MaybeFileName = error(_),
|
|
% Missing file means optimal. We don't install `.analysis_status'
|
|
% files when installing libraries, for example.
|
|
ModuleStatus0 = optimal
|
|
),
|
|
(
|
|
ModuleStatus0 = optimal,
|
|
module_name_to_read_file_name(Compiler, Globals, ModuleName,
|
|
request_suffix, MaybeRequestFileName, !IO),
|
|
(
|
|
% There are outstanding requests for this module.
|
|
MaybeRequestFileName = ok(_),
|
|
ModuleStatus = suboptimal
|
|
;
|
|
MaybeRequestFileName = error(_),
|
|
ModuleStatus = ModuleStatus0
|
|
)
|
|
;
|
|
( ModuleStatus0 = suboptimal
|
|
; ModuleStatus0 = invalid
|
|
),
|
|
ModuleStatus = ModuleStatus0
|
|
).
|
|
|
|
:- pred read_module_overall_status_2(string::in, analysis_status::out,
|
|
io::di, io::uo) is det.
|
|
|
|
read_module_overall_status_2(FileName, ModuleStatus, !IO) :-
|
|
io.open_input(FileName, OpenResult, !IO),
|
|
(
|
|
OpenResult = ok(Stream),
|
|
io.read_line_as_string(Stream, ReadResult, !IO),
|
|
io.close_input(Stream, !IO),
|
|
(
|
|
ReadResult = ok(String),
|
|
( string.prefix(String, "optimal.") ->
|
|
ModuleStatus = optimal
|
|
; string.prefix(String, "suboptimal.") ->
|
|
ModuleStatus = suboptimal
|
|
; string.prefix(String, "invalid.") ->
|
|
ModuleStatus = invalid
|
|
;
|
|
unexpected(this_file,
|
|
"read_module_overall_status_2: unexpected line")
|
|
)
|
|
;
|
|
ReadResult = eof,
|
|
unexpected(this_file,
|
|
"read_module_overall_status_2: unexpected eof")
|
|
;
|
|
ReadResult = error(IOError),
|
|
unexpected(this_file,
|
|
"read_module_overall_status_2: " ++ io.error_message(IOError))
|
|
)
|
|
;
|
|
OpenResult = error(IOError),
|
|
unexpected(this_file,
|
|
"read_module_overall_status_2: " ++ io.error_message(IOError))
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
read_module_analysis_results(Info, Globals, ModuleName, ModuleResults, !IO) :-
|
|
% If the module's overall status is `invalid' then at least one of its
|
|
% results is invalid. However, we can't just discard the results as we
|
|
% want to know which results change after we reanalyse the module.
|
|
Compiler = Info ^ compiler,
|
|
module_name_to_read_file_name(Compiler, Globals, ModuleName,
|
|
analysis_registry_suffix, MaybeAnalysisFileName, !IO),
|
|
(
|
|
MaybeAnalysisFileName = ok(AnalysisFileName),
|
|
|
|
% If analysis file caching is enabled, and the cache file exists and is
|
|
% up-to-date, then read from the cache instead.
|
|
globals.lookup_string_option(Globals, analysis_file_cache_dir,
|
|
CacheDir),
|
|
( CacheDir = "" ->
|
|
read_module_analysis_results_2(Compiler, AnalysisFileName,
|
|
ModuleResults, !IO)
|
|
;
|
|
CacheFileName = make_cache_filename(CacheDir, AnalysisFileName),
|
|
io.file_modification_time(AnalysisFileName, AnalysisTimeResult,
|
|
!IO),
|
|
io.file_modification_time(CacheFileName, CacheTimeResult, !IO),
|
|
(
|
|
AnalysisTimeResult = ok(AnalysisTime),
|
|
CacheTimeResult = ok(CacheTime),
|
|
CacheTime @>= AnalysisTime
|
|
->
|
|
Unpicklers = init_analysis_unpicklers(Compiler),
|
|
unpickle_from_file(Unpicklers, CacheFileName, UnpickleResult,
|
|
!IO),
|
|
(
|
|
UnpickleResult = ok(ModuleResults)
|
|
;
|
|
UnpickleResult = error(Error),
|
|
io.write_string("Error reading ", !IO),
|
|
io.write_string(CacheFileName, !IO),
|
|
io.write_string(": ", !IO),
|
|
io.write_string(io.error_message(Error), !IO),
|
|
io.nl(!IO),
|
|
read_module_analysis_results_2(Compiler, AnalysisFileName,
|
|
ModuleResults, !IO),
|
|
write_analysis_cache_file(CacheFileName, ModuleResults, !IO)
|
|
)
|
|
;
|
|
read_module_analysis_results_2(Compiler, AnalysisFileName,
|
|
ModuleResults, !IO),
|
|
write_analysis_cache_file(CacheFileName, ModuleResults, !IO)
|
|
)
|
|
)
|
|
;
|
|
MaybeAnalysisFileName = error(_),
|
|
ModuleResults = map.init
|
|
).
|
|
|
|
:- pred read_module_analysis_results_2(Compiler::in, string::in,
|
|
module_analysis_map(some_analysis_result)::out, io::di, io::uo) is det
|
|
<= compiler(Compiler).
|
|
|
|
read_module_analysis_results_2(Compiler, AnalysisFileName, ModuleResults,
|
|
!IO) :-
|
|
ModuleResults0 = map.init,
|
|
io.open_input(AnalysisFileName, OpenResult, !IO),
|
|
(
|
|
OpenResult = ok(Stream),
|
|
debug_msg((pred(!.IO::di, !:IO::uo) is det :-
|
|
io.write_string("% Reading analysis registry file ", !IO),
|
|
io.write_string(AnalysisFileName, !IO),
|
|
io.nl(!IO)
|
|
), !IO),
|
|
io.set_input_stream(Stream, OldStream, !IO),
|
|
|
|
check_analysis_file_version_number(!IO),
|
|
promise_equivalent_solutions [Results, !:IO] (
|
|
try_io(read_analysis_file_2(parse_result_entry(Compiler),
|
|
ModuleResults0), Results, !IO)
|
|
),
|
|
io.set_input_stream(OldStream, _, !IO),
|
|
io.close_input(Stream, !IO),
|
|
(
|
|
Results = succeeded(ModuleResults)
|
|
;
|
|
Results = exception(_),
|
|
rethrow(Results)
|
|
)
|
|
;
|
|
OpenResult = error(_),
|
|
debug_msg((pred(!.IO::di, !:IO::uo) is det :-
|
|
io.write_string("% Error reading analysis registry file: ", !IO),
|
|
io.write_string(AnalysisFileName, !IO),
|
|
io.nl(!IO)
|
|
), !IO),
|
|
ModuleResults = ModuleResults0
|
|
).
|
|
|
|
:- pred parse_result_entry(Compiler::in, term::in,
|
|
module_analysis_map(some_analysis_result)::in,
|
|
module_analysis_map(some_analysis_result)::out) is det
|
|
<= compiler(Compiler).
|
|
|
|
parse_result_entry(Compiler, Term, !Results) :-
|
|
(
|
|
Term = term.functor(term.atom(AnalysisName),
|
|
[VersionNumberTerm, FuncIdTerm,
|
|
CallPatternTerm, AnswerPatternTerm, StatusTerm], _),
|
|
StatusTerm = term.functor(term.atom(StatusString), [], _),
|
|
analysis_type(_ : unit(Call), _ : unit(Answer)) =
|
|
analyses(Compiler, AnalysisName),
|
|
|
|
parse_func_id(FuncIdTerm, FuncId),
|
|
from_term(CallPatternTerm, CallPattern : Call),
|
|
from_term(AnswerPatternTerm, AnswerPattern : Answer),
|
|
analysis_status_to_string(Status, StatusString)
|
|
->
|
|
(
|
|
VersionNumber = analysis_version_number(_ : Call, _ : Answer),
|
|
VersionNumberTerm = term.functor(
|
|
term.integer(VersionNumber), [], _)
|
|
->
|
|
Result = 'new some_analysis_result'(CallPattern, AnswerPattern,
|
|
Status),
|
|
( map.search(!.Results, AnalysisName, AnalysisResults0) ->
|
|
AnalysisResults1 = AnalysisResults0
|
|
;
|
|
AnalysisResults1 = map.init
|
|
),
|
|
( map.search(AnalysisResults1, FuncId, FuncResults0) ->
|
|
FuncResults = [Result | FuncResults0]
|
|
;
|
|
FuncResults = [Result]
|
|
),
|
|
map.set(FuncId, FuncResults, AnalysisResults1, AnalysisResults),
|
|
map.set(AnalysisName, AnalysisResults, !Results)
|
|
;
|
|
% Ignore results with an out-of-date version number.
|
|
true
|
|
)
|
|
;
|
|
Msg = "failed to parse result entry: " ++ string(Term),
|
|
throw(invalid_analysis_file(Msg))
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
read_module_analysis_requests(Info, Globals, ModuleName, ModuleRequests,
|
|
!IO) :-
|
|
read_analysis_file(Info ^ compiler, Globals, ModuleName, request_suffix,
|
|
parse_request_entry(Info ^ compiler),
|
|
map.init, ModuleRequests, !IO).
|
|
|
|
:- pred parse_request_entry(Compiler::in, term::in,
|
|
module_analysis_map(analysis_request)::in,
|
|
module_analysis_map(analysis_request)::out) is det
|
|
<= compiler(Compiler).
|
|
|
|
parse_request_entry(Compiler, Term, !Requests) :-
|
|
(
|
|
Term = term.functor(atom("->"), [CallerModuleTerm, RHS], _),
|
|
RHS = term.functor(atom(AnalysisName),
|
|
[VersionNumberTerm, FuncIdTerm, CallPatternTerm], _),
|
|
analysis_type(_ : unit(Call), _ : unit(Answer)) =
|
|
analyses(Compiler, AnalysisName),
|
|
|
|
try_parse_module_name(CallerModuleTerm, CallerModule),
|
|
parse_func_id(FuncIdTerm, FuncId),
|
|
from_term(CallPatternTerm, CallPattern : Call)
|
|
->
|
|
(
|
|
VersionNumber = analysis_version_number(_ : Call, _ : Answer),
|
|
VersionNumberTerm = term.functor(
|
|
term.integer(VersionNumber), [], _)
|
|
->
|
|
Result = 'new analysis_request'(CallPattern, CallerModule),
|
|
( map.search(!.Requests, AnalysisName, AnalysisRequests0) ->
|
|
AnalysisRequests1 = AnalysisRequests0
|
|
;
|
|
AnalysisRequests1 = map.init
|
|
),
|
|
( map.search(AnalysisRequests1, FuncId, FuncRequests0) ->
|
|
FuncRequests = [Result | FuncRequests0]
|
|
;
|
|
FuncRequests = [Result]
|
|
),
|
|
map.set(FuncId, FuncRequests, AnalysisRequests1, AnalysisRequests),
|
|
map.set(AnalysisName, AnalysisRequests, !Requests)
|
|
;
|
|
% Ignore requests with an out-of-date version number.
|
|
true
|
|
)
|
|
;
|
|
Msg = "failed to parse request entry: " ++ string(Term),
|
|
throw(invalid_analysis_file(Msg))
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
read_module_imdg(Info, Globals, ModuleName, ModuleEntries, !IO) :-
|
|
read_analysis_file(Info ^ compiler, Globals, ModuleName, imdg_suffix,
|
|
parse_imdg_arc(Info ^ compiler), map.init, ModuleEntries, !IO).
|
|
|
|
:- pred parse_imdg_arc(Compiler::in, term::in,
|
|
module_analysis_map(imdg_arc)::in, module_analysis_map(imdg_arc)::out)
|
|
is det <= compiler(Compiler).
|
|
|
|
parse_imdg_arc(Compiler, Term, !Arcs) :-
|
|
(
|
|
Term = term.functor(atom("->"), [DependentModuleTerm, ResultTerm], _),
|
|
ResultTerm = functor(atom(AnalysisName),
|
|
[VersionNumberTerm, FuncIdTerm, CallPatternTerm], _),
|
|
analysis_type(_ : unit(Call), _ : unit(Answer))
|
|
= analyses(Compiler, AnalysisName),
|
|
|
|
try_parse_module_name(DependentModuleTerm, DependentModule),
|
|
parse_func_id(FuncIdTerm, FuncId),
|
|
from_term(CallPatternTerm, CallPattern : Call)
|
|
->
|
|
(
|
|
VersionNumber = analysis_version_number(_ : Call, _ : Answer),
|
|
VersionNumberTerm = term.functor(
|
|
term.integer(VersionNumber), [], _)
|
|
->
|
|
Arc = 'new imdg_arc'(CallPattern, DependentModule),
|
|
( map.search(!.Arcs, AnalysisName, AnalysisArcs0) ->
|
|
AnalysisArcs1 = AnalysisArcs0
|
|
;
|
|
AnalysisArcs1 = map.init
|
|
),
|
|
( map.search(AnalysisArcs1, FuncId, FuncArcs0) ->
|
|
FuncArcs = [Arc | FuncArcs0]
|
|
;
|
|
FuncArcs = [Arc]
|
|
),
|
|
map.set(FuncId, FuncArcs, AnalysisArcs1, AnalysisArcs),
|
|
map.set(AnalysisName, AnalysisArcs, !Arcs)
|
|
;
|
|
% Ignore results with an out-of-date version number.
|
|
% XXX: is that the right thing to do?
|
|
% do we really need a version number for the IMDG?
|
|
true
|
|
)
|
|
;
|
|
Msg = "failed to parse IMDG arc: " ++ string(Term),
|
|
throw(invalid_analysis_file(Msg))
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred parse_func_id(term::in, func_id::out) is semidet.
|
|
|
|
parse_func_id(Term, FuncId) :-
|
|
Term = functor(atom(PF), [NameTerm, ArityTerm, ProcTerm], _),
|
|
(
|
|
PF = "p",
|
|
PredOrFunc = pf_predicate
|
|
;
|
|
PF = "f",
|
|
PredOrFunc = pf_function
|
|
),
|
|
NameTerm = functor(atom(Name), [], _),
|
|
ArityTerm = functor(integer(Arity), [], _),
|
|
ProcTerm = functor(integer(ProcInt), [], _),
|
|
proc_id_to_int(ProcId, ProcInt),
|
|
FuncId = func_id(PredOrFunc, Name, Arity, ProcId).
|
|
|
|
:- pred try_parse_module_name(term::in, module_name::out) is semidet.
|
|
|
|
try_parse_module_name(Term, ModuleName) :-
|
|
try_parse_sym_name_and_no_args(Term, ModuleName).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred read_analysis_file(Compiler::in, globals::in, module_name::in,
|
|
string::in, parse_entry(T)::in(parse_entry), T::in, T::out,
|
|
io::di, io::uo) is det <= compiler(Compiler).
|
|
|
|
read_analysis_file(Compiler, Globals, ModuleName, Suffix, ParseEntry,
|
|
ModuleResults0, ModuleResults, !IO) :-
|
|
module_name_to_read_file_name(Compiler, Globals, ModuleName, Suffix,
|
|
MaybeAnalysisFileName, !IO),
|
|
(
|
|
MaybeAnalysisFileName = ok(AnalysisFileName),
|
|
read_analysis_file(AnalysisFileName, ParseEntry,
|
|
ModuleResults0, ModuleResults, !IO)
|
|
;
|
|
MaybeAnalysisFileName = error(Message),
|
|
debug_msg((pred(!.IO::di, !:IO::uo) is det :-
|
|
io.write_string("Couldn't open ", !IO),
|
|
io.write_string(Suffix, !IO),
|
|
io.write_string(" for module ", !IO),
|
|
io.write(ModuleName, !IO),
|
|
io.write_string(": ", !IO),
|
|
io.write_string(Message, !IO),
|
|
io.nl(!IO)
|
|
), !IO),
|
|
ModuleResults = ModuleResults0
|
|
).
|
|
|
|
:- pred read_analysis_file(string::in, parse_entry(T)::in(parse_entry),
|
|
T::in, T::out, io::di, io::uo) is det.
|
|
|
|
read_analysis_file(AnalysisFileName, ParseEntry, ModuleResults0, ModuleResults,
|
|
!IO) :-
|
|
io.open_input(AnalysisFileName, OpenResult, !IO),
|
|
(
|
|
OpenResult = ok(Stream),
|
|
debug_msg((pred(!.IO::di, !:IO::uo) is det :-
|
|
io.write_string("% Reading analysis file ", !IO),
|
|
io.write_string(AnalysisFileName, !IO),
|
|
io.nl(!IO)
|
|
), !IO),
|
|
io.set_input_stream(Stream, OldStream, !IO),
|
|
|
|
promise_equivalent_solutions [Result, !:IO] (
|
|
try_io(
|
|
(pred(Results1::out, !.IO::di, !:IO::uo) is det :-
|
|
check_analysis_file_version_number(!IO),
|
|
read_analysis_file_2(ParseEntry, ModuleResults0, Results1,
|
|
!IO)
|
|
), Result, !IO)
|
|
),
|
|
io.set_input_stream(OldStream, _, !IO),
|
|
io.close_input(Stream, !IO),
|
|
(
|
|
Result = succeeded(ModuleResults)
|
|
;
|
|
Result = exception(_),
|
|
rethrow(Result)
|
|
)
|
|
;
|
|
OpenResult = error(_),
|
|
debug_msg((pred(!.IO::di, !:IO::uo) is det :-
|
|
io.write_string("Error reading analysis file: ", !IO),
|
|
io.write_string(AnalysisFileName, !IO),
|
|
io.nl(!IO)
|
|
), !IO),
|
|
ModuleResults = ModuleResults0
|
|
).
|
|
|
|
:- pred check_analysis_file_version_number(io::di, io::uo) is det.
|
|
|
|
check_analysis_file_version_number(!IO) :-
|
|
parser.read_term(TermResult : read_term, !IO),
|
|
(
|
|
TermResult = term(_, term.functor(term.integer(version_number), [], _))
|
|
->
|
|
true
|
|
;
|
|
Msg = "bad analysis file version: " ++ string(TermResult),
|
|
throw(invalid_analysis_file(Msg))
|
|
).
|
|
|
|
:- pred read_analysis_file_2(parse_entry(T)::in(parse_entry), T::in, T::out,
|
|
io::di, io::uo) is det.
|
|
|
|
read_analysis_file_2(ParseEntry, Results0, Results, !IO) :-
|
|
parser.read_term(TermResult, !IO),
|
|
(
|
|
TermResult = term(_, Term) : read_term,
|
|
ParseEntry(Term, Results0, Results1),
|
|
read_analysis_file_2(ParseEntry, Results1, Results, !IO)
|
|
;
|
|
TermResult = eof,
|
|
Results = Results0
|
|
;
|
|
TermResult = error(Msg, _),
|
|
throw(invalid_analysis_file(Msg))
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Writing.
|
|
%
|
|
|
|
:- type write_entry(T) == pred(analysis_name, func_id, T, io, io).
|
|
:- inst write_entry == (pred(in, in, in, di, uo) is det).
|
|
|
|
write_module_overall_status(Info, Globals, ModuleName, Status, !IO) :-
|
|
module_name_to_write_file_name(Info ^ compiler, Globals, ModuleName,
|
|
analysis_registry_status_suffix, FileName, !IO),
|
|
io.open_output(FileName, OpenResult, !IO),
|
|
(
|
|
OpenResult = ok(Stream),
|
|
(
|
|
Status = optimal,
|
|
io.write_string(Stream, "optimal.\n", !IO)
|
|
;
|
|
Status = suboptimal,
|
|
io.write_string(Stream, "suboptimal.\n", !IO)
|
|
;
|
|
Status = invalid,
|
|
io.write_string(Stream, "invalid.\n", !IO)
|
|
),
|
|
io.close_output(Stream, !IO)
|
|
;
|
|
OpenResult = error(IOError),
|
|
unexpected(this_file,
|
|
"write_module_overall_status: " ++ io.error_message(IOError))
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
write_module_analysis_results(Info, Globals, ModuleName, ModuleResults, !IO) :-
|
|
debug_msg((pred(!.IO::di, !:IO::uo) is det :-
|
|
io.write_string("% Writing module analysis results for ", !IO),
|
|
io.write(ModuleName, !IO),
|
|
io.nl(!IO)
|
|
), !IO),
|
|
ToTmp = yes,
|
|
write_analysis_file(Info ^ compiler, Globals, ModuleName,
|
|
analysis_registry_suffix, ToTmp, write_result_entry,
|
|
ModuleResults, FileName, !IO),
|
|
update_interface_return_changed(Globals, FileName, Result, !IO),
|
|
|
|
% If analysis file caching is turned on, write the internal represention of
|
|
% the module results to disk right now.
|
|
globals.lookup_string_option(Globals, analysis_file_cache_dir, CacheDir),
|
|
(
|
|
CacheDir \= "",
|
|
Result = interface_new_or_changed
|
|
->
|
|
CacheFileName = make_cache_filename(CacheDir, FileName),
|
|
write_analysis_cache_file(CacheFileName, ModuleResults, !IO)
|
|
;
|
|
true
|
|
).
|
|
|
|
:- pred write_result_entry(analysis_name::in, func_id::in,
|
|
some_analysis_result::in, io::di, io::uo) is det.
|
|
|
|
write_result_entry(AnalysisName, FuncId, Result, !IO) :-
|
|
Result = some_analysis_result(Call, Answer, Status),
|
|
VersionNumber = analysis_version_number(Call, Answer),
|
|
analysis_status_to_string(Status, StatusString),
|
|
|
|
io.write_string(AnalysisName, !IO),
|
|
io.write_char('(', !IO),
|
|
io.write_int(VersionNumber, !IO),
|
|
io.write_string(", ", !IO),
|
|
write_func_id(FuncId, !IO),
|
|
io.write_string(", ", !IO),
|
|
term_io.write_term(varset.init, to_term(Call), !IO),
|
|
io.write_string(", ", !IO),
|
|
term_io.write_term(varset.init, to_term(Answer), !IO),
|
|
io.write_string(", ", !IO),
|
|
io.write_string(StatusString, !IO),
|
|
io.write_string(").\n", !IO).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
write_module_analysis_requests(Info, Globals, ModuleName, ModuleRequests,
|
|
!IO) :-
|
|
Compiler = Info ^ compiler,
|
|
module_name_to_write_file_name(Compiler, Globals, ModuleName,
|
|
request_suffix, AnalysisFileName, !IO),
|
|
debug_msg((pred(!.IO::di, !:IO::uo) is det :-
|
|
io.write_string("% Writing module analysis requests to ", !IO),
|
|
io.write_string(AnalysisFileName, !IO),
|
|
io.nl(!IO)
|
|
), !IO),
|
|
io.open_input(AnalysisFileName, InputResult, !IO),
|
|
(
|
|
InputResult = ok(InputStream),
|
|
% Request file already exists. Check it has the right version
|
|
% number, then append the new requests to the end.
|
|
|
|
io.set_input_stream(InputStream, OldInputStream, !IO),
|
|
parser.read_term(VersionResult : read_term, !IO),
|
|
io.set_input_stream(OldInputStream, _, !IO),
|
|
io.close_input(InputStream, !IO),
|
|
(
|
|
VersionResult = term(_, term.functor(
|
|
term.integer(version_number), [], _))
|
|
->
|
|
io.open_append(AnalysisFileName, AppendResult, !IO),
|
|
(
|
|
AppendResult = ok(AppendStream),
|
|
io.set_output_stream(AppendStream, OldOutputStream, !IO),
|
|
write_analysis_file_2(write_request_entry(Compiler),
|
|
ModuleRequests, !IO),
|
|
io.set_output_stream(OldOutputStream, _, !IO),
|
|
io.close_output(AppendStream, !IO),
|
|
Appended = yes
|
|
;
|
|
AppendResult = error(_),
|
|
Appended = no
|
|
)
|
|
;
|
|
Appended = no
|
|
)
|
|
;
|
|
InputResult = error(_),
|
|
Appended = no
|
|
),
|
|
(
|
|
Appended = no,
|
|
write_analysis_file(AnalysisFileName, write_request_entry(Compiler),
|
|
ModuleRequests, !IO)
|
|
;
|
|
Appended = yes
|
|
).
|
|
|
|
:- pred write_request_entry(Compiler::in, analysis_name::in, func_id::in,
|
|
analysis_request::in, io::di, io::uo) is det <= compiler(Compiler).
|
|
|
|
write_request_entry(Compiler, AnalysisName, FuncId, Request, !IO) :-
|
|
Request = analysis_request(Call, CallerModule),
|
|
(
|
|
analysis_type(_ : unit(Call), _ : unit(Answer))
|
|
= analyses(Compiler, AnalysisName)
|
|
->
|
|
VersionNumber = analysis_version_number(_ : Call, _ : Answer)
|
|
;
|
|
unexpected(this_file,
|
|
"write_request_entry: unknown analysis type")
|
|
),
|
|
|
|
write_module_name(CallerModule, !IO),
|
|
io.write_string(" -> ", !IO),
|
|
io.write_string(AnalysisName, !IO),
|
|
io.write_string("(", !IO),
|
|
io.write_int(VersionNumber, !IO),
|
|
io.write_string(", ", !IO),
|
|
write_func_id(FuncId, !IO),
|
|
io.write_string(", ", !IO),
|
|
term_io.write_term(varset.init, to_term(Call), !IO),
|
|
io.write_string(").\n", !IO).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
write_module_imdg(Info, Globals, ModuleName, ModuleEntries, !IO) :-
|
|
ToTmp = no,
|
|
write_analysis_file(Info ^ compiler, Globals, ModuleName, imdg_suffix,
|
|
ToTmp, write_imdg_arc(Info ^ compiler), ModuleEntries, _FileName, !IO).
|
|
|
|
:- pred write_imdg_arc(Compiler::in, analysis_name::in, func_id::in,
|
|
imdg_arc::in, io::di, io::uo) is det <= compiler(Compiler).
|
|
|
|
write_imdg_arc(Compiler, AnalysisName, FuncId, Arc, !IO) :-
|
|
Arc = imdg_arc(Call, DependentModule),
|
|
(
|
|
analysis_type(_ : unit(Call), _ : unit(Answer))
|
|
= analyses(Compiler, AnalysisName)
|
|
->
|
|
VersionNumber = analysis_version_number(_ : Call, _ : Answer)
|
|
;
|
|
unexpected(this_file, "write_imdg_arc: unknown analysis type")
|
|
),
|
|
|
|
write_module_name(DependentModule, !IO),
|
|
io.write_string(" -> ", !IO),
|
|
io.write_string(AnalysisName, !IO),
|
|
io.write_char('(', !IO),
|
|
io.write_int(VersionNumber, !IO),
|
|
io.write_string(", ", !IO),
|
|
write_func_id(FuncId, !IO),
|
|
io.write_string(", ", !IO),
|
|
term_io.write_term(varset.init, to_term(Call), !IO),
|
|
io.write_string(").\n", !IO).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred write_func_id(func_id::in, io::di, io::uo) is det.
|
|
|
|
write_func_id(func_id(PredOrFunc, Name, Arity, ProcId), !IO) :-
|
|
(
|
|
PredOrFunc = pf_predicate,
|
|
io.write_string("p(", !IO)
|
|
;
|
|
PredOrFunc = pf_function,
|
|
io.write_string("f(", !IO)
|
|
),
|
|
term_io.quote_atom(Name, !IO),
|
|
io.write_string(", ", !IO),
|
|
io.write_int(Arity, !IO),
|
|
io.write_string(", ", !IO),
|
|
io.write_int(proc_id_to_int(ProcId), !IO),
|
|
io.write_char(')', !IO).
|
|
|
|
:- pred write_module_name(module_name::in, io::di, io::uo) is det.
|
|
|
|
write_module_name(ModuleName, !IO) :-
|
|
write_quoted_sym_name(ModuleName, !IO).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred write_analysis_file(Compiler::in, globals::in, module_name::in,
|
|
string::in, bool::in, write_entry(T)::in(write_entry),
|
|
module_analysis_map(T)::in, string::out, io::di, io::uo) is det
|
|
<= compiler(Compiler).
|
|
|
|
write_analysis_file(Compiler, Globals, ModuleName, Suffix, ToTmp, WriteEntry,
|
|
ModuleResults, FileName, !IO) :-
|
|
module_name_to_write_file_name(Compiler, Globals, ModuleName,
|
|
Suffix, FileName, !IO),
|
|
(
|
|
ToTmp = yes,
|
|
WriteFileName = FileName ++ ".tmp"
|
|
;
|
|
ToTmp = no,
|
|
WriteFileName = FileName
|
|
),
|
|
write_analysis_file(WriteFileName, WriteEntry, ModuleResults, !IO).
|
|
|
|
:- pred write_analysis_file(string::in, write_entry(T)::in(write_entry),
|
|
module_analysis_map(T)::in, io::di, io::uo) is det.
|
|
|
|
write_analysis_file(FileName, WriteEntry, ModuleResults, !IO) :-
|
|
io.open_output(FileName, OpenResult, !IO),
|
|
(
|
|
OpenResult = ok(Stream),
|
|
io.set_output_stream(Stream, OldOutput, !IO),
|
|
io.write_int(version_number, !IO),
|
|
io.write_string(".\n", !IO),
|
|
write_analysis_file_2(WriteEntry, ModuleResults, !IO),
|
|
io.set_output_stream(OldOutput, _, !IO),
|
|
io.close_output(Stream, !IO)
|
|
;
|
|
OpenResult = error(IOError),
|
|
unexpected(this_file, "write_analysis_file: error opening `" ++
|
|
FileName ++ "' for output: " ++ io.error_message(IOError))
|
|
).
|
|
|
|
:- pred write_analysis_file_2(write_entry(T)::in(write_entry),
|
|
module_analysis_map(T)::in, io::di, io::uo) is det.
|
|
|
|
write_analysis_file_2(WriteEntry, ModuleResults, !IO) :-
|
|
map.foldl(write_analysis_file_3(WriteEntry), ModuleResults, !IO).
|
|
|
|
:- pred write_analysis_file_3(write_entry(T)::in(write_entry), string::in,
|
|
func_analysis_map(T)::in, io::di, io::uo) is det.
|
|
|
|
write_analysis_file_3(WriteEntry, AnalysisName, FuncResults, !IO) :-
|
|
map.foldl(write_analysis_file_4(WriteEntry, AnalysisName),
|
|
FuncResults, !IO).
|
|
|
|
:- pred write_analysis_file_4(write_entry(T)::in(write_entry), string::in,
|
|
func_id::in, list(T)::in, io::di, io::uo) is det.
|
|
|
|
write_analysis_file_4(WriteEntry, AnalysisName, FuncId, FuncResultList, !IO) :-
|
|
list.sort(FuncResultList, FuncResultListSorted),
|
|
list.foldl(
|
|
(pred(FuncResult::in, !.IO::di, !:IO::uo) is det :-
|
|
WriteEntry(AnalysisName, FuncId, FuncResult, !IO)
|
|
), FuncResultListSorted, !IO).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
empty_request_file(Info, Globals, ModuleName, !IO) :-
|
|
module_name_to_write_file_name(Info ^ compiler, Globals, ModuleName,
|
|
request_suffix, RequestFileName, !IO),
|
|
debug_msg((pred(!.IO::di, !:IO::uo) is det :-
|
|
io.write_string("% Removing request file ", !IO),
|
|
io.write_string(RequestFileName, !IO),
|
|
io.nl(!IO)
|
|
), !IO),
|
|
io.remove_file(RequestFileName, _, !IO).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Analysis file caching.
|
|
%
|
|
% An analysis cache file stores a binary representation of the parsed
|
|
% information in the corresponding .analysis file. In some cases, the binary
|
|
% format can be faster to read than the usual representation. The textual
|
|
% analysis files are portable, more stable (doesn't depend on compiler
|
|
% internals) and easier to debug, hence the reason we don't just use the
|
|
% binary files exclusively.
|
|
%
|
|
|
|
:- func make_cache_filename(string, string) = string.
|
|
|
|
make_cache_filename(Dir, FileName) = CacheFileName :-
|
|
Components = string.split_at_separator(dir_sep, FileName),
|
|
EscFileName = string.join_list(":", Components),
|
|
CacheFileName = Dir / EscFileName.
|
|
|
|
:- pred dir_sep(char::in) is semidet.
|
|
|
|
dir_sep(Char) :-
|
|
dir.is_directory_separator(Char).
|
|
|
|
:- pred write_analysis_cache_file(string::in,
|
|
module_analysis_map(some_analysis_result)::in, io::di, io::uo) is det.
|
|
|
|
write_analysis_cache_file(CacheFileName, ModuleResults, !IO) :-
|
|
% Write to a temporary file first and only move it into place once it is
|
|
% complete.
|
|
TmpFileName = CacheFileName ++ ".tmp",
|
|
io.tell_binary(TmpFileName, TellRes, !IO),
|
|
(
|
|
TellRes = ok,
|
|
pickle(init_analysis_picklers, ModuleResults, !IO),
|
|
io.told_binary(!IO),
|
|
io.rename_file(TmpFileName, CacheFileName, RenameRes, !IO),
|
|
(
|
|
RenameRes = ok
|
|
;
|
|
RenameRes = error(Error),
|
|
io.write_string("Error renaming ", !IO),
|
|
io.write_string(CacheFileName, !IO),
|
|
io.write_string(": ", !IO),
|
|
io.write_string(io.error_message(Error), !IO),
|
|
io.nl(!IO),
|
|
io.remove_file(TmpFileName, _, !IO)
|
|
)
|
|
;
|
|
TellRes = error(Error),
|
|
unexpected(this_file, "pickle_to_file: " ++ io.error_message(Error))
|
|
).
|
|
|
|
:- func init_analysis_picklers = picklers.
|
|
|
|
init_analysis_picklers = Pickles :-
|
|
some [!Pickles] (
|
|
!:Pickles = init_picklers,
|
|
Dummy = 'new some_analysis_result'(any_call, dummy_answer, optimal),
|
|
Type = type_ctor(type_of(Dummy)),
|
|
register_pickler(Type, pickle_analysis_result, !Pickles),
|
|
Pickles = !.Pickles
|
|
).
|
|
|
|
:- pred pickle_analysis_result(picklers::in, univ::in, io::di, io::uo) is det.
|
|
|
|
pickle_analysis_result(Pickles, Univ, !IO) :-
|
|
det_univ_to_type(Univ, some_analysis_result(Call, Answer, Status)),
|
|
Name = analysis_name(Call, Answer),
|
|
pickle(Pickles, Name, !IO),
|
|
pickle(Pickles, Call, !IO),
|
|
pickle(Pickles, Answer, !IO),
|
|
pickle(Pickles, Status, !IO).
|
|
|
|
:- func init_analysis_unpicklers(Compiler) = unpicklers
|
|
<= compiler(Compiler).
|
|
|
|
init_analysis_unpicklers(Compiler) = Unpicklers :-
|
|
some [!Unpicklers] (
|
|
!:Unpicklers = init_unpicklers,
|
|
Dummy = 'new some_analysis_result'(any_call, dummy_answer, optimal),
|
|
Type = type_ctor(type_of(Dummy)),
|
|
register_unpickler(Type, unpickle_analysis_result(Compiler),
|
|
!Unpicklers),
|
|
Unpicklers = !.Unpicklers
|
|
).
|
|
|
|
:- pred unpickle_analysis_result(Compiler::in, unpicklers::in,
|
|
unpickle_handle::in, type_desc::in, univ::out,
|
|
unpickle_state::di, unpickle_state::uo) is det
|
|
<= compiler(Compiler).
|
|
|
|
unpickle_analysis_result(Compiler, Unpicklers, Handle, _Type, Univ, !State) :-
|
|
unpickle(Unpicklers, Handle, Name : string, !State),
|
|
(
|
|
analysis_type(_ : unit(Call), _ : unit(Answer))
|
|
= analyses(Compiler, Name)
|
|
->
|
|
unpickle(Unpicklers, Handle, Call : Call, !State),
|
|
unpickle(Unpicklers, Handle, Answer : Answer, !State),
|
|
unpickle(Unpicklers, Handle, Status, !State),
|
|
Result = 'new some_analysis_result'(Call, Answer, Status),
|
|
type_to_univ(Result, Univ)
|
|
;
|
|
unexpected(this_file, "unpickle_analysis_result: " ++ Name)
|
|
).
|
|
|
|
% This is only needed so we can get the type_ctor_desc of
|
|
% `some_analysis_result' without referring to a real analysis.
|
|
|
|
:- type dummy_answer
|
|
---> dummy_answer.
|
|
|
|
:- instance answer_pattern(no_func_info, dummy_answer) where [].
|
|
:- instance partial_order(no_func_info, dummy_answer) where [
|
|
( more_precise_than(no_func_info, _, _) :-
|
|
semidet_fail
|
|
),
|
|
equivalent(no_func_info, dummy_answer, dummy_answer)
|
|
].
|
|
:- instance to_term(dummy_answer) where [
|
|
( to_term(dummy_answer) = Term :-
|
|
Term = term.functor(atom("dummy"), [], context_init)
|
|
),
|
|
( from_term(Term, dummy_answer) :-
|
|
Term = term.functor(atom("dummy"), [], _)
|
|
)
|
|
].
|
|
|
|
:- instance analysis(no_func_info, any_call, dummy_answer) where [
|
|
analysis_name(_, _) = "dummy",
|
|
analysis_version_number(_, _) = 1,
|
|
preferred_fixpoint_type(_, _) = greatest_fixpoint,
|
|
bottom(_, _) = dummy_answer,
|
|
top(_, _) = dummy_answer,
|
|
get_func_info(_, _, _, _, _, no_func_info)
|
|
].
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
:- end_module analysis.file.
|
|
%-----------------------------------------------------------------------------%
|