8000 Implement `let mutable` by jra4 · Pull Request #3964 · oxcaml/oxcaml · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Implement let mutable #3964

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

Merged
merged 40 commits into from
Jun 27, 2025
Merged

Implement let mutable #3964

merged 40 commits into from
Jun 27, 2025

Conversation

jra4
Copy link
Contributor
@jra4 jra4 commented May 2, 2025

Implement let mutable.

This feature makes explicit an existing optimization which puts refs in registers or the call stack (which eliminates an allocation). See jane/doc/extensions/_08-miscellaneous-extensions/let-mutable.md for more.

Most relevant functionality is tested in testsuite/tests/typing-local/let_mutable.ml, and other tests have been modified (e.g. typing-local/alloc.ml now makes sure let mutable does not allocate). Additional testing with unboxed types is done in testsuite/tests/typing-layouts/let_mutable.ml. Parse error tests have been added to testsuite/tests/parse-errors.

@jra4 jra4 requested review from ccasin and riaqn May 2, 2025 20:27
Copy link
github-actions bot commented May 2, 2025

Parser Change Checklist

This PR modifies the parser. Please check that the following tests are updated:

  • parsetree/source_jane_street.ml

This test should have examples of every new bit of syntax you are adding. Feel free to just check the box if your PR does not actually change the syntax (because it is refactoring the parser, say).

@jra4
Copy link
Contributor Author
jra4 commented May 2, 2025

I've been leaving myself comments in the diff (all start with jra:) for things I was unsure about. Looks like there are 7 comments I still haven't resolved.

@riaqn
Copy link
Contributor
riaqn commented May 5, 2025

I think there are some tricky questions to be asked and answered wrt modes - let's discuss in other channels.

Copy link
Contributor
@ccasin ccasin left a comment

Choose a reason for hiding this comment

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

Just a quick pass over some of the places where you had questions - we'll chat more tomorrow.

Copy link
Contributor
@ccasin ccasin left a comment

Choose a reason for hiding this comment

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

OK - I've now read everything except the modes bits we're going to discuss with @riaqn.

@jra4 jra4 force-pushed the jra.let-mutable branch from b089e8d to a466d56 Compare May 30, 2025 17:23
@riaqn
Copy link
Contributor
riaqn commented Jun 2, 2025

I pushed some change to mode checking, more needs to be done. For example, type checking let mutable x = ... should call check_construct_mutability.

@jra4
Copy link
Contributor Author
jra4 commented Jun 3, 2025

Feature request: not mutating a mutable variable should result in a warning

Implemented here: #4089

@jra4 jra4 force-pushed the jra.let-mutable branch from ba8312d to 8dd866d Compare June 12, 2025 21:15
@stedolan
Copy link
Contributor

@ccasin 's comment that let mutable variables cannot be captured by closures made me realise that this patch needs more tests for the various sneaky ways that OCaml lets you make closures:

  • Lazy:

    let mutable x = 42 in
    (lazy x, lazy (x + 1))
    

    This should be disallowed. You should test both lazy x and lazy (x+1) since single-identifier obviously-a-value lazy expressions are special-cased in the compiler.

  • Eta-expansion of reordered arguments

    let f ~y ~x = (x, y) in
    let mutable x = 42 in
    let g = f ~x in
    x <- 10;
    g 0
    

    This should be allowed, but the closure g should evaluate x before it's changed to 10, so this should yield (42, 0).

  • Locally defined functors

    module type T = sig module F () : sig val x end end
    ...
    let mutable x = 42 in
    (module (struct module F () = struct let x = x end end) : (module T))
    

    This should be disallowed - it's a sneaky closure.

From some local testing, I think the patch currently gets the first two right and fails with an internal compiler failure on the third, but all three should have tests.

@lthls
Copy link
Contributor
lthls commented Jun 17, 2025

Don't forget monadic operators:

let (let*) x f = f x in
let mutable x = 42 in
let* y = 0 in
x + y

(This example should be forbidden as x is captured by the implicit closure for y)

@jra4
Copy link
Contributor Author
jra4 commented Jun 17, 2025

@stedolan @lthls Tests have been added: 4c24f74

Every test has the behavior described except one: Locally defined functors is not giving me a compiler error like you said. The code you gave has syntax errors, so I fixed it up like this:

module type T = sig module F () : sig val x : int end end
let m =
  let mutable x = 42 in
  (module (struct module F () = struct let x = x end end) : T)

Though this might not be what you intended. Anyway, this compiles just fine for me, and it seems that x was evaluated when the module is defined, not when the functor is instantiated. For example:

module type T = sig module F () : sig val x : int end end
let m =
  let mutable x = 42 in
  let module M = (struct module F () = struct let x = x end end) in
  x <- 10;
  (module M : T)

let x =
  let module M = (val m : T) in
  let module M = M.F() in
  M.x

let () = print_int x

This program prints 42 even though mutable x is reassigned.

@jra4
Copy link
Contributor Author
jra4 commented Jun 17, 2025

Removing this test of unboxed products because it is not supported in lambda.

let triangle_i64_i32_f64 n =
  let mutable sum = #(#0L, #(#0l, #0.)) in
  for i = 1 to n do
    let #(a, #(b, c)) = sum in
    sum <- #(Int64_u.add a (Int64_u.of_int i),
             #(Int32_u.add b (Int32_u.of_int i),
               Float_u.add c (Float_u.of_int i)))
  done;
  sum

let () =
  let #(a, #(b, c)) = triangle_i64_i32_f64 10 in
  Printf.printf "%d %d %.2f\n" (Int64_u.to_int a)
                               (Int32_u.to_int b)
                               (Float_u.to_float c)

@jra4 jra4 force-pushed the jra.let-mutable branch from 4c24f74 to 5216aa6 Compare June 17, 2025 16:31
@jra4
Copy link
Contributor Author
jra4 commented Jun 17, 2025

Removing this test of unboxed products because it is not supported in lambda.

let triangle_i64_i32_f64 n =
  let mutable sum = #(#0L, #(#0l, #0.)) in
  for i = 1 to n do
    let #(a, #(b, c)) = sum in
    sum <- #(Int64_u.add a (Int64_u.of_int i),
             #(Int32_u.add b (Int32_u.of_int i),
               Float_u.add c (Float_u.of_int i)))
  done;
  sum

let () =
  let #(a, #(b, c)) = triangle_i64_i32_f64 10 in
  Printf.printf "%d %d %.2f\n" (Int64_u.to_int a)
                               (Int32_u.to_int b)
                               (Float_u.to_float c)

Continuing on this thread, I've updated let-mutable.md to reflect this lack of support. I've also added an error for unboxed products in translcore.ml. This error is tested in typing-local/let_mutable.ml under Test 21.

@jra4 jra4 force-pushed the jra.let-mutable branch from 2b645be to 683f05b Compare June 17, 2025 18:01
@stedolan
Copy link
Contributor

Anyway, this compiles just fine for me, and it seems that x was evaluated when the module is defined, not when the functor is instantiated

I'm confused, it fails for me with:

>> Fatal error: find_simple_from_id: Cannot find [Ident] x/8 in environment
Fatal error: exception Misc.Fatal_error

@jra4 jra4 force-pushed the jra.let-mutable branch from fbc143b to e94b66a Compare June 23, 2025 18:28
@jra4
Copy link
Contributor Author
jra4 commented Jun 23, 2025

@stedolan It is disallowed now: e94b66a#diff-748f86aa68fdeea50b75e3d9c839c6f365d55d0f2f49f141cbaab409ffa12247

It was actually an existing bug, fixed by Zesen's fix-pmod-functor-locks PR: #4168
(worth noting that let-mutable has been rebased onto fix-pmod-functor-locks, which has yet to be merged into main)

Copy link
Contributor
@ccasin ccasin left a comment

Choose a reason for hiding this comment

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

Looks good to merge once remaining comments are addressed. Thanks!

@mshinwell
Copy link
Collaborator

The unboxed product support is now in #4184 (which will merge into this PR).

@jra4 jra4 force-pushed the jra.let-mutable branch from 109ffdb to 1b47845 Compare June 27, 2025 17:23
@jra4 jra4 enabled auto-merge (squash) June 27, 2025 17:25
@jra4 jra4 merged commit c6ca48e into main Jun 27, 2025
23 of 24 checks passed
@jra4 jra4 deleted the jra.let-mutable branch June 27, 2025 17:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants
0