Discussion:
[m-dev.] Abstract exported dummy types
Zoltan Somogyi
2017-10-18 13:37:18 UTC
Permalink
Mantis bug 441 raises some interesting questions I would like your opinions on.

The bug test case has a module (vfm2.m) that abstract exports a type (fio(T))
that it privately defines to be a dummy type. When compiling that module,
the compiler knows that fio(T) and its instances are dummy types, so
when a function returns such an instance, it makes the return type
of the generated C function void. The other module in the program,
not knowing that vfm2.m privately defines fio(T) to be a dummy type,
expects a real, nonvoid return value. The root cause of the bug is therefore
the fact that the two modules make inconsistent assumptions about whether
instances of fio(T) are dummy types or not.

I know a way to fix the problem, but I am not sure we want to pay the cost,
because the proposed fix is complicated.

Suppose we have a module x.m

- that defines a type t that it knows is a dummy type, but other modules
don't know that, because it abstract exports t,
- that x.m defines a function f that has at least one argument of type t, and
- function is exported, and it is also called from within x.m.

Question is: should the signature of the C function we generate for f reflect
the fact that t is dummy or not?

Neither answer works. If we say it should, then callers from outside the module
will do the wrong thing; if we say it shouldn't, then callers from inside the module
will do the wrong thing.

One thing I think can do in such cases is to create a new inner function
for every function f whose argument list includes a type that is known to be dummy
only in this module. The original function would be compiled as if t were *not* dummy,
and this would be the function that is exported. Its implementation would call the
newly created inner function, which would treat t as a dummy type, and all calls
to f in x.m would be updated to refer to it. This would preserve the speedup we get
from not passing dummy arguments around inside x.m. However, this approach
has a problem: what do we do with references to f other than calls? If some other
function g takes f's address and constructs a closure with it, that closure can potentially
be called both inside x.m and outside it, and we are back where we started, with the
original problem. We can solve this new instance of the problem, e.g. by decreeing
that when generating higher order calls, we always pass all arguments including
the ones that look like dummy types to us (since they may look like nondummy types
to other users of the closure), but that is a solution I don't much like.

In any case, the approach above does not allow code passing around values of type t
to be optimized outside x.m. We could fix this in one of two ways.

One way would for us to put a pragma in x's interface files to say that f is a dummy type.
The other would be simply to generate an error when a dummy type is abstract exported,
requiring the programmer to choose between exporting the definition of the dummy type
and keeping the type private but making it non-dummy.

Note that other modules would need to be recompiled when either an exported dummy type
is changed (with the second way above), or when a nonexported type is changed either
from a dummy type to a nondummy type or vice versa. So the two ways above have similar
performance implications; the difference being that the second is in a way more "honest"
about the costs (and the benefits) of exposing the dummy-ness of the type.

I have a slight preference for the last solution above: generating errors for abstract-exported
dummy types. Judging by the absence of previous bug reports similar to Mantis 441, most people
won't even notice this new rule.

What does everyone else think?

Zoltan.
Julien Fischer
2017-10-18 23:53:51 UTC
Permalink
Hi Zoltan,
Post by Zoltan Somogyi
Mantis bug 441 raises some interesting questions I would like your opinions on.
The bug test case has a module (vfm2.m) that abstract exports a type (fio(T))
that it privately defines to be a dummy type. When compiling that module,
the compiler knows that fio(T) and its instances are dummy types, so
when a function returns such an instance, it makes the return type
of the generated C function void.
To be more accurate, fio(T) is:

1. an abstract type
2. a notag type
3. some instances of it can be dummy types, e.g. fio(unit)

vfm2.m is a cut-down version of the original program; the original also
exported functions that instantiate the type variable T to other
(non-dummy) types.
Post by Zoltan Somogyi
The other module in the program, not knowing that vfm2.m privately
defines fio(T) to be a dummy type, expects a real, nonvoid return
value. The root cause of the bug is therefore the fact that the two
modules make inconsistent assumptions about whether
instances of fio(T) are dummy types or not.
I know a way to fix the problem, but I am not sure we want to pay the cost,
because the proposed fix is complicated.
Suppose we have a module x.m
- that defines a type t that it knows is a dummy type, but other modules
don't know that, because it abstract exports t,
- that x.m defines a function f that has at least one argument of type t, and
- function is exported, and it is also called from within x.m.
Question is: should the signature of the C function we generate for f reflect
the fact that t is dummy or not?
Neither answer works. If we say it should, then callers from outside the module
will do the wrong thing; if we say it shouldn't, then callers from inside the module
will do the wrong thing.
One thing I think can do in such cases is to create a new inner function
for every function f whose argument list includes a type that is known to be dummy
only in this module. The original function would be compiled as if t were *not* dummy,
and this would be the function that is exported. Its implementation would call the
newly created inner function, which would treat t as a dummy type, and all calls
to f in x.m would be updated to refer to it. This would preserve the speedup we get
from not passing dummy arguments around inside x.m. However, this approach
has a problem: what do we do with references to f other than calls? If some other
function g takes f's address and constructs a closure with it, that closure can potentially
be called both inside x.m and outside it, and we are back where we started, with the
original problem. We can solve this new instance of the problem, e.g. by decreeing
that when generating higher order calls, we always pass all arguments including
the ones that look like dummy types to us (since they may look like nondummy types
to other users of the closure), but that is a solution I don't much like.
That seems quite complicated (and likely brittle).
Post by Zoltan Somogyi
In any case, the approach above does not allow code passing around values of type t
to be optimized outside x.m. We could fix this in one of two ways.
One way would for us to put a pragma in x's interface files to say that f is a dummy type.
Presumably, you mean add the pragma if _t_ is a dummy type? Or would
the pragma be on the function that returns the dummy type?
Post by Zoltan Somogyi
The other would be simply to generate an error when a dummy type is abstract exported,
requiring the programmer to choose between exporting the definition of the dummy type
and keeping the type private but making it non-dummy.
Removing the abstraction barrier may be potentially unsafe (in this case
it definitely is); making this type non-dummy (in the case where T is
dummy) could only be done by making it a non notag type which would
incur a memory allocation everytime a (non dummy) value is wrapped.
Post by Zoltan Somogyi
Note that other modules would need to be recompiled when either an exported dummy type
is changed (with the second way above), or when a nonexported type is changed either
from a dummy type to a nondummy type or vice versa. So the two ways above have similar
performance implications; the difference being that the second is in a way more "honest"
about the costs (and the benefits) of exposing the dummy-ness of the type.
Is it possible to not apply the dummy type optimization to abstract
exported types (i.e. the dummy type optimization would not apply in both
the defining module and any importing ones)?
Post by Zoltan Somogyi
I have a slight preference for the last solution above: generating
errors for abstract-exported dummy types.
What would the error message be generated for? One of the type
declarations or the function return types?
Post by Zoltan Somogyi
Judging by the absence of previous bug reports similar to Mantis 441,
most people won't even notice this new rule.
It requires a fairly specialised set of circumstances to trip over it.

Julien.
Peter Wang
2017-10-19 01:17:26 UTC
Permalink
Post by Julien Fischer
Hi Zoltan,
Post by Zoltan Somogyi
In any case, the approach above does not allow code passing around values of type t
to be optimized outside x.m. We could fix this in one of two ways.
One way would for us to put a pragma in x's interface files to say that f is a dummy type.
Presumably, you mean add the pragma if _t_ is a dummy type? Or would
the pragma be on the function that returns the dummy type?
Post by Zoltan Somogyi
The other would be simply to generate an error when a dummy type is abstract exported,
requiring the programmer to choose between exporting the definition of the dummy type
and keeping the type private but making it non-dummy.
Removing the abstraction barrier may be potentially unsafe (in this case
it definitely is); making this type non-dummy (in the case where T is
dummy) could only be done by making it a non notag type which would
incur a memory allocation everytime a (non dummy) value is wrapped.
Post by Zoltan Somogyi
Note that other modules would need to be recompiled when either an exported dummy type
is changed (with the second way above), or when a nonexported type is changed either
from a dummy type to a nondummy type or vice versa. So the two ways above have similar
performance implications; the difference being that the second is in a way more "honest"
about the costs (and the benefits) of exposing the dummy-ness of the type.
Is it possible to not apply the dummy type optimization to abstract
exported types (i.e. the dummy type optimization would not apply in both
the defining module and any importing ones)?
The compiler could silently disable the dummy type optimization on
abstract exported types, or require a pragma from the programmer to
disable the dummy type optimization on that type explicitly.

But I would still prefer if users could define their own abstract
exported dummy types, by adding a pragma in interface files.

We currently have "where type_is_abstract_enum" to expose something
about the representation of values of an abstract type while maintaing
the abstraction barrier, and I think we'll want something for
fixed-width integer types, too. Exposing the dumminess of a type seems
like just another aspect of the same idea.

Peter
Michael Day
2017-10-19 01:20:41 UTC
Permalink
Post by Peter Wang
But I would still prefer if users could define their own abstract
exported dummy types, by adding a pragma in interface files.
We currently have "where type_is_abstract_enum" to expose something
about the representation of values of an abstract type while maintaing
the abstraction barrier, and I think we'll want something for
fixed-width integer types, too. Exposing the dumminess of a type seems
like just another aspect of the same idea.
This sounds better than silently falling back to a different
representation or aborting, I think.

Michael
--
Prince: Print with CSS!
http://www.princexml.com
Julien Fischer
2017-10-19 01:36:34 UTC
Permalink
Post by Peter Wang
Post by Julien Fischer
Post by Zoltan Somogyi
Note that other modules would need to be recompiled when either an exported dummy type
is changed (with the second way above), or when a nonexported type is changed either
from a dummy type to a nondummy type or vice versa. So the two ways above have similar
performance implications; the difference being that the second is in a way more "honest"
about the costs (and the benefits) of exposing the dummy-ness of the type.
Is it possible to not apply the dummy type optimization to abstract
exported types (i.e. the dummy type optimization would not apply in both
the defining module and any importing ones)?
The compiler could silently disable the dummy type optimization on
abstract exported types, or require a pragma from the programmer to
disable the dummy type optimization on that type explicitly.
But I would still prefer if users could define their own abstract
exported dummy types, by adding a pragma in interface files.
We currently have "where type_is_abstract_enum" to expose something
about the representation of values of an abstract type while maintaing
the abstraction barrier, and I think we'll want something for
fixed-width integer types, too. Exposing the dumminess of a type seems
like just another aspect of the same idea.
There are two cases here:

1. (as in Mantis 441)

:- interface.
:- type fio(T).
:- implementation.
:- type fio(T) ---> fio(T).

2.
:- interface
:- type fio(T).
:- implementation.
:- type fio(T) ---> fio.

(1) is only dummy if T is dummy (e.g. fio(unit)), (2) is always dummy.
Ideally, you would want the dummy type optimization to always apply to
case (2).

Julien.
Zoltan Somogyi
2017-10-19 03:38:40 UTC
Permalink
Post by Julien Fischer
Post by Peter Wang
Post by Julien Fischer
Post by Zoltan Somogyi
Note that other modules would need to be recompiled when either an exported dummy type
is changed (with the second way above), or when a nonexported type is changed either
from a dummy type to a nondummy type or vice versa. So the two ways above have similar
performance implications; the difference being that the second is in a way more "honest"
about the costs (and the benefits) of exposing the dummy-ness of the type.
Is it possible to not apply the dummy type optimization to abstract
exported types (i.e. the dummy type optimization would not apply in both
the defining module and any importing ones)?
The compiler could silently disable the dummy type optimization on
abstract exported types, or require a pragma from the programmer to
disable the dummy type optimization on that type explicitly.
But I would still prefer if users could define their own abstract
exported dummy types, by adding a pragma in interface files.
We currently have "where type_is_abstract_enum" to expose something
about the representation of values of an abstract type while maintaing
the abstraction barrier, and I think we'll want something for
fixed-width integer types, too. Exposing the dumminess of a type seems
like just another aspect of the same idea.
OK, that seems to be the consensus.
Post by Julien Fischer
1. (as in Mantis 441)
:- interface.
:- type fio(T).
:- implementation.
:- type fio(T) ---> fio(T).
2.
:- interface
:- type fio(T).
:- implementation.
:- type fio(T) ---> fio.
(1) is only dummy if T is dummy (e.g. fio(unit)), (2) is always dummy.
Ideally, you would want the dummy type optimization to always apply to
case (2).
You are right; these two cases would need the compiler to put two
*different* pragmas into interface files, one to say that fio is a notag type,
the other to say that it is a dummy type.

Julien, how urgent is Mantis 441? I can do a fix that disables optimization
of fio(_) in vfm2.m and fixes the bug that way significantly more quickly
than a fix that generates and uses these new pragmas.

Zoltan.
Julien Fischer
2017-10-19 03:54:59 UTC
Permalink
Post by Zoltan Somogyi
Julien, how urgent is Mantis 441? I can do a fix that disables optimization
of fio(_) in vfm2.m and fixes the bug that way significantly more quickly
than a fix that generates and uses these new pragmas.
It isn't urgent at all -- it was just me trying some things out.

Julien.

Loading...