[m-rev.] for review: Fix bug with direct-arg ctors and intermodule optimisation.

Zoltan Somogyi zoltan.somogyi at runbox.com
Sat Jan 10 10:30:57 AEDT 2026



On Fri,  9 Jan 2026 17:25:57 +1100, Peter Wang <novalazy at gmail.com> wrote:

> If the compiler decides that a du type should use the direct-arg
> representation for some of its constructors, it must include information
> about that into the .opt file of the module defining the type, in the
> form of `where direct_arg is' clauses, which will be used by modules
> opt-importing that module and that type. That information was not being
> included for du types defined in the *interface* section of a module.
> 
> Also fix a related issue that was uncovered: a word_aligned_pointer
> assertion on a foreign_type definition would have no effect if there is
> a corresponding no-tag du type definition for the same type constructor.

"corresponding" is redundant, given "for the same constructor".

> compiler/hlds_data.m:
>     Add a helper predicate.

Lots of modules import hlds_data.m. Please see whether it makes sense
to add the new predicate to e.g. intermod_decide.m instead.

>  :- pred add_foreign_if_word_aligned_ptr(module_info::in, decide_du_params::in,
> -    type_ctor::in, foreign_type_body::in,
> +    type_ctor::in, foreign_type_body::in, bool::in,
>      component_type_map::in, component_type_map::out,
>      list(error_spec)::in, list(error_spec)::out) is det.
>  
>  add_foreign_if_word_aligned_ptr(ModuleInfo, Params, TypeCtor,
> -        ForeignType, !ComponentTypeMap, !Specs) :-
> +        ForeignType, ExpectForTarget, !ComponentTypeMap, !Specs) :-
>      DirectArgMap = Params ^ ddp_direct_arg_map,
>      ( if map.search(DirectArgMap, TypeCtor, _DirectArgFunctors) then
>          DirectArgPieces = [words("Error:"), qual_type_ctor(TypeCtor),
> @@ -2199,7 +2221,12 @@ add_foreign_if_word_aligned_ptr(ModuleInfo, Params, TypeCtor,
>              true
>          )
>      else
> -        unexpected($pred, "foreign type is not for this backend")
> +        (
> +            ExpectForTarget = yes,
> +            unexpected($pred, "foreign type is not for this backend")
> +        ;
> +            ExpectForTarget = no
> +        )
>      ).

I would replace the bool with a bespoke type, maybe with functors named
require_foreign_type_for_cur_backend and allow_foreign_type_for_any_backend.

> --- a/compiler/intermod.m
> +++ b/compiler/intermod.m
> @@ -2,7 +2,7 @@
>  % vim: ft=mercury ts=4 sw=4 et
>  %---------------------------------------------------------------------------%
>  % Copyright (C) 1996-2012 The University of Melbourne.
> -% Copyright (C) 2013-2025 The Mercury team.
> +% Copyright (C) 2013-2026 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.
>  %---------------------------------------------------------------------------%
> @@ -219,8 +219,16 @@ some_type_needs_to_be_written([], no).
>  some_type_needs_to_be_written([_ - TypeDefn | TypeCtorDefns], NeedWrite) :-
>      hlds_data.get_type_defn_status(TypeDefn, TypeStatus),
>      ( if
> -        ( TypeStatus = type_status(status_abstract_exported)
> -        ; TypeStatus = type_status(status_exported_to_submodules)
> +        (
> +            TypeStatus = type_status(status_exported),
> +            % A du type defined in the interface section (exported) will need
> +            % to be written to the .opt file if it has any direct-arg ctors.
> +            hlds_data.get_type_defn_body(TypeDefn, TypeBody),
> +            is_du_type_with_direct_arg_ctors(TypeBody)

I would extend either this comment, or the one in intermod_decide.m,
to say *why*; put the relevant part of the log message in front of the people
reading this. The other can be either a copy of the comment, or a pointer to
the other comment.

> --- a/tests/hard_coded/Mmakefile
> +++ b/tests/hard_coded/Mmakefile
> @@ -111,6 +111,7 @@ ORDINARY_PROGS = \
>  	det_in_semidet_cntxt \
>  	digraph_tc \
>  	dir_fold \
> +	direct_arg_opt_imported \
>  	direct_arg_partial_inst_1 \
>  	direct_arg_partial_inst_2 \
>  	direct_arg_tags_1 \

The new test case should be in a different list from ORDINARY_PROGS,
because the foreign code of some predicates exists only for C.
Please add it to C_ONLY_PROGS instead.

> diff --git a/tests/hard_coded/direct_arg_opt_imported.m b/tests/hard_coded/direct_arg_opt_imported.m
> new file mode 100644
> index 000000000..7bd3d299e
> --- /dev/null
> +++ b/tests/hard_coded/direct_arg_opt_imported.m
> @@ -0,0 +1,36 @@
> +%---------------------------------------------------------------------------%
> +% vim: ts=4 sw=4 et ft=mercury
> +%---------------------------------------------------------------------------%
> +%
> +% With intermodule optimisation enabled, the body of indirect_new_object is
> +% inlined into main/2, including the construction:
> +%
> +%   yes_object(Ob : object) : maybe_object

This talks about the particulars of this test.

> +% When a type is opt-imported (but not imported) from a module, we depend on
> +% information from the corresponding .opt file to tell us which of its
> +% constructors use the direct-arg representation.

This paragraph is generic. Rewrite to explain the specific applicability of this
point to this test case.

> +% The problem was that that additional information about the type was missing
> +% from the .opt file because maybe_object is defined in the interface section
> +% of its module, i.e. it was already exported.
> +%

Likewise.

> +:- implementation.
> +
> +:- import_module direct_arg_opt_imported_helper_1.
> +% not imported: direct_arg_opt_imported_helper_1.sub.

I would expand on this and say: the bug we are testing for occurred
only in the absence of the second import.

> diff --git a/tests/hard_coded/direct_arg_opt_imported_helper_1.sub.m b/tests/hard_coded/direct_arg_opt_imported_helper_1.sub.m

Since the big rename, we include "helper_N" in the names of submodules,
as shown e.g. by prince_frameopt_helper_1.prince_frameopt_helper_2.m.

This would be simpler if you shortened the name of the test, maybe to 
direct_arg_opt.

> +:- implementation.
> +
> +:- import_module list.
> +:- import_module string.
> +
> +:- pragma foreign_decl("C", "
> +typedef struct {
> +    MR_Word magic;
> +} Object;
> +").
> +
> +new_object(object(-1)).
> +
> +:- pragma foreign_proc("C",
> +    new_object(Ob::out),
> +    [will_not_call_mercury, thread_safe, promise_pure, may_not_export_body],
> +"
> +    Ob = MR_GC_NEW(Object);
> +    Ob->magic = 0xdeadbeef;
> +").
> +
> +maybe_check_object(MaybeOb, !IO) :-
> +    (
> +        MaybeOb = yes_object(Ob),
> +        get_magic(Ob, Magic),
> +        ( if Magic = 0xdeadbeef then
> +            io.write_string("ok\n", !IO)
> +        else if Magic = -1 then
> +            io.write_string("ok\n", !IO)
> +        else
> +            io.format("BUG: Magic = %#x\n", [i(Magic)], !IO)
> +        )
> +    ;
> +        MaybeOb = no_object
> +    ).
> +
> +:- pred get_magic(object::in, int::out) is det.
> +
> +get_magic(object(Magic), Magic).
> +
> +:- pragma foreign_proc(c,
> +    get_magic(Ob::in, Magic::out),
> +    [will_not_call_mercury, thread_safe, promise_pure, may_not_export_body],
> +"
> +    Magic = Ob->magic;
> +    assert(Magic == 0xdeadbeef);
> +").

I would put the definitions of new_object and get_magic next to
each other. I would also try to find a better name than "object"
for the data structure, though I can see that finding a better name
is not easy :-(

Other than the above, the diff is fine.

Zoltan.


More information about the reviews mailing list