Repository navigation
Compile integer operations with an identity operand to the operand - #8862
Closed
cristianoc wants to merge 1 commit into
Closed
cristianoc wants to merge 1 commit into
cristianoc wants to merge 1 commit into
Conversation
cristianoc
added a commit
that referenced
this pull request
Oct 14, 2026
Integer `x + 0`, `0 + x`, `x - 0`, `x * 1`, `1 * x` and `x / 1` compile
to `x`. A ReScript `int` value is a 32-bit integer in JavaScript, so the
`| 0` truncation (or `<< 0` for `* 1`) that these operations emitted
leaves the value unchanged. The JavaScript expression builders in
`Js_exp_make` (`int32_add`, `int32_minus`, `int32_mul`, `int32_div`)
return the other operand when one operand is the literal identity. The
dropped operand is a literal, so evaluation of the kept operand is
unchanged.
Example (rescript):
let addZero = (x: int) => x + 0
let subZero = (x: int) => x - 0
let mulOne = (x: int) => x * 1
let divOne = (x: int) => x / 1
Before (js):
function addZero(x) {
return x + 0 | 0;
}
function subZero(x) {
return x - 0 | 0;
}
function mulOne(x) {
return (x << 0);
}
function divOne(x) {
return x / 1 | 0;
}
After (js):
function addZero(x) {
return x;
}
function subZero(x) {
return x;
}
function mulOne(x) {
return x;
}
function divOne(x) {
return x;
}
Source: `bsc Example.res` (CommonJS, to stdout) with bsc built from
aa2078e (Before) and from this commit (After); function bodies shown, the
module header and exports are identical.
Changelog entry: - Compile integer `x + 0`, `0 + x`, `x - 0`, `x * 1`,
`1 * x` and `x / 1` to `x`. #8862
Review: Can an `int` operand hold a value outside the 32-bit range? Every
int the compiler computes is already truncated: compiling `-x`,
`Int.fromFloat(f)` and `Int.shiftRightUnsigned(x, y)` with this commit
gives `-x | 0`, `f | 0` and `(x >>> y) | 0`. A value outside the range
reaches an `int` only through an external or `%raw` typed `int`; for
those the result changes, for example `Number.MAX_SAFE_INTEGER + 0` gave
-1 and gives 9007199254740991. The compiler already relies on the same
assumption: `Plsrint` by the literal 0 compiles to the operand
(compiler/core/lam_compile_primitive.ml, `[e1; 0] -> e1`), dropping
`(x >>> 0) | 0`.
Review: Does `x / 1` still check for division by zero? The divisor is the
literal 1, so no check applies; the existing rule for a nonzero literal
divisor already skipped the runtime `div` call.
Area: codegen tests
Changelog: polish
Output: changed integer `+ 0`, `0 +`, `- 0`, `* 1`, `1 *`, `/ 1` on a
non-literal operand, now the bare operand; in tests/tests/src one existing
snapshot changes (gpr_3609_test.mjs, `0 + state._0 | 0` -> `state._0`),
and the stdlib build (make lib) emits no changed JS
Formats: none
Tests: +tests/tests/src/int_identity_ops_test.res
+tests/tests/src/int_identity_ops_test.mjs
~tests/tests/src/gpr_3609_test.mjs
Ran: make (dune exit 0); make lib (exit 0); make test (exit 0: 269 ounit
tests OK, tests/tests mocha 606 passing including Int_identity_ops_test,
stdlib mocha 744 passing); make checkformat (exit 0); yarn changelog:check
(fails only on the XXXX placeholder link, as expected before a PR exists)
Depends: #8861 (stacked)
PR: #8862
Signed-off-by: Cristiano Calcagno <ccrisccris@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cristianoc
force-pushed
the
cristianoc/int-identity-ops
branch
from
October 14, 2026 07:13
3189f1c to
faf5ecb
Compare
cristianoc
added this pull request to stack #8863
October 14, 2026 07:13
cristianoc
added a commit
that referenced
this pull request
Oct 14, 2026
Integer `x + 0`, `0 + x`, `x - 0`, `x * 1`, `1 * x` and `x / 1` compile
to `x`. A ReScript `int` value is a 32-bit integer in JavaScript, so the
`| 0` truncation (or `<< 0` for `* 1`) that these operations emitted
leaves the value unchanged. The JavaScript expression builders in
`Js_exp_make` (`int32_add`, `int32_minus`, `int32_mul`, `int32_div`)
return the other operand when one operand is the literal identity. The
dropped operand is a literal, so evaluation of the kept operand is
unchanged.
Example (rescript):
let addZero = (x: int) => x + 0
let subZero = (x: int) => x - 0
let mulOne = (x: int) => x * 1
let divOne = (x: int) => x / 1
Before (js):
function addZero(x) {
return x + 0 | 0;
}
function subZero(x) {
return x - 0 | 0;
}
function mulOne(x) {
return (x << 0);
}
function divOne(x) {
return x / 1 | 0;
}
After (js):
function addZero(x) {
return x;
}
function subZero(x) {
return x;
}
function mulOne(x) {
return x;
}
function divOne(x) {
return x;
}
Source: `bsc Example.res` (CommonJS, to stdout) with bsc built from
aa2078e (Before) and from this commit (After); function bodies shown, the
module header and exports are identical.
Changelog entry: - Compile integer `x + 0`, `0 + x`, `x - 0`, `x * 1`,
`1 * x` and `x / 1` to `x`. #8862
Review: Can an `int` operand hold a value outside the 32-bit range? Every
int the compiler computes is already truncated: compiling `-x`,
`Int.fromFloat(f)` and `Int.shiftRightUnsigned(x, y)` with this commit
gives `-x | 0`, `f | 0` and `(x >>> y) | 0`. A value outside the range
reaches an `int` only through an external or `%raw` typed `int`; for
those the result changes, for example `Number.MAX_SAFE_INTEGER + 0` gave
-1 and gives 9007199254740991. The compiler already relies on the same
assumption: `Plsrint` by the literal 0 compiles to the operand
(compiler/core/lam_compile_primitive.ml, `[e1; 0] -> e1`), dropping
`(x >>> 0) | 0`.
Review: Does `x / 1` still check for division by zero? The divisor is the
literal 1, so no check applies; the existing rule for a nonzero literal
divisor already skipped the runtime `div` call.
Area: codegen tests
Changelog: polish
Output: changed integer `+ 0`, `0 +`, `- 0`, `* 1`, `1 *`, `/ 1` on a
non-literal operand, now the bare operand; in tests/tests/src one existing
snapshot changes (gpr_3609_test.mjs, `0 + state._0 | 0` -> `state._0`),
and the stdlib build (make lib) emits no changed JS
Formats: none
Tests: +tests/tests/src/int_identity_ops_test.res
+tests/tests/src/int_identity_ops_test.mjs
~tests/tests/src/gpr_3609_test.mjs
Ran: make (dune exit 0); make lib (exit 0); make test (exit 0: 269 ounit
tests OK, tests/tests mocha 606 passing including Int_identity_ops_test,
stdlib mocha 744 passing); make checkformat (exit 0); yarn changelog:check
(passes)
Depends: #8861 (stacked)
PR: #8862
Signed-off-by: Cristiano Calcagno <ccrisccris@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cristianoc
force-pushed
the
cristianoc/int-identity-ops
branch
from
October 14, 2026 07:13
faf5ecb to
a253891
Compare
cristianoc
marked this pull request as ready for review
October 14, 2026 07:14
Integer `x + 0`, `0 + x`, `x - 0`, `x * 1`, `1 * x` and `x / 1` compile
to `x`. A ReScript `int` value is a 32-bit integer in JavaScript, so the
`| 0` truncation (or `<< 0` for `* 1`) that these operations emitted
leaves the value unchanged. The JavaScript expression builders in
`Js_exp_make` (`int32_add`, `int32_minus`, `int32_mul`, `int32_div`)
return the other operand when one operand is the literal identity. The
dropped operand is a literal, so evaluation of the kept operand is
unchanged.
Example (rescript):
let addZero = (x: int) => x + 0
let subZero = (x: int) => x - 0
let mulOne = (x: int) => x * 1
let divOne = (x: int) => x / 1
Before (js):
function addZero(x) {
return x + 0 | 0;
}
function subZero(x) {
return x - 0 | 0;
}
function mulOne(x) {
return (x << 0);
}
function divOne(x) {
return x / 1 | 0;
}
After (js):
function addZero(x) {
return x;
}
function subZero(x) {
return x;
}
function mulOne(x) {
return x;
}
function divOne(x) {
return x;
}
Source: `bsc Example.res` (CommonJS, to stdout) with bsc built from
aa2078e (Before) and from this commit (After); function bodies shown, the
module header and exports are identical.
Changelog entry: - Compile integer `x + 0`, `0 + x`, `x - 0`, `x * 1`,
`1 * x` and `x / 1` to `x`. #8862
Review: Can an `int` operand hold a value outside the 32-bit range? Every
int the compiler computes is already truncated: compiling `-x`,
`Int.fromFloat(f)` and `Int.shiftRightUnsigned(x, y)` with this commit
gives `-x | 0`, `f | 0` and `(x >>> y) | 0`. A value outside the range
reaches an `int` only through an external or `%raw` typed `int`; for
those the result changes, for example `Number.MAX_SAFE_INTEGER + 0` gave
-1 and gives 9007199254740991. The compiler already relies on the same
assumption: `Plsrint` by the literal 0 compiles to the operand
(compiler/core/lam_compile_primitive.ml, `[e1; 0] -> e1`), dropping
`(x >>> 0) | 0`.
Review: Does `x / 1` still check for division by zero? The divisor is the
literal 1, so no check applies; the existing rule for a nonzero literal
divisor already skipped the runtime `div` call.
Area: codegen tests
Changelog: polish
Output: changed integer `+ 0`, `0 +`, `- 0`, `* 1`, `1 *`, `/ 1` on a
non-literal operand, now the bare operand; in tests/tests/src one existing
snapshot changes (gpr_3609_test.mjs, `0 + state._0 | 0` -> `state._0`),
and the stdlib build (make lib) emits no changed JS
Formats: none
Tests: +tests/tests/src/int_identity_ops_test.res
+tests/tests/src/int_identity_ops_test.mjs
~tests/tests/src/gpr_3609_test.mjs
Ran: make (dune exit 0); make lib (exit 0); make test (exit 0: 269 ounit
tests OK, tests/tests mocha 606 passing including Int_identity_ops_test,
stdlib mocha 744 passing); make checkformat (exit 0); yarn changelog:check
(passes)
Depends: #8861 (stacked)
PR: #8862
Signed-off-by: Cristiano Calcagno <cristianoc@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cristianoc
force-pushed
the
cristianoc/int-identity-ops
branch
from
October 14, 2026 07:14
a253891 to
92a9528
Compare
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Compile integer operations with an identity operand to the operand
Integer
x + 0,0 + x,x - 0,x * 1,1 * xandx / 1compile tox. A ReScriptintvalue is a 32-bit integer in JavaScript, so the| 0truncation (or<< 0for* 1) these operations emitted leaves the value unchanged.int32_add,int32_minus,int32_mulandint32_divincompiler/core/js_exp_make.mlreturn the other operand when one operand is the literal identity. The dropped operand is a literal, so the kept operand is evaluated exactly as before.Stacked on #8861. This description is the commit's record converted to markdown. The rendered page is at https://claude.ai/artifact/6nbY5DMccWCk7yYDbuoZrH, or run
node scripts/commitRecords.js --out review.html master..cristianoc/int-identity-ops.return x + 0 | 0;return x;return x - 0 | 0;return x;return (x << 0);return x;return x / 1 | 0;return x;Source:
bsc Example.reswith bsc built from aa2078e (Before) and from this commit (After).Output: this changes the generated JS for these operations on a non-literal operand. One existing snapshot changes (
tests/tests/src/gpr_3609_test.mjs:0 + state._0 | 0becomesstate._0), andmake libproduces no changed stdlib JS. Formats unchanged.Review
intoperand hold a value outside the 32-bit range? Every int the compiler computes is already truncated:-x,Int.fromFloat(f)andInt.shiftRightUnsigned(x, y)compile to-x | 0,f | 0and(x >>> y) | 0. An out-of-range value reaches anintonly through an external or%rawthat is typedint. For those values the result does change:Number.MAX_SAFE_INTEGER + 0used to give -1 and now gives 9007199254740991. The compiler already makes the same assumption, sincePlsrintby the literal 0 compiles to its operand (compiler/core/lam_compile_primitive.ml), which drops(x >>> 0) | 0.x / 1still check for division by zero? The divisor is the literal 1, so no check applies. The existing rule for a nonzero literal divisor already skipped the runtimedivcall.Test plan
tests/tests/src/int_identity_ops_test.res(with its.mjs) covers each identity, the int32 boundaries, and checks that a side-effecting operand runs exactly once.make,make libandmake testall exit 0 (269 ounit tests OK, 606 tests/tests mocha passing, 744 stdlib mocha passing).make checkformatexits 0.🤖 Generated with Claude Code