%---------------------------------------------------------------------------% % vim: ft=mercury ts=4 sw=4 et %---------------------------------------------------------------------------% % Copyright (C) 2003, 2005-2012 The University of Melbourne. % Copyright (C) 2014-2016, 2019-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.file.m % Main author: stayl, wangp. % % This module deals with the on-disk representations of the analysis registry % and associated files. % % NOTE: I (zs) find the criteria used for the division of tasks between % this module and analysis.operations.m (which used to be just analysis.m % when it was this module's parent) to be unclear. % %---------------------------------------------------------------------------% :- module analysis.file. :- interface. :- import_module analysis.framework. :- import_module analysis.operations. :- 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 io. :- import_module list. %---------------------------------------------------------------------------% % 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(ProgressStream, 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(io.text_output_stream::in, analysis_info::in, globals::in, module_name::in, module_analysis_map(some_analysis_result)::out, list(error_spec)::out, io::di, io::uo) is det. % write_module_analysis_results(ProgressStream, 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(io.text_output_stream::in, 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, list(error_spec)::in, list(error_spec)::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, list(error_spec)::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 hlds. :- import_module hlds.hlds_pred. :- import_module libs.options. :- import_module libs.pickle. :- import_module mdbcomp.prim_data. :- import_module parse_tree.file_names. :- import_module parse_tree.module_cmds. :- import_module parse_tree.parse_sym_name. :- import_module parse_tree.parse_tree_out_sym_name. :- import_module parse_tree.parse_tree_out_term. :- import_module parse_tree.prog_data. :- import_module bool. :- import_module char. :- import_module dir. :- import_module io.file. :- import_module map. :- import_module maybe. :- import_module mercury_term_lexer. :- import_module mercury_term_parser. :- import_module require. :- import_module string. :- import_module term. :- import_module term_context. :- import_module term_int. :- import_module term_io. :- import_module type_desc. :- import_module unit. :- 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). :- func version_number = int. version_number = 6. %---------------------------------------------------------------------------% :- 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 and writing overall status. % :- type parse_entry(T) == pred(varset, term, T, T, list(error_spec), list(error_spec)). :- inst parse_entry == (pred(in, in, in, out, in, out) is det). read_module_overall_status(Compiler, Globals, ModuleName, ModuleStatus, !IO) :- ExtStatus = ext_cur_ngs_gs(ext_cur_ngs_gs_an_analysis_status), module_name_to_read_file_name(Compiler, Globals, ExtStatus, ModuleName, 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, ExtRequest = ext_cur_ngs_gs_max_ngs(ext_cur_ngs_gs_max_ngs_an_request), module_name_to_read_file_name(Compiler, Globals, ExtRequest, ModuleName, 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), ( if string.prefix(String, "optimal.") then ModuleStatus = optimal else if string.prefix(String, "suboptimal.") then ModuleStatus = suboptimal else if string.prefix(String, "invalid.") then ModuleStatus = invalid else unexpected($pred, "unexpected line") ) ; ReadResult = eof, unexpected($pred, "unexpected eof") ; ReadResult = error(IOError), unexpected($pred, io.error_message(IOError)) ) ; OpenResult = error(IOError), unexpected($pred, io.error_message(IOError)) ). %---------------------% :- type write_entry(T) == pred(io.text_output_stream, analysis_name, func_id, T, io, io). :- inst write_entry == (pred(in, in, in, in, di, uo) is det). write_module_overall_status(Info, Globals, ModuleName, Status, !IO) :- ExtStatus = ext_cur_ngs_gs(ext_cur_ngs_gs_an_analysis_status), module_name_to_write_file_name(analysis_info_get_compiler(Info), Globals, ExtStatus, ModuleName, 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($pred, io.error_message(IOError)) ). %---------------------------------------------------------------------------% % % Reading and writing analysis results. % read_module_analysis_results(ProgressStream, Info, Globals, ModuleName, ModuleResults, Specs, !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 = analysis_info_get_compiler(Info), ExtAnalysis = ext_cur_ngs_gs_max_ngs(ext_cur_ngs_gs_max_ngs_an_analysis), module_name_to_read_file_name(Compiler, Globals, ExtAnalysis, ModuleName, 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), ( if CacheDir = "" then do_read_module_analysis_results(Compiler, AnalysisFileName, ModuleResults, Specs, !IO) else CacheFileName = make_cache_filename(CacheDir, AnalysisFileName), io.file.file_modification_time(AnalysisFileName, AnalysisTimeResult, !IO), io.file.file_modification_time(CacheFileName, CacheTimeResult, !IO), ( if AnalysisTimeResult = ok(AnalysisTime), CacheTimeResult = ok(CacheTime), CacheTime @>= AnalysisTime then Unpicklers = init_analysis_unpicklers(Compiler), unpickle_from_file(Unpicklers, CacheFileName, UnpickleResult, !IO), ( UnpickleResult = ok(ModuleResults), Specs = [] ; UnpickleResult = error(Error), io.error_message(Error, ErrorMsg), io.format(ProgressStream, "Error reading %s: %s\n", [s(CacheFileName), s(ErrorMsg)], !IO), do_read_module_analysis_results(Compiler, AnalysisFileName, ModuleResults, Specs, !IO), maybe_write_analysis_cache_file(CacheFileName, ModuleResults, Specs, !IO) ) else do_read_module_analysis_results(Compiler, AnalysisFileName, ModuleResults, Specs, !IO), maybe_write_analysis_cache_file(CacheFileName, ModuleResults, Specs, !IO) ) ) ; MaybeAnalysisFileName = error(_), ModuleResults = map.init, % XXX Why is a failed open of an input file *less* fatal than % not being able to parse the contents of the file? Specs = [] ). :- pred do_read_module_analysis_results(Compiler::in, string::in, module_analysis_map(some_analysis_result)::out, list(error_spec)::out, io::di, io::uo) is det <= compiler(Compiler). do_read_module_analysis_results(Compiler, AnalysisFileName, !:ModuleResults, !:Specs, !IO) :- !:ModuleResults = map.init, !:Specs = [], io.read_named_file_as_string(AnalysisFileName, FileResult, !IO), ( FileResult = ok(FileStr), get_analysis_debug_stream(MaybeDebugStream, !IO), ( MaybeDebugStream = no ; MaybeDebugStream = yes(DebugStream), io.format(DebugStream, "%% Reading analysis registry file %s\n", [s(AnalysisFileName)], !IO) ), string.length(FileStr, MaxOffset), LineContext0 = line_context(1, 0), LinePosn0 = line_posn(0), check_analysis_file_version_number(AnalysisFileName, FileStr, MaxOffset, LineContext0, LineContext1, LinePosn0, LinePosn1, !Specs), parse_analysis_file_entries(AnalysisFileName, FileStr, MaxOffset, parse_result_entry(Compiler), LineContext1, LinePosn1, !ModuleResults, !Specs) ; FileResult = error(_), get_analysis_debug_stream(MaybeDebugStream, !IO), ( MaybeDebugStream = no ; MaybeDebugStream = yes(DebugStream), io.format(DebugStream, "%% Error reading analysis registry file: %s\n", [s(AnalysisFileName)], !IO) ) % XXX Why is a failed open of an input file *less* fatal than % not being able to parse the contents of the file? ). :- pred parse_result_entry(Compiler::in, varset::in, term::in, module_analysis_map(some_analysis_result)::in, module_analysis_map(some_analysis_result)::out, list(error_spec)::in, list(error_spec)::out) is det <= compiler(Compiler). parse_result_entry(Compiler, VarSet, Term, !Results, !Specs) :- ( if Term = term.functor(term.atom(AnalysisName), [VersionNumberTerm, FuncIdTerm, CallPatternTerm, AnswerPatternTerm, StatusTerm], _), StatusTerm = term.functor(term.atom(StatusString), [], _), analyses(Compiler, AnalysisName, Analysis), analysis_type(_ : unit(Call), _ : unit(Answer)) = Analysis, parse_func_id(FuncIdTerm, FuncId), from_term(CallPatternTerm, CallPattern : Call), from_term(AnswerPatternTerm, AnswerPattern : Answer), analysis_status_to_string(Status, StatusString) then ( if VersionNumber = analysis_version_number(_ : Call, _ : Answer), term_int.decimal_term_to_int(VersionNumberTerm, VersionNumber) then Result = 'new some_analysis_result'(CallPattern, AnswerPattern, Status), ( if map.search(!.Results, AnalysisName, AnalysisResults0) then AnalysisResults1 = AnalysisResults0 else AnalysisResults1 = map.init ), ( if map.search(AnalysisResults1, FuncId, FuncResults0) then FuncResults = [Result | FuncResults0] else FuncResults = [Result] ), map.set(FuncId, FuncResults, AnalysisResults1, AnalysisResults), map.set(AnalysisName, AnalysisResults, !Results) else % Ignore results with an out-of-date version number. true ) else TermStr = describe_error_term(VarSet, Term), Pieces = [words("Error: expected a result entry, got"), quote(TermStr), suffix("."), nl], Spec = spec($pred, severity_error, phase_read_files, get_term_context(Term), Pieces), !:Specs = [Spec | !.Specs] ). %---------------------% write_module_analysis_results(ProgressStream, Info, Globals, ModuleName, ModuleResults, !IO) :- get_analysis_debug_stream(MaybeDebugStream, !IO), ( MaybeDebugStream = no ; MaybeDebugStream = yes(DebugStream), io.format(DebugStream, "%%s Writing module analysis results for %s\n", [s(sym_name_to_string(ModuleName))], !IO) ), ExtAnalysis = ext_cur_ngs_gs_max_ngs(ext_cur_ngs_gs_max_ngs_an_analysis), find_and_write_analysis_file(analysis_info_get_compiler(Info), Globals, add_dot_temp, write_result_entry, ExtAnalysis, ModuleName, ModuleResults, FileName, !IO), copy_dot_tmp_to_base_file_return_changed(ProgressStream, Globals, FileName, UpdateResult, !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), ( if CacheDir \= "", UpdateResult = base_file_new_or_changed then CacheFileName = make_cache_filename(CacheDir, FileName), write_analysis_cache_file(CacheFileName, ModuleResults, !IO) else true ). :- pred write_result_entry(io.text_output_stream::in, analysis_name::in, func_id::in, some_analysis_result::in, io::di, io::uo) is det. write_result_entry(OutStream, AnalysisName, FuncId, Result, !IO) :- Result = some_analysis_result(Call, Answer, Status), VersionNumber = analysis_version_number(Call, Answer), analysis_status_to_string(Status, StatusString), FuncIdStr = func_id_to_string(FuncId), io.format(OutStream, "%s(%d, %s, ", [s(AnalysisName), i(VersionNumber), s(FuncIdStr)], !IO), term_io.write_term(OutStream, varset.init, to_term(Call), !IO), io.write_string(OutStream, ", ", !IO), term_io.write_term(OutStream, varset.init, to_term(Answer), !IO), io.format(OutStream, ", %s).\n", [s(StatusString)], !IO). %---------------------------------------------------------------------------% % % Reading and writing analysis requests. % read_module_analysis_requests(Info, Globals, ModuleName, ModuleRequests, !Specs, !IO) :- Compiler = analysis_info_get_compiler(Info), ExtRequest = ext_cur_ngs_gs_max_ngs(ext_cur_ngs_gs_max_ngs_an_request), find_and_read_analysis_file(Compiler, Globals, parse_request_entry(Compiler), ExtRequest, ModuleName, map.init, ModuleRequests, !Specs, !IO). :- pred parse_request_entry(Compiler::in, varset::in, term::in, module_analysis_map(analysis_request)::in, module_analysis_map(analysis_request)::out, list(error_spec)::in, list(error_spec)::out) is det <= compiler(Compiler). parse_request_entry(Compiler, VarSet, Term, !Requests, !Specs) :- ( if Term = term.functor(atom("->"), [CallerModuleTerm, RHS], _), RHS = term.functor(atom(AnalysisName), [VersionNumberTerm, FuncIdTerm, CallPatternTerm], _), analyses(Compiler, AnalysisName, Analysis), analysis_type(_ : unit(Call), _ : unit(Answer)) = Analysis, try_parse_module_name(CallerModuleTerm, CallerModule), parse_func_id(FuncIdTerm, FuncId), from_term(CallPatternTerm, CallPattern : Call) then ( if VersionNumber = analysis_version_number(_ : Call, _ : Answer), term_int.decimal_term_to_int(VersionNumberTerm, VersionNumber) then Result = 'new analysis_request'(CallPattern, CallerModule), ( if map.search(!.Requests, AnalysisName, AnalysisRequests0) then AnalysisRequests1 = AnalysisRequests0 else AnalysisRequests1 = map.init ), ( if map.search(AnalysisRequests1, FuncId, FuncRequests0) then FuncRequests = [Result | FuncRequests0] else FuncRequests = [Result] ), map.set(FuncId, FuncRequests, AnalysisRequests1, AnalysisRequests), map.set(AnalysisName, AnalysisRequests, !Requests) else % Ignore requests with an out-of-date version number. true ) else TermStr = describe_error_term(VarSet, Term), Pieces = [words("Error: expected a request entry, got"), quote(TermStr), suffix("."), nl], Spec = spec($pred, severity_error, phase_read_files, get_term_context(Term), Pieces), !:Specs = [Spec | !.Specs] ). %---------------------% write_module_analysis_requests(Info, Globals, ModuleName, ModuleRequests, !IO) :- Compiler = analysis_info_get_compiler(Info), ExtRequest = ext_cur_ngs_gs_max_ngs(ext_cur_ngs_gs_max_ngs_an_request), module_name_to_write_file_name(Compiler, Globals, ExtRequest, ModuleName, AnalysisFileName, !IO), get_analysis_debug_stream(MaybeDebugStream, !IO), ( MaybeDebugStream = no ; MaybeDebugStream = yes(DebugStream), io.format(DebugStream, "%% Writing module analysis requests to %s\n", [s(AnalysisFileName)], !IO) ), io.read_named_file_as_string(AnalysisFileName, FileResult, !IO), ( FileResult = ok(FileStr), % Request file already exists. Check it has the right version number, % then append the new requests to the end. % XXX Check whether FileStr is well_formed. string.length(FileStr, MaxOffset), LineContext0 = line_context(1, 0), LinePosn0 = line_posn(0), mercury_term_parser.read_term_from_linestr(AnalysisFileName, FileStr, MaxOffset, LineContext0, _LineContext1, LinePosn0, _LinePosn1, VersionResult : read_term), ( if VersionResult = term(_, NumberTerm), term_int.decimal_term_to_int(NumberTerm, version_number) then io.open_append(AnalysisFileName, AppendResult, !IO), ( AppendResult = ok(AppendStream), write_module_analysis_map(AppendStream, write_request_entry(Compiler), ModuleRequests, !IO), io.close_output(AppendStream, !IO), Appended = yes ; AppendResult = error(_), Appended = no ) else Appended = no ) ; FileResult = error(_), Appended = no ), ( Appended = no, write_analysis_file(AnalysisFileName, write_request_entry(Compiler), ModuleRequests, !IO) ; Appended = yes ). :- pred write_request_entry(Compiler::in, io.text_output_stream::in, analysis_name::in, func_id::in, analysis_request::in, io::di, io::uo) is det <= compiler(Compiler). write_request_entry(Compiler, OutStream, AnalysisName, FuncId, Request, !IO) :- Request = analysis_request(Call, CallerModule), ( if analyses(Compiler, AnalysisName, Analysis), analysis_type(_ : unit(Call), _ : unit(Answer)) = Analysis then VersionNumber = analysis_version_number(_ : Call, _ : Answer) else unexpected($pred, "unknown analysis type") ), FuncIdStr = func_id_to_string(FuncId), CallerModuleStr = escaped_sym_name_to_string(CallerModule), CallTermStr = term_io.term_to_string(varset.init, to_term(Call)), io.format(OutStream, "'%s' -> %s(%d, %s, %s).\n", [s(CallerModuleStr), s(AnalysisName), i(VersionNumber), s(FuncIdStr), s(CallTermStr)], !IO). %---------------------------------------------------------------------------% % % Reading and writing imdgs. % read_module_imdg(Info, Globals, ModuleName, ModuleEntries, Specs, !IO) :- Compiler = analysis_info_get_compiler(Info), ExtImdg = ext_cur_ngs_gs_max_ngs(ext_cur_ngs_gs_max_ngs_an_imdg), find_and_read_analysis_file(Compiler, Globals, parse_imdg_arc(Compiler), ExtImdg, ModuleName, map.init, ModuleEntries, [], Specs, !IO). :- pred parse_imdg_arc(Compiler::in, varset::in, term::in, module_analysis_map(imdg_arc)::in, module_analysis_map(imdg_arc)::out, list(error_spec)::in, list(error_spec)::out) is det <= compiler(Compiler). parse_imdg_arc(Compiler, VarSet, Term, !Arcs, !Specs) :- ( if Term = term.functor(atom("->"), [DependentModuleTerm, ResultTerm], _), ResultTerm = functor(atom(AnalysisName), [VersionNumberTerm, FuncIdTerm, CallPatternTerm], _), analyses(Compiler, AnalysisName, Analysis), analysis_type(_ : unit(Call), _ : unit(Answer)) = Analysis, try_parse_module_name(DependentModuleTerm, DependentModule), parse_func_id(FuncIdTerm, FuncId), from_term(CallPatternTerm, CallPattern : Call) then ( if VersionNumber = analysis_version_number(_ : Call, _ : Answer), term_int.decimal_term_to_int(VersionNumberTerm, VersionNumber) then Arc = 'new imdg_arc'(CallPattern, DependentModule), ( if map.search(!.Arcs, AnalysisName, AnalysisArcs0) then AnalysisArcs1 = AnalysisArcs0 else AnalysisArcs1 = map.init ), ( if map.search(AnalysisArcs1, FuncId, FuncArcs0) then FuncArcs = [Arc | FuncArcs0] else FuncArcs = [Arc] ), map.set(FuncId, FuncArcs, AnalysisArcs1, AnalysisArcs), map.set(AnalysisName, AnalysisArcs, !Arcs) else % 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 ) else TermStr = describe_error_term(VarSet, Term), Pieces = [words("Error: expected an imdb arc, got"), quote(TermStr), suffix("."), nl], Spec = spec($pred, severity_error, phase_read_files, get_term_context(Term), Pieces), !:Specs = [Spec | !.Specs] ). %---------------------% write_module_imdg(Info, Globals, ModuleName, ModuleEntries, !IO) :- Compiler = analysis_info_get_compiler(Info), ExtImdg = ext_cur_ngs_gs_max_ngs(ext_cur_ngs_gs_max_ngs_an_imdg), find_and_write_analysis_file(Compiler, Globals, do_not_add_dot_temp, write_imdg_arc(Compiler), ExtImdg, ModuleName, ModuleEntries, _FileName, !IO). :- pred write_imdg_arc(Compiler::in, io.text_output_stream::in, analysis_name::in, func_id::in, imdg_arc::in, io::di, io::uo) is det <= compiler(Compiler). write_imdg_arc(Compiler, OutStream, AnalysisName, FuncId, Arc, !IO) :- Arc = imdg_arc(Call, DependentModule), ( if analyses(Compiler, AnalysisName, Analysis), analysis_type(_ : unit(Call), _ : unit(Answer)) = Analysis then VersionNumber = analysis_version_number(_ : Call, _ : Answer) else unexpected($pred, "unknown analysis type") ), FuncIdStr = func_id_to_string(FuncId), DependentModuleStr = escaped_sym_name_to_string(DependentModule), CallTermStr = term_io.term_to_string(varset.init, to_term(Call)), io.format(OutStream, "'%s' -> %s(%i, %s, %s).\n", [s(DependentModuleStr), s(AnalysisName), i(VersionNumber), s(FuncIdStr), s(CallTermStr)], !IO). %---------------------------------------------------------------------------% % % Common code for reading. % :- pred parse_func_id(term::in, func_id::out) is semidet. parse_func_id(Term, FuncId) :- Term = term.functor(term.atom(PF), [NameTerm, ArityTerm, ProcTerm], _), ( PF = "p", PredOrFunc = pf_predicate ; PF = "f", PredOrFunc = pf_function ), NameTerm = term.functor(term.atom(Name), [], _), term_int.decimal_term_to_int(ArityTerm, Arity), term_int.decimal_term_to_int(ProcTerm, ProcIdInt), proc_id_to_int(ProcId, ProcIdInt), FuncId = func_id(PredOrFunc, Name, pred_form_arity(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 find_and_read_analysis_file(Compiler::in, globals::in, parse_entry(T)::in(parse_entry), ext::in(ext_analysis), module_name::in, T::in, T::out, list(error_spec)::in, list(error_spec)::out, io::di, io::uo) is det <= compiler(Compiler). find_and_read_analysis_file(Compiler, Globals, ParseEntry, Ext, ModuleName, !ModuleResults, !Specs, !IO) :- module_name_to_read_file_name(Compiler, Globals, Ext, ModuleName, MaybeAnalysisFileName, !IO), ( MaybeAnalysisFileName = ok(AnalysisFileName), read_analysis_file(AnalysisFileName, ParseEntry, !ModuleResults, !Specs, !IO) ; MaybeAnalysisFileName = error(Message), get_analysis_debug_stream(MaybeDebugStream, !IO), ( MaybeDebugStream = no ; MaybeDebugStream = yes(DebugStream), ExtStr = extension_to_string(Globals, Ext), io.format(DebugStream, "Couldn't find %s file for module %s: %s\n", [s(ExtStr), s(sym_name_to_string(ModuleName)), s(Message)], !IO) ) % XXX Why is not being able to find a file *less* fatal than % not being able to parse the contents of the file? ). :- pred read_analysis_file(string::in, parse_entry(T)::in(parse_entry), T::in, T::out, list(error_spec)::in, list(error_spec)::out, io::di, io::uo) is det. read_analysis_file(AnalysisFileName, ParseEntry, !ModuleResults, !Specs, !IO) :- io.read_named_file_as_string(AnalysisFileName, FileResult, !IO), ( FileResult = ok(FileStr), get_analysis_debug_stream(MaybeDebugStream, !IO), ( MaybeDebugStream = no ; MaybeDebugStream = yes(DebugStream), io.format(DebugStream, "%% Reading analysis file %s\n", [s(AnalysisFileName)], !IO) ), string.length(FileStr, MaxOffset), LineContext0 = line_context(1, 0), LinePosn0 = line_posn(0), check_analysis_file_version_number(AnalysisFileName, FileStr, MaxOffset, LineContext0, LineContext1, LinePosn0, LinePosn1, !Specs), parse_analysis_file_entries(AnalysisFileName, FileStr, MaxOffset, ParseEntry, LineContext1, LinePosn1, !ModuleResults, !Specs) ; FileResult = error(_), get_analysis_debug_stream(MaybeDebugStream, !IO), ( MaybeDebugStream = no ; MaybeDebugStream = yes(DebugStream), io.format(DebugStream, "Error reading analysis file: %s\n", [s(AnalysisFileName)], !IO) ) % XXX Why is a failed open of an input file *less* fatal than % not being able to parse the contents of the file? ). :- pred check_analysis_file_version_number(string::in, string::in, int::in, line_context::in, line_context::out, line_posn::in, line_posn::out, list(error_spec)::in, list(error_spec)::out) is det. check_analysis_file_version_number(FileName, FileStr, MaxOffset, !LineContext, !LinePosn, !Specs) :- LineContext0 = !.LineContext, read_term_from_linestr(FileName, FileStr, MaxOffset, !LineContext, !LinePosn, TermResult : read_term), ( if TermResult = term(_VarSet, NumberTerm), term_int.decimal_term_to_int(NumberTerm, Number) then ( if Number = version_number then true else Pieces = [words("Error: version number mismatch."), words("Expected"), int_fixed(version_number), suffix(","), words("got"), int_fixed(Number), suffix("."), nl], LineContext0 = line_context(LineNumber, _), Context = context(FileName, LineNumber), Spec = spec($pred, severity_error, phase_read_files, Context, Pieces), !:Specs = [Spec | !.Specs] ) else Pieces = [words("Error: this file"), words("should start with a version number"), words("(specifically,"), int_fixed(version_number), suffix("),"), words("but it does not."), nl], LineContext0 = line_context(LineNumber, _), Context = context(FileName, LineNumber), Spec = spec($pred, severity_error, phase_read_files, Context, Pieces), !:Specs = [Spec | !.Specs] ). :- pred parse_analysis_file_entries(string::in, string::in, int::in, parse_entry(T)::in(parse_entry), line_context::in, line_posn::in, T::in, T::out, list(error_spec)::in, list(error_spec)::out) is det. parse_analysis_file_entries(FileName, FileStr, MaxOffset, ParseEntry, !.LineContext, !.LinePosn, !Results, !Specs) :- LineContext0 = !.LineContext, read_term_from_linestr(FileName, FileStr, MaxOffset, !LineContext, !LinePosn, TermResult : read_term), ( TermResult = term(VarSet, Term), ParseEntry(VarSet, Term, !Results, !Specs), parse_analysis_file_entries(FileName, FileStr, MaxOffset, ParseEntry, !.LineContext, !.LinePosn, !Results, !Specs) ; TermResult = eof ; TermResult = error(Msg, _), Pieces = [words(Msg), nl], LineContext0 = line_context(LineNumber, _), Context = context(FileName, LineNumber), Spec = spec($pred, severity_error, phase_read_files, Context, Pieces), !:Specs = [Spec | !.Specs] ). %---------------------------------------------------------------------------% % % Common code for reading. % :- func func_id_to_string(func_id) = string. func_id_to_string(FuncId) = String :- FuncId = func_id(PredOrFunc, Name, PredFormArity, ProcId), ( PredOrFunc = pf_predicate, PFStr = "p" ; PredOrFunc = pf_function, PFStr = "f" ), PredFormArity = pred_form_arity(Arity), NameStr = term_io.quoted_atom(Name), string.format("%s(%s, %d, %d)", [s(PFStr), s(NameStr), i(Arity), i(proc_id_to_int(ProcId))], String). %---------------------% :- type maybe_add_dot_temp ---> do_not_add_dot_temp ; add_dot_temp. :- pred find_and_write_analysis_file(Compiler::in, globals::in, maybe_add_dot_temp::in, write_entry(T)::in(write_entry), ext::in(ext_analysis), module_name::in, module_analysis_map(T)::in, string::out, io::di, io::uo) is det <= compiler(Compiler). find_and_write_analysis_file(Compiler, Globals, ToTmp, WriteEntry, Ext, ModuleName, ModuleResults, FileName, !IO) :- module_name_to_write_file_name(Compiler, Globals, Ext, ModuleName, FileName, !IO), ( ToTmp = add_dot_temp, WriteFileName = FileName ++ ".tmp" ; ToTmp = do_not_add_dot_temp, 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(FileStream), io.format(FileStream, "%d.\n", [i(version_number)], !IO), write_module_analysis_map(FileStream, WriteEntry, ModuleResults, !IO), io.close_output(FileStream, !IO) ; OpenResult = error(IOError), string.format("error opening `%s' for output: %s\n", [s(FileName), s(io.error_message(IOError))], IOErrorMsg), unexpected($pred, IOErrorMsg) ). :- pred write_module_analysis_map(io.text_output_stream::in, write_entry(T)::in(write_entry), module_analysis_map(T)::in, io::di, io::uo) is det. write_module_analysis_map(OutStream, WriteEntry, ModuleResults, !IO) :- map.foldl(write_module_analysis_map_entry(OutStream, WriteEntry), ModuleResults, !IO). :- pred write_module_analysis_map_entry(io.text_output_stream::in, write_entry(T)::in(write_entry), string::in, func_analysis_map(T)::in, io::di, io::uo) is det. write_module_analysis_map_entry(OutStream, WriteEntry, AnalysisName, FuncResults, !IO) :- map.foldl(write_module_analysis_func(OutStream, WriteEntry, AnalysisName), FuncResults, !IO). :- pred write_module_analysis_func(io.text_output_stream::in, write_entry(T)::in(write_entry), string::in, func_id::in, list(T)::in, io::di, io::uo) is det. write_module_analysis_func(OutStream, WriteEntry, AnalysisName, FuncId, FuncResultList, !IO) :- list.sort(FuncResultList, FuncResultListSorted), list.foldl( ( pred(FuncResult::in, !.IO::di, !:IO::uo) is det :- WriteEntry(OutStream, AnalysisName, FuncId, FuncResult, !IO) ), FuncResultListSorted, !IO). %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% empty_request_file(Info, Globals, ModuleName, !IO) :- ExtRequest = ext_cur_ngs_gs_max_ngs(ext_cur_ngs_gs_max_ngs_an_request), module_name_to_write_file_name(analysis_info_get_compiler(Info), Globals, ExtRequest, ModuleName, RequestFileName, !IO), get_analysis_debug_stream(MaybeDebugStream, !IO), ( MaybeDebugStream = no ; MaybeDebugStream = yes(DebugStream), io.format(DebugStream, "%% Removing request file %s\n", [s(RequestFileName)], !IO) ), io.file.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, they are more stable (they don't % depend on compiler internals) and are easier to debug, which is why % we don't use 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 maybe_write_analysis_cache_file(string::in, module_analysis_map(some_analysis_result)::in, list(error_spec)::in, io::di, io::uo) is det. maybe_write_analysis_cache_file(CacheFileName, ModuleResults, Specs, !IO) :- ( Specs = [], write_analysis_cache_file(CacheFileName, ModuleResults, !IO) ; Specs = [_ | _] ). :- 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.open_binary_output(TmpFileName, TmpFileResult, !IO), ( TmpFileResult = ok(TmpFileStream), pickle(TmpFileStream, init_analysis_picklers, ModuleResults, !IO), io.close_binary_output(TmpFileStream, !IO), io.file.rename_file(TmpFileName, CacheFileName, RenameResult, !IO), ( RenameResult = ok ; RenameResult = error(Error), % XXX Our caller should tell us what stream we should print % any error messages to. io.stderr_stream(StdErrStream, !IO), io.format(StdErrStream, "Error renaming %s: %s\n", [s(CacheFileName), s(io.error_message(Error))], !IO), io.file.remove_file(TmpFileName, _, !IO) ) ; TmpFileResult = error(Error), unexpected($pred, 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(io.binary_output_stream::in, picklers::in, univ::in, io::di, io::uo) is det. pickle_analysis_result(OutputStream, Pickles, Univ, !IO) :- det_univ_to_type(Univ, some_analysis_result(Call, Answer, Status)), Name = analysis_name(Call, Answer), pickle(OutputStream, Pickles, Name, !IO), pickle(OutputStream, Pickles, Call, !IO), pickle(OutputStream, Pickles, Answer, !IO), pickle(OutputStream, 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, AnalysisName : string, !State), ( if analyses(Compiler, AnalysisName, Analysis), analysis_type(_ : unit(Call), _ : unit(Answer)) = Analysis then 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) else unexpected($pred, AnalysisName) ). % 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"), [], dummy_context) ), ( 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. %---------------------------------------------------------------------------%