[m-rev.] for review: pragma input_spec

Zoltan Somogyi zoltan.somogyi at runbox.com
Fri Aug 14 19:28:20 AEST 2026



On Fri, 14 Aug 2026 17:56:27 +1000, Julien Fischer <jfischer at opturion.com> wrote:
> > Add a new pragma, ":- pragma input_spec(...)".
> 
> I would prefer the name "input_mode_spec". Despite being slightly
> redundant (an input is a mode), having mode in the pragma name better
> ties it to the mode declaration.

I prefer input_spec, though input_mode_spec is good too.
We could even allow both, though this would complicate
documentation. Peter, do you want to break the tie?

> > or even from its predmode equivalent. This pragma is therefore
> > not so much a *program* optimization, as a *programmer time*
> > optimization.
> 
> Is this sufficiently common that it justifies the addition of this pragma?

This pragma may be useful 

- in modules where programmers already use this technique manually, and
- in modules where they do not.

We can count the first set of modules, but we cannot count the second set.
And it is probably the larger set, since applying this technique by hand
is *very* tedious. I remember that I wished for this pragma when working
on a couple of other modules of the compiler.

Also, as the last person to do real work on both modules of the compiler
that do employ this technique, ml_elim_nested.m and quantification.m,
I can tell you that the presence of those duplicated/triplicated mode
declarations also make maintainance significantly harder. They do this

- by reducing the amount of actual code that fits on a screen,
- by making it harder to match up the type of an argument with its mode, and
- by requiring you to either type near-duplicates of each mode declaration,
  or think about which unneeded near-copies you can *avoid* typing.

> > (If the second argument of the pragma were "add_to_in_mode",
> > the compiler would just add the new modes in front of the existing one,
> > instead of entirely replacing the existing mode.)
> 
> The diff does not allow replace_in_mode for exported predicates, which makes
> sense since there would be a inconsistency with the interface files.

I just finished replacing the manually-created near-duplicate mode decls
in those two modules with the new pragma. In ml_elim_nested.m,
I actually think that allowing replace_in_mode for exported predicates
is what is actually appropriate for that module: all its callers do specify
which specialization they want.

And (after the updated diff I posted) there is no problem with interface files.
The .int file for ml_elim_nested.m is attached. It contains the module qualified
inst_ctors' names in the input spec, and mmc, when processing any module
that imports this .int file, will apply the pragma to all the predicates
in that same module (which will be all the predicates declared in that same
.int file).

> It does seemingly allow add_to_mode for exported predicates. If that
> is the case, some comment on its interaction wiht the interface files is
> needed.

Yes, it is needed. It's just that yesterday I did not yet know for sure what that
interaction would have to be :-(

> > +            Types = choose_number(NonDuTypeCtors, "type", "type"),
> 
> Both of the alternatives there are "type".

Fixed.

> > +            NonDuPieces = [words("Error: the"), words(NDefns),
> > +                words("of the type"), words(NCtors)] ++
> > +                piece_list_to_color_pieces(color_subject, "and", [],
> > +                    NonDuTypeCtors) ++
> > +                [words(Are)] ++
> > +                color_as_incorrect([words("not discriminated union"),
> > +                    words(Types), suffix(".")]) ++
> > +                [nl],
> 
> In the case where NonDuTypeCtors has one member, the last part of
> that error message will say "is a not discriminated union type".

Good catch; will fix.

> > diff --git a/compiler/input_specialization.m b/compiler/input_specialization.m
> > new file mode 100644
> > index 000000000..768d4601e
> > --- /dev/null
> > +++ b/compiler/input_specialization.m
> > @@ -0,0 +1,248 @@
> > +%---------------------------------------------------------------------------%
> > +% vim: ft=mercury ts=4 sw=4 et
> > +%---------------------------------------------------------------------------%
> > +% Copyright (C) 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.
> > +%---------------------------------------------------------------------------%
> > +%
> > +% File: input_specialization.m.
> > +% Main author: zs.
> > +%
> > +% This file contains code for improving the names of head variables,
> > +% replacing HeadVar__n with user-given names whereever the clauses
> > +% agree on the names.
> 
> This is copy-and-pasted from compiler/headvar_names.m.

Sorry. I have now written a proper module header.

> > +input_specialize_in_module(!ModuleInfo) :-
> > +    module_info_get_input_spec_table(!.ModuleInfo, InputSpecTable),
> > +    module_info_get_valid_pred_ids(!.ModuleInfo, PredIds),
> 
> Won't this apply this transformation to uci preds and other compiler-generated
> predicates?  Is that intentional?

Yes, in the current form of the diff, it will apply to uci preds, and
no, this was not intentional.

Most of the time, the type to be input specialized is an enum type
that does not actually need an uci predicate, because values of that type
would be unified only with constants, which are deconstruct unifications,
and therefore do not invoke the type_ctor's unify predicate. And they
are not compared either. Creating input-specialized versions of the type's
uci predicates is therefore wasteful in that it creates more unused procedures
for dead-proc-elimination to get rid of, but it does not lead to bloat.

On the other hand, if some unusual code *did* unify some such values,
then the input-specialized versions could have tighter determinisms
(either failure or det) than the non-specialized versions (semidet).

I lean towards not input specializing uci preds. You?

> This seems suspicious. The HLDS has a lot of references to proc_ids.
> Why does this renumbering not invalidate the ones in, for example,
> the list of pragma exported procs?

I didn't think of that. However, foreign_export pragmas specify
the procedure to export by giving a vector of the argument's modes,
not by mode number, so renumbering should not be a problem.
The same should be true for rest of the language, since no part of
Mercury syntax allows code to refer to mode numbers.

The replacement of an exported mode is something we could detect,
though it would require processing all foreign_export pragmas
before any input_spec pragmas, which we do not (yet) do.

There are some other pragmas (such as external and obsolete)
that also refer to procedures by argument vector modes.
We would want to check for them as well. However, I don't think
any non-pragma part of Mercury allows such references.
Do you know of any?

> At least, had a comment giving
> a correctness argument.
> 
> > +%---------------------------------------------------------------------------%
> > +
> > +:- pred input_specialize_proc_table_in_given_arg(module_info::in,
> > +    int::in, input_spec_info::in,
> > +    list(proc_info)::in, list(proc_info)::out, maybe_changed::out) is det.
> > +
> > +input_specialize_proc_table_in_given_arg(_, _, _, [], [], unchanged).
> > +input_specialize_proc_table_in_given_arg(ModuleInfo, ArgNum, InputSpecInfo,
> > +        [HeadProcInfo0 | TailProcInfos0], ProcInfos, Changed) :-
> > +    proc_info_get_argmodes(HeadProcInfo0, ArgModes),
> > +    list.det_index1(ArgModes, ArgNum, SelectedArgMode),
> > +    ( if
> > +        mode_get_insts_semidet(ModuleInfo, SelectedArgMode,
> > +            InitInst, FinalInst),
> > +        InitInst = FinalInst,
> > +        (
> > +            InitInst = ground(shared, none_or_default_func)
> > +        ;
> > +            InitInst = defined_inst(user_inst(SymName, [])),
> > +            SymName = qualified(mercury_public_builtin_module, Name),
> > +            ( Name = "in" ; Name = "input" )
> 
> 
> "in" and "input" are modes, not insts.

Doh!

> > +
> > +%---------------------------------------------------------------------------%
> > +
> > +% XXX I (zs) think these should be in library/list.m.
> 
> No objection to adding them from me.

Will do in a separate change, so that the relationships
to the existing preds can be reviewed.

> The clauses in the .opt file are taken from the clauses_info, so should
> never rely on an input_spec pragma being present.  Adding input_spec
> pragmas to .opt files shoudl improve the ability of importing modules
> to optimized, but their absence is safe.  Is that a correct reading
> of this.

I originally thought it was, but on reflection, I am not sure.
The danger is that an opt-exported predicate p calls a non-exported
predicate q. If q has had one of its "in" modes replaced (as opposed
to added-to), I don't yet have a correctness argument proving
that this call won't cause an issue. I will get back to you on that.

> (It's probably worth noting this in prog_parse_tree.m at the spot
> where we define the contents of .opt files.

Agreed.

> > +                % Both forms start out as just containing the inst name
> > +                % that the program contains, and then both get module
> > +                % qualified along with the test of the compilation unit.
> 
> s/test/rest/

Fixed.

> >  :- type decl_pragma_oisu_info
> >      --->    decl_pragma_oisu_info(
> >                  oisu_type_ctor          :: type_ctor,
> 
> None of the additional tests cover the case where input_spec pragma occurs in
> a module inteface with replace_in_mode.

As mentioned above, I am not 100% sure we ought to keep the prohibition
on that. If we decide to keep it, I will add a test for that.

Thanks for the review. As for the next steps: I can either commit what I have,
unresolved correctness arguments and all, or wait until all such issues
have been reviewed and resolved. I favor the first approach, because
it makes creating diffs easier and more reliable, and the presence of any bugs
in a construct that noone knows about and therefore can't use will not
be detectable. What do you think?

Zoltan.



-------------- next part --------------
A non-text attachment was scrubbed...
Name: ml_backend.ml_elim_nested.int
Type: application/octet-stream
Size: 702 bytes
Desc: not available
URL: <http://lists.mercurylang.org/archives/reviews/attachments/20260814/3a84fadd/attachment-0001.obj>


More information about the reviews mailing list