Skip to content

Commit e00a4ea

Browse files
cknittclaude
andauthored
Unwrap a known Some(x) without the runtime when x is a variable (#9022)
Why: Matching on a value bound to Some(x) still called Primitive_option.some and valFromOption whenever x's type may contain undefined, although unwrapping Some(x) always gives back x. Lambda modified (simplify_alias): Before (lambda): let a = Psome(y) in ... Pval_from_option(a) After (lambda): let a = Psome(y) in ... y when: y is a variable or a non-allocating constant JS modified (matching on Some of a variable): Example: let null = (~val1: Nullable.t<int>, ~val2: Nullable.t<int>) => { let a = Some(val1) let b = Some(val2) switch (a, b) { | (_, Some(Value(_))) | (Some(Value(_)), _) => "a" | _ => "b" } } Before (js): tests/tests/src/option_optimisation.res let a = Primitive_option.some(val1); let b = Primitive_option.some(val2); let tmp = Primitive_option.valFromOption(b); if (tmp !== null && tmp !== undefined) { return "a"; } tmp === null; let tmp$1 = Primitive_option.valFromOption(a); if (tmp$1 == null) { After (js): tests/tests/src/option_optimisation.res if (val2 !== null && val2 !== undefined) { return "a"; } val2 === null; if (val1 == null) { Review: Why only variables and constants? Substituting any other payload would evaluate it a second time; Some(f()) keeps the runtime calls. Claude-Session: https://claude.ai/code/session_015yE4gCmTmJKVecrVt1PU8C Signed-off-by: Christoph Knittel <ck@cca.io> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 41a2860 commit e00a4ea

5 files changed

Lines changed: 125 additions & 8 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Drop `Primitive_option.some`/`valFromOption` calls when matching on `Some(x)` with `x` a variable.

‎compiler/core/lam_pass_remove_alias.ml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ let simplify_alias (meta : Lam_stats.t) (lam : Lambda.t) : Lambda.t =
8585
} as x -> (
8686
match Hash_ident.find_opt meta.ident_tbl v with
8787
| Some (OptionalBlock (l, _)) -> l
88+
(* [v] bound to [Some(l)]: unwrapping gives back [l], whether or not
89+
it was nested. Only substitute forms that are safe to repeat. *)
90+
| Some (Normal_optional (Lvar _ as l)) -> l
91+
| Some (Normal_optional (Lconst c as l))
92+
when not (Lambda.const_is_allocating c) ->
93+
l
8894
| _ -> if p = Pval_from_option_not_nest then lvar else x)
8995
| Lifthenelse (Lprim {primitive = Pis_not_none; args = [Lvar id]}, l2, l3)
9096
-> (
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Generated by ReScript, PLEASE EDIT WITH CARE
2+
3+
import * as Mocha from "mocha";
4+
import * as Test_utils from "./test_utils.mjs";
5+
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.mjs";
6+
7+
function nested(y) {
8+
if (y !== undefined) {
9+
return y;
10+
} else {
11+
return 0;
12+
}
13+
}
14+
15+
function poly(y) {
16+
return [y];
17+
}
18+
19+
let calls = {
20+
contents: 0
21+
};
22+
23+
function next() {
24+
calls.contents = calls.contents + 1 | 0;
25+
if (calls.contents > 1) {
26+
return calls.contents;
27+
}
28+
}
29+
30+
function effectful() {
31+
let n = Primitive_option.some(next());
32+
let n$1 = Primitive_option.valFromOption(n);
33+
if (n$1 !== undefined) {
34+
return n$1;
35+
} else {
36+
return 0;
37+
}
38+
}
39+
40+
Mocha.describe("Option_known_some_test", () => {
41+
Mocha.test("nested payload", () => {
42+
Test_utils.eq("File \"option_known_some_test.res\", line 35, characters 7-14", 0, 0);
43+
Test_utils.eq("File \"option_known_some_test.res\", line 36, characters 7-14", 3, 3);
44+
});
45+
Mocha.test("polymorphic payload", () => {
46+
Test_utils.eq("File \"option_known_some_test.res\", line 39, characters 7-14", [undefined], [undefined]);
47+
let v = Primitive_option.some(Primitive_option.some(undefined));
48+
Test_utils.eq("File \"option_known_some_test.res\", line 40, characters 7-14", [Primitive_option.some(undefined)], [Primitive_option.valFromOption(v)]);
49+
Test_utils.eq("File \"option_known_some_test.res\", line 41, characters 7-14", [1], [1]);
50+
});
51+
Mocha.test("payload with side effects", () => {
52+
Test_utils.eq("File \"option_known_some_test.res\", line 44, characters 7-14", 0, effectful());
53+
Test_utils.eq("File \"option_known_some_test.res\", line 45, characters 7-14", 1, calls.contents);
54+
Test_utils.eq("File \"option_known_some_test.res\", line 46, characters 7-14", 2, effectful());
55+
Test_utils.eq("File \"option_known_some_test.res\", line 47, characters 7-14", 2, calls.contents);
56+
});
57+
});
58+
59+
export {
60+
nested,
61+
poly,
62+
calls,
63+
next,
64+
effectful,
65+
}
66+
/* Not a pure module */
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
open Mocha
2+
open Test_utils
3+
4+
// Matching on [Some(x)] with [x] a variable reads [x] directly: no
5+
// Primitive_option.some / valFromOption calls in the output.
6+
let nested = (y: option<int>) =>
7+
switch Some(y) {
8+
| Some(Some(n)) => n
9+
| Some(None) => 0
10+
| None => -1
11+
}
12+
13+
let poly = (y: 'a) =>
14+
switch Some(y) {
15+
| Some(v) => [v]
16+
| None => []
17+
}
18+
19+
// A payload with side effects is evaluated once.
20+
let calls = ref(0)
21+
let next = () => {
22+
calls := calls.contents + 1
23+
calls.contents > 1 ? Some(calls.contents) : None
24+
}
25+
26+
let effectful = () =>
27+
switch Some(next()) {
28+
| Some(Some(n)) => n
29+
| Some(None) => 0
30+
| None => -1
31+
}
32+
33+
describe(__MODULE__, () => {
34+
test("nested payload", () => {
35+
eq(__LOC__, 0, nested(None))
36+
eq(__LOC__, 3, nested(Some(3)))
37+
})
38+
test("polymorphic payload", () => {
39+
eq(__LOC__, [None], poly((None: option<int>)))
40+
eq(__LOC__, [Some(None)], poly((Some(None): option<option<int>>)))
41+
eq(__LOC__, [1], poly(1))
42+
})
43+
test("payload with side effects", () => {
44+
eq(__LOC__, 0, effectful())
45+
eq(__LOC__, 1, calls.contents)
46+
eq(__LOC__, 2, effectful())
47+
eq(__LOC__, 2, calls.contents)
48+
})
49+
})

‎tests/tests/src/option_optimisation.mjs‎

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Generated by ReScript, PLEASE EDIT WITH CARE
22

3-
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.mjs";
43

54
function boolean(val1, val2) {
65
if (val2 || val1) {
@@ -11,15 +10,11 @@ function boolean(val1, val2) {
1110
}
1211

1312
function $$null(val1, val2) {
14-
let a = Primitive_option.some(val1);
15-
let b = Primitive_option.some(val2);
16-
let tmp = Primitive_option.valFromOption(b);
17-
if (tmp !== null && tmp !== undefined) {
13+
if (val2 !== null && val2 !== undefined) {
1814
return "a";
1915
}
20-
tmp === null;
21-
let tmp$1 = Primitive_option.valFromOption(a);
22-
if (tmp$1 == null) {
16+
val2 === null;
17+
if (val1 == null) {
2318
return "b";
2419
} else {
2520
return "a";

0 commit comments

Comments
 (0)