Skip to content

simplify types fix (fixes #107)#110

Merged
shwestrick merged 18 commits into
MPLLang:masterfrom
MatthewFluet:mpl-simplify-types-fix
Feb 15, 2020
Merged

simplify types fix (fixes #107)#110
shwestrick merged 18 commits into
MPLLang:masterfrom
MatthewFluet:mpl-simplify-types-fix

Conversation

@shwestrick

Copy link
Copy Markdown
Collaborator

This is the solution Matthew implemented to fix the simplifyTypes bug (#107) which was also present in mainline MLton (MLton/mlton#367) but had only been observed with MPL.

Previously, the `simplifyType` property had the type
`Type.t -> Type.t`, where `Type.unit` was used to signal an
uninhabited type.  However, this is ambiguous, as `Type.unit` is an
inhabited type.

The refactored `simplifyTypeOpt` property has the type
`Type.t -> Type.t option`, where `NONE` is used to signal
an uninhabited type.
Following the previous commit, further use a `Type.t option` and
`NONE` to signal an uninhabited type, rather than a `Type.t` and
`Type.unit`.

In particular, a `Statement.t` that purports to produce a value of an
uninhabited type must be unreachable.  This optimization isn't
expressible when an uninhabited type is signaled by `Type.unit`,
because many statements may produce a value of `Type.unit`.
After identifying an unreachable statement (e.g., the `ConApp` of a
`Useless` constructor), `SimplifyTypes` stops visiting subsequent
statements in the block.  However, visiting statements is necessary to
`setVarInfo` on the variables defined in the block.  Because
`SimplifyTypes` visits the blocks of a function via a DFS, it may
visit (necessarily unreachable) blocks that use variables that were
defined (but not visited) by a previously unreachable block.  This
triggers a `no varInfo property` compiler error.

This was previously noted (see the comment at the end of
MLton/mlton@19b07c0) and has now been observed in the wild
(see MPLLang#107).

To fix the bug, switch from a DFS to a dominator tree traversal, where
the dominator tree traversal does not visit any children of an
unreachable block.  This addresses the `no varInfo property` compiler
error, because any block that uses a variable must be dominated by the
block that defines the variable.
It is impossible for a block to be called with a value of an
uninhabited type.

In practice, such a block is usually the target of a `Case` transfer
for a `Useless` constructor. The corresponding branch of the `Case`
transfer will be eliminated.
It is impossible for a function to be called with a value of an
uninhabited type.
It is impossible for a primitive to be called with a value of an
uninhabited type.  Hence, a reachable primitive must have all
inhabited (and therefore useful) arguments.  As a further consequence,
a reachable `MLton_eq` and `MLton_equal` must have all inhabited (and
therefore useful) arguments and there will be no opportunity to
simplify the equality to `true`.
An array or vector of an uninhabited type is necessarily empty.

This is important, because while an `Array_sub` or `Vector_sub` of a
sequence of an uninhabited type will be recognized as unreachable, the
"origin" of the unreachability is the dominating bounds check that
must necessarily fail.  Replacing `Array_length` and `Vector_length`
of a sequence of an uninhabited type allows the dominating bounds
check to be simplified.
It is impossible to `ConApp`, `Call`, `Raise`, or `Return` with a
value of an uninhabited type.  Hence, a reachable `ConApp`, `Call`,
`Raise`, or `Return` must have all inhabited (and therefore useful)
arguments.
In a previous commit, it was noted that it is impossible for a block
to be called with a value of an uninhabited type.  This was used to
justify not including such a block in the transformed function.  One
example is a block that is the target of a `Case` transfer for a
`Useless` constructor.  The corresponding branch of the `Case`
transfer will be eliminated and the block will not have a use in the
transformed function.

However, it is possible for such a block to be the `cont` or `handle`
of a non-tail call.  While it will be impossible for the called
function to `Return` or `Raise` with a value of an uninhabited type,
the type of the function must be (mostly) preserved.  In particular,
the `returns` and `raises` are transformed via:

    val returns = Option.map (returns, keepSimplifyTypes)
    val raises = Option.map (raises, keepSimplifyTypes)

where `keepSimplifyTypes` drops uninhabited components.  Because the
`returns` and `raises` of the function are not eliminated, the `cont`
and `handle` blocks cannot be eliminated.  On the other hand, when the
block is the target of a `Case` transfer for a `Useless` constructor,
then there will be no use of the block in the transformed program and
the block will be eliminated by `Shrink`.

While it would be possible to adjust the `cont` and `handle` at a
`Call`, it is non-trivial.  For example, a function that purports to
return a value of an uninhabited type cannot actually `Return`, but
could continue to `Raise`.  However, the SSA IR cannot express a
non-tail call with a `handle` but no `cont`, so some dummy `cont`
block would nonetheless be required.  Moreover, it would require
inspecting the `returns` and `raises` types of the called function,
which are not currently recorded in a property for `SimplifyTypes`.
Finally, this kind of calling-convention adjustment is suitably
handled by `RemoveUnused`.
It is impossible to encounter a `ConRep.FFI` `ConApp`, because all
`ConRep.FFI` constructors were converted to `ConRep.Useful`.

It is impossible to encounter a `ConRep.Useless` `ConApp`, because it
such constructors were deemed useless.
All statements are either dead or kept (and none are deleted), so a
simple option suffices.
`MLton_eq` and `MLton_equal` at a type of cardinality 1 must return
`true`.
@shwestrick shwestrick merged commit f7c0632 into MPLLang:master Feb 15, 2020
@MatthewFluet MatthewFluet deleted the mpl-simplify-types-fix branch February 16, 2020 19:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants