Skip to content

Matching compilation: fix a pessimization from #13076#13338

Merged
gasche merged 4 commits intoocaml:trunkfrom
gasche:matching-bug-13076-pessimization-fix
Aug 22, 2024
Merged

Matching compilation: fix a pessimization from #13076#13338
gasche merged 4 commits intoocaml:trunkfrom
gasche:matching-bug-13076-pessimization-fix

Conversation

@gasche
Copy link
Copy Markdown
Member

@gasche gasche commented Jul 29, 2024

This PR, which sits on top of #13337, proposes a fix for the pessimization of pattern-matching compilation analyzed in #13076 (comment) . The fix is precisely along the lines proposed in that comment, to track the "global" (or toplevel) partiality of pattern-matching separately from the "current" (or local) partiality of the submatrix.

@gasche
Copy link
Copy Markdown
Member Author

gasche commented Jul 29, 2024

In #13076 (comment) @ncik-roberts proposed two further testcases:

type nothing = |
type t = A | B | C of nothing
let f : bool * t -> int = function
  | true, A -> 3
  | false, A -> 4
  | _, B -> 5
  | _, C _ -> .

and

    type t =
      | A of int
      | B of string
      | C of string
      | D of string

    let compare t1 t2 =
      match t1, t2 with
      | A i, A j -> Int.compare i j
      | B l1, B l2 -> String.compare l1 l2
      | C l1, C l2 -> String.compare l1 l2
      | D l1, D l2 -> String.compare l1 l2
      | A _, (B _ | C _ | D _ ) -> -1
      | (B _ | C _ | D _ ), A _ -> 1
      | B _, (C _ | D _) -> -1
      | (C _ | D _), B _ -> 1
      | C _, D _ -> -1
      | D _, C _ -> 1

My checks seem to indicate that both examples indeed are pessimized in the current trunk, and this pessimization is fixed by the present PR. Good news! But I'm not sure why the second example is affected, as it does not rely on type-checker totality information. (For the first example: knowing that empty types and not inhabited requires type-checker information similar to GADTs.) I think that what is going on in the second example is related to the way or-pattern compilation discards some parts of the default environment, as discussed in details in #13076 (comment) .

Copy link
Copy Markdown
Contributor

@ncik-roberts ncik-roberts left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for looking into this! Indeed, I checked all of the regression cases I found, and this appears to fix them. The code seems correct and clear to me.

@ncik-roberts
Copy link
Copy Markdown
Contributor

Actually I notice a few things with polymorphic variants. These get Match_failure cases where they didn't before:

let f x y =
 match x, y with
 | _, `Y1 -> 0
 | `X1, `Y2 -> 1
 | (`X2 | `X3), `Y3 -> 2
 | `X1, `Y3
 | `X2, `Y2
 | `X3, _  -> 3


let check_results r1 r2 =
  match r1 r2 with
  | (Ok _ as r), _ | _, (Ok _ as r) -> r
  | (Error `A as r), Error _
  | Error _, (Error `A as r) -> r
  | (Error `B as r), Error `B -> r

I haven't looked into why yet (sorry, I'm timing out for the day soon).

@gasche
Copy link
Copy Markdown
Member Author

gasche commented Jul 31, 2024

Thanks for the polymorphic-variants examples. I haven't looked at the root cause either, but my guess would be that it is similar to the situation analyzed in details in #13076 (comment) : or-patterns trim the default environment (instead of growing the context with negative information), and there are a few cases where, before #13076, the pattern-matching compiler could locally detect that a sub-matrix is Total because its default environment becomes empty.

Thinking it, it may be possible to imitate this behavior now (with the global/current partiality split), by strengthening the current partiality into Total when the global partiality is also Total.

@gasche
Copy link
Copy Markdown
Member Author

gasche commented Jul 31, 2024

The fix I had in mind appears to work. I pushed your examples in the testsuite, and the fix. This is an addition to the previous fix (there are now two separate fixes for two separate code-quality regressions in #13076), so it would need a re-review.

| { cases = []; args = [] } -> comp_exit ctx m.default
| { cases = []; args = [] } ->
begin match comp_exit partial ctx m.default with
| None -> fatal_error "Matching: impossible empty matrix in a Total match"
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not fond of leaving "fatal errors" in the compiler codebase, and even less fond of adding them. This one is in fact not new, it was present before #13076, it corresponds to the following case:

| None -> fatal_error "Matching.comp_exit"
. This means that any code that was accepted by the compiler in 5.2 will remain accepted with the proposed change.

(The new fatal_error appears to be raised less often than the previous one: before comp_exit would fail unconditionally when the default environment is empty, and now we only fail when the match is known to be Total. But note that before #13076 there was an extra catch-all clause added to the default environment for partial matches, so partial matches would never see an empty environment. In other words, the conditions to raise the fatal error are in fact the same as before.)

Copy link
Copy Markdown
Contributor

@ncik-roberts ncik-roberts left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reviewed the final two commits and confirmed they fix the issues I flagged.


When a pattern-matching is globally Total, a jump out of a given
submatrix may only target a default submatrix correspond to
a further split. When it is Total, some jumps may fail to match
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"currently [Total]" in opposition to "globally [Total]"?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this was a typo / brain bug and I meant Partial. Thanks! Rebased.

@gasche gasche force-pushed the matching-bug-13076-pessimization-fix branch from 4dd3c6a to 38ef9f1 Compare July 31, 2024 17:06
gasche and others added 4 commits July 31, 2024 21:55
… default

This un-does a small pessimization of ocaml#13076, which was analyzed in
details in
ocaml#13076 (comment), but
at the time was believed to not affect any program in the wild.

Since then Nick Roberts found instances of this pattern involving
or-patterns and polymorphic variants.

Thanks to the new presentation of partiality information that
distingues the "global" and "current" information, the pessimization
is now easy to undo, as done in this commit.
Copy link
Copy Markdown
Contributor

@lpw25 lpw25 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving on the basis of @ncik-roberts review

@gasche gasche merged commit 7c7866e into ocaml:trunk Aug 22, 2024
@gasche
Copy link
Copy Markdown
Member Author

gasche commented Aug 22, 2024

Thanks! Merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants