Files
mercury/compiler/make.timestamp.m
Zoltan Somogyi 86fc0e526e Check what files can be searched for where ...
... (mostly) in one place.

compiler/file_names.m:
    Change the module_name_to_search_file_name predicate to

    - take as input a specification of where the caller intends to search for
      the file name returned by this predicate (the new search_which_dirs
      input argument),

    - use insts to check, at compile time, whether this specification
      is compatible with the extension of the file (by intentionally causing
      the call to have a mode error if they are not compatible),

    - if they are compatible, return a "search authorization" that contains
      the information in the search_which_dirs argument in a form that
      should be known *only* to file_names.m (which issues the authorization)
      and find_module.m (which acts on such authorizations).

    To make the above possible, move the search_which_dirs type here
    from find_module.m, move many insts on the ext type here from several
    other modules, and add some new insts on that type.

    Improve documentation.

compiler/find_module.m:
    Delete the search_which_dirs type that this diff moves to file_names.m.
    Add in its place the search_auth_dirs type, which represents search
    authorizations. Make all predicates in this module take search
    authorizations instead of search specifications as inputs.
    The difference is that only the module_name_to_search_file_name
    predicate in file_names.m, and a few functions in this module,
    generate search authorizations that involve references to the
    various options containing lists of directory names.

compiler/make.file_names.m:
    Replace predicates that take a maybe_for_search argument with two
    predicates each, one for not for search and one for search. This is
    because the for search versions now take two extra arguments:
    a search_which_dirs input argument and a search_auth_dirs output argument.

    Factor out the common code from the resulting predicate pairs
    where this is worth while.

    Improve documentation.

compiler/make.timestamp.m:
    Replace predicates that take a maybe_for_search argument with two
    predicates each, one for not for search and one for search. This is
    because the for search versions and the not_for_search versions
    now call different predicates, and two separate predicates ends up
    being less code that tests around all the calls.

    Factor out the common code from the resulting predicate pairs
    where this is worth while.

    Delete the code that specifies the default list of directories
    in which to search for files with given extensions, since it is now
    in file_names.m (in a modified form).

compiler/analysis.framework.m:
    Delete the inst definitions that this diff moves to file_names.m.

compiler/compile_target_code.m:
compiler/file_kind.m:
    Provide more precise insts for the outputs of some predicates.
    This is because some of their callers need those insts to satisfy
    the new requirements of module_name_to_search_file_name.

compiler/make.top_level.m:
    Add a note.

compiler/read_modules.m:
    Conform to the changes above. Split the handling of source files
    (which do not require search) from the handling of .int* and .*opt files
    (which do).

compiler/analysis.file.m:
compiler/check_libgrades.m:
compiler/make.check_up_to_date.m:
compiler/make.get_module_dep_info.m:
compiler/make.module_target.m:
compiler/make.program_target.m:
compiler/mercury_compile_front_end.m:
compiler/mmc_analysis.m:
compiler/options_file.m:
compiler/write_deps_file.m:
    Conform to the changes above.
2024-11-09 02:40:45 +11:00

449 lines
18 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2023 The Mercury team.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%---------------------------------------------------------------------------%
%
% File: make.timestamp.m.
% Authors: stayl, wangp.
%
% Timestamp handling predicates used to implement `mmc --make'.
%
%---------------------------------------------------------------------------%
:- module make.timestamp.
:- interface.
:- import_module libs.
:- import_module libs.file_util.
:- import_module libs.globals.
:- import_module libs.timestamp.
:- import_module make.make_info.
:- import_module parse_tree.
:- import_module parse_tree.find_module.
:- import_module io.
:- import_module list.
:- import_module maybe.
%---------------------------------------------------------------------------%
:- func init_target_file_timestamp_map = target_file_timestamp_map.
%---------------------------------------------------------------------------%
% Find the timestamp for the given dependency file.
%
:- pred get_dependency_timestamp(io.text_output_stream::in, globals::in,
dependency_file::in, maybe_error(timestamp)::out,
make_info::in, make_info::out, io::di, io::uo) is det.
%---------------------------------------------------------------------------%
% get_target_timestamp(ProgressStream, Globals, TargetFile,
% MaybeTimestamp, !Info, !IO):
%
% Return the timestamp for TargetFile, if it exists.
%
:- pred get_target_timestamp(io.text_output_stream::in, globals::in,
target_file::in, maybe_error(timestamp)::out,
make_info::in, make_info::out, io::di, io::uo) is det.
% get_target_timestamp_search(ProgressStream, Globals, TargetFile,
% MaybeTimestamp, !Info, !IO):
%
% Search for TargetFile in the places where its target type indicates
% it should be searched for, and return its timestamp if it exists.
%
:- pred get_target_timestamp_search(io.text_output_stream::in, globals::in,
target_file::in, maybe_error(timestamp)::out,
make_info::in, make_info::out, io::di, io::uo) is det.
%---------------------------------------------------------------------------%
% get_file_timestamp(SearchAuthDirs, FileName,
% SearchDirs, MaybeTimestamp, !Info, !IO):
%
% Find the timestamp of the first file matching the given
% file name in one of the search directories. We return the list of
% directories we search in SearchDirs, or, if we got MaybeTimestamp
% from the cache, we return the list of directories we searched
% when the cache entry was created.
%
:- pred get_file_timestamp(search_auth_dirs::in, file_name::in,
list(dir_name)::out, maybe_error(timestamp)::out,
make_info::in, make_info::out, io::di, io::uo) is det.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module analysis.
:- import_module analysis.framework.
:- import_module analysis.operations.
:- import_module make.file_names.
:- import_module make.get_module_dep_info.
:- import_module make.hash.
:- import_module mdbcomp.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.module_dep_info.
:- import_module transform_hlds.
:- import_module transform_hlds.mmc_analysis.
:- import_module dir.
:- import_module map.
:- import_module require.
:- import_module string.
:- import_module version_hash_table.
%---------------------------------------------------------------------------%
init_target_file_timestamp_map =
version_hash_table.unsafe_init_default(target_file_hash).
%---------------------------------------------------------------------------%
get_dependency_timestamp(ProgressStream, Globals, DependencyFile,
MaybeTimestamp, !Info, !IO) :-
(
DependencyFile = dep_file(FileName),
get_file_timestamp(search_auth_cur_dir, FileName,
_SearchDirs, MaybeTimestamp, !Info, !IO)
;
DependencyFile = dep_target(Target),
get_target_timestamp_search(ProgressStream, Globals, Target,
MaybeTimestamp0, !Info, !IO),
( if
Target = target_file(_, module_target_c_header(header_mih)),
MaybeTimestamp0 = ok(_)
then
% Don't rebuild the `.o' file if an irrelevant part of a
% `.mih' file has changed. If a relevant part of a `.mih'
% file changed, the interface files of the imported module
% must have changed in a way that would force the `.c' and
% `.o' files of the current module to be rebuilt.
MaybeTimestamp = ok(oldest_timestamp)
else
MaybeTimestamp = MaybeTimestamp0
)
).
%---------------------------------------------------------------------------%
get_target_timestamp(ProgressStream, Globals, TargetFile, MaybeTimestamp,
!Info, !IO) :-
TargetFile = target_file(ModuleName, TargetType),
( if TargetType = module_target_analysis_registry then
% XXX LEGACY
module_target_to_file_name(Globals, $pred,
TargetType, ModuleName, FileName, _FileNameProposed, !IO),
get_target_timestamp_analysis_registry(ProgressStream, Globals,
TargetFile, FileName, MaybeTimestamp, !Info, !IO)
else
( if is_timestamp_in_cache(!.Info, TargetFile, Timestamp) then
trace [compile_time(flag("target_timestamp_cache")), io(!TIO)] (
verify_cached_target_file_timestamp(ProgressStream,
Globals, TargetFile, Timestamp, !.Info, _Info, !TIO)
),
MaybeTimestamp = ok(Timestamp)
else
% XXX LEGACY
module_maybe_nested_target_file_to_file_name(ProgressStream,
Globals, $pred, TargetFile,
FileName, _FileNameProposed, !Info, !IO),
get_target_timestamp_uncached(ProgressStream, Globals,
ModuleName, TargetType, FileName, MaybeTimestamp, !Info, !IO),
record_timestamp_if_ok(TargetFile, MaybeTimestamp, !Info)
)
).
get_target_timestamp_search(ProgressStream, Globals, TargetFile,
MaybeTimestamp, !Info, !IO) :-
TargetFile = target_file(ModuleName, TargetType),
( if TargetType = module_target_analysis_registry then
% XXX LEGACY
module_target_to_search_file_name(Globals, $pred,
TargetType, ModuleName, SearchAuthDirs,
FileName, _FileNameProposed, !IO),
get_target_timestamp_analysis_registry_search(ProgressStream, Globals,
SearchAuthDirs, TargetFile, FileName, MaybeTimestamp, !Info, !IO)
else
( if is_timestamp_in_cache(!.Info, TargetFile, Timestamp) then
trace [compile_time(flag("target_timestamp_cache")), io(!TIO)] (
verify_cached_target_file_timestamp_search(ProgressStream,
Globals, TargetFile, Timestamp, !.Info, _Info, !TIO)
),
MaybeTimestamp = ok(Timestamp)
else
% XXX LEGACY
module_maybe_nested_target_file_to_search_file_name(ProgressStream,
Globals, $pred, TargetFile, SearchAuthDirs,
FileName, _FileNameProposed, !Info, !IO),
get_target_timestamp_uncached_search(ProgressStream, Globals,
ModuleName, TargetType, FileName, SearchAuthDirs,
MaybeTimestamp, !Info, !IO),
record_timestamp_if_ok(TargetFile, MaybeTimestamp, !Info)
)
).
%---------------%
% The code points we are called from are hit very frequently, so it is
% worth caching timestamps by target_file. It avoids having to compute
% a file name for a target_file first, before looking up its timestamp.
% XXX Wouldn't the search be even faster if, instead of the module's
% name, our caller gave us its module_index?
%
:- pred is_timestamp_in_cache(make_info::in, target_file::in,
timestamp::out) is semidet.
is_timestamp_in_cache(Info0, TargetFile, Timestamp) :-
Cache0 = make_info_get_target_file_timestamp_map(Info0),
version_hash_table.search(Cache0, TargetFile, Timestamp).
:- pred record_timestamp_if_ok(target_file::in, maybe_error(timestamp)::in,
make_info::in, make_info::out) is det.
record_timestamp_if_ok(TargetFile, MaybeTimestamp, !Info) :-
(
MaybeTimestamp = ok(Timestamp),
TargetFileTimestampMap0 =
make_info_get_target_file_timestamp_map(!.Info),
version_hash_table.det_insert(TargetFile, Timestamp,
TargetFileTimestampMap0, TargetFileTimestampMap),
make_info_set_target_file_timestamp_map(TargetFileTimestampMap, !Info)
;
MaybeTimestamp = error(_)
% Do not record errors. These would usually be due to files
% not yet made, and the result would have to be updated
% once the file *is* made.
).
%---------------%
:- pred verify_cached_target_file_timestamp(io.text_output_stream::in,
globals::in, target_file::in, timestamp::in,
make_info::in, make_info::out, io::di, io::uo) is det.
verify_cached_target_file_timestamp(ProgressStream, Globals,
TargetFile, CachedTimestamp, !Info, !IO) :-
TargetFile = target_file(ModuleName, TargetType),
% XXX LEGACY
module_maybe_nested_target_file_to_file_name(ProgressStream,
Globals, $pred, TargetFile, FileName, _FileNameProposed, !Info, !IO),
get_target_timestamp_uncached(ProgressStream, Globals,
ModuleName, TargetType, FileName, MaybeFileTimestamp, !Info, !IO),
abort_for_any_verification_failure(CachedTimestamp,
MaybeFileTimestamp, !Info).
:- pred verify_cached_target_file_timestamp_search(io.text_output_stream::in,
globals::in, target_file::in, timestamp::in,
make_info::in, make_info::out, io::di, io::uo) is det.
verify_cached_target_file_timestamp_search(ProgressStream, Globals,
TargetFile, CachedTimestamp, !Info, !IO) :-
TargetFile = target_file(ModuleName, TargetType),
% XXX LEGACY
module_maybe_nested_target_file_to_search_file_name(ProgressStream,
Globals, $pred, TargetFile, SearchAuthDirs,
FileName, _FileNameProposed, !Info, !IO),
get_target_timestamp_uncached_search(ProgressStream, Globals,
ModuleName, TargetType, FileName, SearchAuthDirs,
MaybeFileTimestamp, !Info, !IO),
abort_for_any_verification_failure(CachedTimestamp,
MaybeFileTimestamp, !Info).
%---------------%
% We take a in,out pair of make_info args because we don't want calls
% to this predicate to be deleted as det code that computes nothing.
%
:- pred abort_for_any_verification_failure(timestamp::in,
maybe_error(timestamp)::in, make_info::in, make_info::out) is det.
:- pragma no_inline(pred(abort_for_any_verification_failure/4)).
abort_for_any_verification_failure(CachedTimestamp, MaybeFileTimestamp,
Info0, Info) :-
(
MaybeFileTimestamp = ok(FileTimestamp),
( if CachedTimestamp = FileTimestamp then
Info = Info0
else
string.format(
"target file timestamp differs: %s (cached) vs %s (actual)",
[s(timestamp_to_string(CachedTimestamp)),
s(timestamp_to_string(FileTimestamp))], Msg),
unexpected($pred, Msg)
)
;
MaybeFileTimestamp = error(Error),
string.format(
"target file timestamp differs: %s (cached) vs %s (actual)",
[s(timestamp_to_string(CachedTimestamp)), s(Error)], Msg),
unexpected($pred, Msg)
).
%---------------%
% Special treatment for `.analysis' files. If the corresponding
% `.analysis_status' file says the `.analysis' file is invalid,
% then we treat it as out of date.
%
:- pred get_target_timestamp_analysis_registry(io.text_output_stream::in,
globals::in, target_file::in, file_name::in, maybe_error(timestamp)::out,
make_info::in, make_info::out, io::di, io::uo) is det.
get_target_timestamp_analysis_registry(ProgressStream, Globals,
TargetFile, FileName, MaybeTimestamp, !Info, !IO) :-
TargetFile = target_file(ModuleName, TargetType),
FileTimestampMap0 = make_info_get_file_timestamp_map(!.Info),
( if map.search(FileTimestampMap0, FileName, MapValue) then
MapValue = {_SearchDirs, MaybeTimestamp}
else
do_read_module_overall_status(mmc, Globals, ModuleName, Status, !IO),
(
( Status = optimal
; Status = suboptimal
),
get_target_timestamp_uncached(ProgressStream, Globals,
ModuleName, TargetType, FileName, MaybeTimestamp, !Info, !IO)
;
Status = invalid,
MaybeTimestamp = error("invalid module"),
map.det_insert(FileName, {[], MaybeTimestamp},
FileTimestampMap0, FileTimestampMap),
make_info_set_file_timestamp_map(FileTimestampMap, !Info)
)
).
:- pred get_target_timestamp_analysis_registry_search(
io.text_output_stream::in, globals::in, search_auth_dirs::in,
target_file::in, file_name::in, maybe_error(timestamp)::out,
make_info::in, make_info::out, io::di, io::uo) is det.
get_target_timestamp_analysis_registry_search(ProgressStream, Globals,
SearchAuthDirs, TargetFile, FileName, MaybeTimestamp, !Info, !IO) :-
TargetFile = target_file(ModuleName, TargetType),
FileTimestampMap0 = make_info_get_file_timestamp_map(!.Info),
( if map.search(FileTimestampMap0, FileName, MapValue) then
MapValue = {_SearchDirs, MaybeTimestamp}
else
do_read_module_overall_status(mmc, Globals, ModuleName, Status, !IO),
(
( Status = optimal
; Status = suboptimal
),
get_target_timestamp_uncached_search(ProgressStream, Globals,
ModuleName, TargetType, FileName, SearchAuthDirs,
MaybeTimestamp, !Info, !IO)
;
Status = invalid,
MaybeTimestamp = error("invalid module"),
map.det_insert(FileName, {[], MaybeTimestamp},
FileTimestampMap0, FileTimestampMap),
make_info_set_file_timestamp_map(FileTimestampMap, !Info)
)
).
%---------------%
:- pred get_target_timestamp_uncached(io.text_output_stream::in,
globals::in, module_name::in, module_target_type::in, file_name::in,
maybe_error(timestamp)::out,
make_info::in, make_info::out, io::di, io::uo) is det.
get_target_timestamp_uncached(ProgressStream, Globals,
ModuleName, TargetType, FileName, MaybeTimestamp, !Info, !IO) :-
SearchAuthDirs = search_auth_cur_dir,
get_file_timestamp(SearchAuthDirs, FileName,
SearchDirs, MaybeTimestamp0, !Info, !IO),
get_target_timestamp_handle_any_errors(ProgressStream, Globals,
ModuleName, TargetType, FileName, SearchDirs,
MaybeTimestamp0, MaybeTimestamp, !Info, !IO).
:- pred get_target_timestamp_uncached_search(io.text_output_stream::in,
globals::in, module_name::in, module_target_type::in, file_name::in,
search_auth_dirs::in, maybe_error(timestamp)::out,
make_info::in, make_info::out, io::di, io::uo) is det.
get_target_timestamp_uncached_search(ProgressStream, Globals,
ModuleName, TargetType, FileName, SearchAuthDirs, MaybeTimestamp,
!Info, !IO) :-
get_file_timestamp(SearchAuthDirs, FileName,
SearchDirs, MaybeTimestamp0, !Info, !IO),
get_target_timestamp_handle_any_errors(ProgressStream, Globals,
ModuleName, TargetType, FileName, SearchDirs,
MaybeTimestamp0, MaybeTimestamp, !Info, !IO).
% If a `.opt' file in another directory doesn't exist, it just means
% that a library wasn't compiled with `--intermodule-optimization'.
% Similarly for `.analysis' files.
%
:- pred get_target_timestamp_handle_any_errors(io.text_output_stream::in,
globals::in, module_name::in, module_target_type::in, file_name::in,
list(dir_name)::in,
maybe_error(timestamp)::in, maybe_error(timestamp)::out,
make_info::in, make_info::out, io::di, io::uo) is det.
get_target_timestamp_handle_any_errors(ProgressStream, Globals,
ModuleName, TargetType, FileName, SearchDirs,
MaybeTimestamp0, MaybeTimestamp, !Info, !IO) :-
( if
MaybeTimestamp0 = error(_),
( TargetType = module_target_opt
; TargetType = module_target_analysis_registry
)
then
get_maybe_module_dep_info(ProgressStream, Globals,
ModuleName, MaybeModuleDepInfo, !Info, !IO),
( if
MaybeModuleDepInfo = some_module_dep_info(ModuleDepInfo),
module_dep_info_get_source_file_dir(ModuleDepInfo, ModuleDir),
% NOTE This test can't succeed for the non-search caller.
ModuleDir \= dir.this_directory
then
MaybeTimestamp = ok(oldest_timestamp),
FileTimestampMap0 = make_info_get_file_timestamp_map(!.Info),
map.set(FileName, {SearchDirs, MaybeTimestamp},
FileTimestampMap0, FileTimestampMap),
make_info_set_file_timestamp_map(FileTimestampMap, !Info)
else
MaybeTimestamp = MaybeTimestamp0
)
else
MaybeTimestamp = MaybeTimestamp0
).
%---------------------------------------------------------------------------%
get_file_timestamp(SearchAuthDirs, FileName,
SearchDirs, MaybeTimestamp, !Info, !IO) :-
FileTimestampMap0 = make_info_get_file_timestamp_map(!.Info),
( if map.search(FileTimestampMap0, FileName, MapValue) then
MapValue = {SearchDirs, MaybeTimestamp}
else
search_for_file_mod_time(SearchAuthDirs, FileName,
SearchDirs, SearchResult, !IO),
(
SearchResult = ok(TimeT),
Timestamp = time_t_to_timestamp(TimeT),
MaybeTimestamp = ok(Timestamp),
map.det_insert(FileName, {SearchDirs, MaybeTimestamp},
FileTimestampMap0, FileTimestampMap),
make_info_set_file_timestamp_map(FileTimestampMap, !Info)
;
SearchResult = error(_SearchError),
% XXX MAKE We should not ignore _SearchError.
% XXX SEARCH_ERROR SearchDirs
string.format("file `%s' not found", [s(FileName)], NotFoundMsg),
MaybeTimestamp = error(NotFoundMsg)
)
).
%---------------------------------------------------------------------------%
:- end_module make.timestamp.
%---------------------------------------------------------------------------%