Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bug in lazy pattern matching #3647

Merged
merged 5 commits into from
Mar 14, 2025
Merged

Conversation

Gbury
Copy link
Contributor

@Gbury Gbury commented Mar 3, 2025

In some situations (i.e. with specialization), flambda2 might propagate too much information during lazy pattern matching.

Consider a pattern matching on a lazy value, e.g. :

type t = A | B | C | D
let value : t lazy = lazy (...)
let foo () =
  match Lazy.force value with
  | D -> true | _ -> false

This correspond to the following pseudo-code right before flambda2:

let value = make_block ...

let foo () =
  let value = project_value_slot ... (* project from thye module block to get `value` *) in
  let tag = caml_obj_tag value in (* in parctice this is a C-call with a continuation, but it doesn't matter for the bug *) in
  if tag = 250 then (* case 1: forward tag *)
    let field = block_load value ... in
    apply_cont k field
  else if tag = 246 || tag = 244 then (* case 2 : lazy tag || forcing tag *) then
    apply_expr <k> lazy_force value
  else (* case 3 : value already shortcut *)
    apply_cont k value
  with k v ->
    let b  = v >= 3 (* comparison with the constructor `D` *) in
    ...

If we specialize/inline the continuation k at its call site in case 3, we end up with an integer comparison between 3 and v, but flambda2 can deduce that v = value and is the result of a make_block and therefore is a block. An integer comparison between an integer and a block is then considered dead code and flambda2 generates code that will SIGABRT at runtime. Obviously this is incorrect and if that simplification happens, just running foo twice with a GC in between will trigger the bug at runtime.

The fix is relatively simple and it only needs to add one more opaque identity in the code generated by lambda for pattern matching of lazy values, to prevent flambda2 from equating the result of the make_block and the value being compared after pattern matching.

@Gbury Gbury added bug Something isn't working to upstream PR should be sent to upstream OCaml flambda2 Prerequisite for, or part of, flambda2 labels Mar 3, 2025
@Gbury
Copy link
Contributor Author

Gbury commented Mar 3, 2025

Actually, this requires a bit more work for the fix to be better: the actual fix is to add a Popaque to the creation of lazy values, but this also requires adjusting the let-rec size computation.

@Gbury Gbury marked this pull request as draft March 3, 2025 17:47
This adds a new primitive in Lambda and Flambda2 to create a block
for a (not short-circuited) lazy value. These primitives allow to
more systematically handle lazy blocks correctly, for instance with
respect to the value-let-rec size computation, and in flambda to better
ensure that we do not track block appromixations for lazy values
(without relying on the frontend to insert opaque identities).
@Gbury Gbury force-pushed the more_opaque_in_lazy branch from 8bbe689 to bc70ebf Compare March 12, 2025 17:10
@Gbury Gbury marked this pull request as ready for review March 12, 2025 17:20
@Gbury
Copy link
Contributor Author

Gbury commented Mar 12, 2025

After some more discussions, this PR actually adds new primitive in lambda and flambda to better ensure we treat lazy adequately everywhere and never handle them as regular blocks without explicitly wanting to.

Copy link
Contributor

@lthls lthls left a comment

Choose a reason for hiding this comment

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

Fine with me, modulo the size estimate that I think we should preserve.

@lthls
Copy link
Contributor

lthls commented Mar 13, 2025

I have pushed the fix for code size, as well as a commit that gets rid of the allocation mode on lazy blocks (as discussed on Slack). @Gbury please check that everything is still fine and then merge.

@Gbury Gbury merged commit c308057 into ocaml-flambda:main Mar 14, 2025
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working flambda2 Prerequisite for, or part of, flambda2 to upstream PR should be sent to upstream OCaml
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants