numtypes 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE +12 -0
  3. package/LICENSE-APACHE +201 -0
  4. package/LICENSE-MIT +21 -0
  5. package/README.md +652 -0
  6. package/dist/lib/index.d.ts +22 -0
  7. package/dist/lib/index.d.ts.map +1 -0
  8. package/dist/lib/index.js +2 -0
  9. package/dist/lib/index.js.map +1 -0
  10. package/dist/transformer/analyze/analyze-source-file.d.ts +15 -0
  11. package/dist/transformer/analyze/analyze-source-file.d.ts.map +1 -0
  12. package/dist/transformer/analyze/analyze-source-file.js +605 -0
  13. package/dist/transformer/analyze/analyze-source-file.js.map +1 -0
  14. package/dist/transformer/analyze/get-contextual-domain.d.ts +19 -0
  15. package/dist/transformer/analyze/get-contextual-domain.d.ts.map +1 -0
  16. package/dist/transformer/analyze/get-contextual-domain.js +197 -0
  17. package/dist/transformer/analyze/get-contextual-domain.js.map +1 -0
  18. package/dist/transformer/analyze/get-expression-domain.d.ts +26 -0
  19. package/dist/transformer/analyze/get-expression-domain.d.ts.map +1 -0
  20. package/dist/transformer/analyze/get-expression-domain.js +804 -0
  21. package/dist/transformer/analyze/get-expression-domain.js.map +1 -0
  22. package/dist/transformer/analyze/type-domain.d.ts +41 -0
  23. package/dist/transformer/analyze/type-domain.d.ts.map +1 -0
  24. package/dist/transformer/analyze/type-domain.js +260 -0
  25. package/dist/transformer/analyze/type-domain.js.map +1 -0
  26. package/dist/transformer/ast.d.ts +10 -0
  27. package/dist/transformer/ast.d.ts.map +1 -0
  28. package/dist/transformer/ast.js +115 -0
  29. package/dist/transformer/ast.js.map +1 -0
  30. package/dist/transformer/diagnostics.d.ts +17 -0
  31. package/dist/transformer/diagnostics.d.ts.map +1 -0
  32. package/dist/transformer/diagnostics.js +30 -0
  33. package/dist/transformer/diagnostics.js.map +1 -0
  34. package/dist/transformer/domains.d.ts +11 -0
  35. package/dist/transformer/domains.d.ts.map +1 -0
  36. package/dist/transformer/domains.js +32 -0
  37. package/dist/transformer/domains.js.map +1 -0
  38. package/dist/transformer/index.d.ts +10 -0
  39. package/dist/transformer/index.d.ts.map +1 -0
  40. package/dist/transformer/index.js +60 -0
  41. package/dist/transformer/index.js.map +1 -0
  42. package/dist/transformer/operators.d.ts +16 -0
  43. package/dist/transformer/operators.d.ts.map +1 -0
  44. package/dist/transformer/operators.js +44 -0
  45. package/dist/transformer/operators.js.map +1 -0
  46. package/dist/transformer/options.d.ts +19 -0
  47. package/dist/transformer/options.d.ts.map +1 -0
  48. package/dist/transformer/options.js +17 -0
  49. package/dist/transformer/options.js.map +1 -0
  50. package/dist/transformer/symbols.d.ts +56 -0
  51. package/dist/transformer/symbols.d.ts.map +1 -0
  52. package/dist/transformer/symbols.js +270 -0
  53. package/dist/transformer/symbols.js.map +1 -0
  54. package/dist/transformer/transform/erase-imports.d.ts +14 -0
  55. package/dist/transformer/transform/erase-imports.d.ts.map +1 -0
  56. package/dist/transformer/transform/erase-imports.js +174 -0
  57. package/dist/transformer/transform/erase-imports.js.map +1 -0
  58. package/dist/transformer/transform/generated-coercions.d.ts +9 -0
  59. package/dist/transformer/transform/generated-coercions.d.ts.map +1 -0
  60. package/dist/transformer/transform/generated-coercions.js +22 -0
  61. package/dist/transformer/transform/generated-coercions.js.map +1 -0
  62. package/dist/transformer/transform/optimize-coercions.d.ts +11 -0
  63. package/dist/transformer/transform/optimize-coercions.d.ts.map +1 -0
  64. package/dist/transformer/transform/optimize-coercions.js +1702 -0
  65. package/dist/transformer/transform/optimize-coercions.js.map +1 -0
  66. package/dist/transformer/transform/transform-declaration-file.d.ts +9 -0
  67. package/dist/transformer/transform/transform-declaration-file.d.ts.map +1 -0
  68. package/dist/transformer/transform/transform-declaration-file.js +376 -0
  69. package/dist/transformer/transform/transform-declaration-file.js.map +1 -0
  70. package/dist/transformer/transform/transform-expression.d.ts +24 -0
  71. package/dist/transformer/transform/transform-expression.d.ts.map +1 -0
  72. package/dist/transformer/transform/transform-expression.js +545 -0
  73. package/dist/transformer/transform/transform-expression.js.map +1 -0
  74. package/dist/transformer/transform/transform-source-file.d.ts +10 -0
  75. package/dist/transformer/transform/transform-source-file.d.ts.map +1 -0
  76. package/dist/transformer/transform/transform-source-file.js +52 -0
  77. package/dist/transformer/transform/transform-source-file.js.map +1 -0
  78. package/dist/transformer/ts-compat.d.ts +4 -0
  79. package/dist/transformer/ts-compat.d.ts.map +1 -0
  80. package/dist/transformer/ts-compat.js +24 -0
  81. package/dist/transformer/ts-compat.js.map +1 -0
  82. package/docs/implementation-plan.md +335 -0
  83. package/docs/lib-implementation.md +77 -0
  84. package/docs/lowering-optimization-spec.md +1020 -0
  85. package/docs/project-structure.md +52 -0
  86. package/docs/transform-spec.md +2114 -0
  87. package/package.json +83 -0
@@ -0,0 +1,1020 @@
1
+ # Coercion Optimization Spec
2
+
3
+ This document defines the cleanup pass for removing redundant numeric coercions
4
+ that were generated by the numtypes lowering and for propagating local
5
+ numeric-domain proofs.
6
+
7
+ The transform spec in `docs/transform-spec.md` defines the conservative
8
+ correctness lowering. This document defines a later proof-based optimization
9
+ layer over the generated JavaScript AST. The pass may use ordinary JavaScript
10
+ coercion expressions as proof sources, but it may remove only coercion
11
+ expressions that the numtypes lowering generated. The optimization must never
12
+ be required for correctness.
13
+
14
+ The intended pipeline is deliberately two-step:
15
+
16
+ 1. The early transform aggressively inserts coercions wherever a domain boundary
17
+ might need one.
18
+ 2. The later optimization pass analyzes the resulting JavaScript and removes
19
+ generated coercions that are statically proven redundant.
20
+
21
+ This design keeps correctness simple. The first pass is allowed to be
22
+ over-conservative. The second pass is allowed to be purely local and
23
+ proof-driven.
24
+
25
+ The implementation tracks symbol-based local proofs inside function-like bodies,
26
+ handles straight-line statements, branches, loops, `switch`, and `try` joins,
27
+ recognizes the core coercion idioms plus `Math.imul`, sinks some redundant u32
28
+ closures through low32 bit-pattern locals, and invalidates only captured mutable
29
+ local proofs at possible reentry points such as unknown calls, getters, setters,
30
+ and computed element access. More aggressive range analysis, heap alias
31
+ analysis, async/generator suspension modeling, and closure escape precision can
32
+ be added without changing the correctness lowering.
33
+
34
+ ## Motivation
35
+
36
+ The conservative numtypes lowering closes every typed operation at the operand
37
+ and result boundaries:
38
+
39
+ ```typescript
40
+ function add(a: f64, b: f64): f64 {
41
+ a = f64(a);
42
+ b = f64(b);
43
+
44
+ return f64(a + b);
45
+ }
46
+ ```
47
+
48
+ Naive lowering may produce:
49
+
50
+ ```javascript
51
+ function add(a, b) {
52
+ a = +a;
53
+ b = +b;
54
+
55
+ return +(+a + +b);
56
+ }
57
+ ```
58
+
59
+ After `a = +a` and `b = +b`, the pass can prove that both local variables hold
60
+ primitive number values. For `f64`, JavaScript arithmetic over primitive numbers
61
+ already produces a Number value, so the return can be simplified:
62
+
63
+ ```javascript
64
+ function add(a, b) {
65
+ a = +a;
66
+ b = +b;
67
+
68
+ return a + b;
69
+ }
70
+ ```
71
+
72
+ The same idea applies to `i32`, `u32`, and `f32`, but each domain has different
73
+ closure rules. In particular, proving that operands are already closed does not
74
+ always prove that the next operation result is closed.
75
+
76
+ This proof does not have to come from numtypes types. It can come from ordinary
77
+ JavaScript syntax:
78
+
79
+ ```javascript
80
+ function add(a, b) {
81
+ a = +a;
82
+ b = +b;
83
+
84
+ return +(a + b);
85
+ }
86
+ ```
87
+
88
+ The assignments prove that `a` and `b` are primitive Number values for later
89
+ uses in the same control-flow region. If the final unary plus was generated by
90
+ numtypes lowering, it is redundant and can be removed. If the final unary plus
91
+ was written by the user, it must remain. This is similar in spirit to constant
92
+ folding, but the rewrite authority is limited to generated coercions.
93
+
94
+ The important design point is that the optimizer is not a numtypes type pass.
95
+ It is a generated-coercion cleanup pass with JavaScript proof facts. Numtypes
96
+ lowering produces the removable coercion idioms; user-authored JavaScript can
97
+ produce proof facts, but its coercion expressions are not removed.
98
+
99
+ ## Goals
100
+
101
+ - remove generated coercions that are provably redundant;
102
+ - use JavaScript AST shapes as proof sources, regardless of whether they came
103
+ from numtypes lowering or user-authored JavaScript;
104
+ - preserve user-authored coercion expressions even when they are redundant;
105
+ - allow the early transform to insert redundant coercions freely;
106
+ - keep the conservative numtypes transform as the correctness baseline;
107
+ - operate with symbol identity and control-flow facts, not name-based maps;
108
+ - preserve all observable JavaScript behavior of coercions;
109
+ - support function-level reasoning for locals, parameters, branches, loops,
110
+ closures, async functions, and generators;
111
+ - keep diagnostics independent from optimization.
112
+
113
+ ## Non-Goals
114
+
115
+ - no range analysis in the first implementation;
116
+ - no interprocedural proof propagation across function calls;
117
+ - no property, ordinary array, object-shape, or heap alias analysis in the first
118
+ implementation;
119
+ - no semantic rewrite that inserts new normalization assignments unless a later
120
+ explicit hoisting phase defines it separately.
121
+
122
+ ## Terminology
123
+
124
+ `Correctness lowering` means the numtypes transform that inserts the full
125
+ coercion shape required by `docs/transform-spec.md`.
126
+
127
+ `Optimization pass` means a function-scoped JavaScript AST pass that removes
128
+ redundant coercions when a proof exists.
129
+
130
+ `Runtime proof` means a static fact that a local expression is already closed
131
+ over a numeric domain at runtime.
132
+
133
+ `Storage domain` means the TypeScript/numtypes domain used for operation
134
+ dispatch. A storage domain is not a runtime proof.
135
+
136
+ `Closed value` means a value that has already passed through the domain closure
137
+ operation:
138
+
139
+ ```text
140
+ i32: x | 0
141
+ u32: x >>> 0
142
+ f32: Math.fround(x)
143
+ f64: +x
144
+ ```
145
+
146
+ `Low32 bit-pattern value` means a JavaScript Number whose low 32 bits are known
147
+ to match the corresponding u32 value, even if the Number itself is a signed
148
+ int32 result. For example, `x ^ y` and `Math.imul(x, y)` can produce negative
149
+ Numbers, but their low 32 bits are exactly what a later `>>> 0` observes.
150
+
151
+ `Coercion expression` means a JavaScript expression that closes its input over a
152
+ numeric domain:
153
+
154
+ ```text
155
+ i32: expr | 0
156
+ u32: expr >>> 0
157
+ f32: Math.fround(expr)
158
+ f64: +expr
159
+ ```
160
+
161
+ The optimizer may reason about any such expression, whether it was emitted by
162
+ numtypes lowering or written directly by the user. It may remove only coercion
163
+ expressions marked as generated by the numtypes lowering. It must use
164
+ JavaScript semantics, not TypeScript types, to decide whether a generated
165
+ rewrite is valid.
166
+
167
+ `Closed producer expression` means an expression that produces a closed value
168
+ without being a direct one-argument coercion. The initial producer set is:
169
+
170
+ ```text
171
+ i32: Math.imul(left, right)
172
+ ```
173
+
174
+ `Math.imul` returns a signed int32 Number, so a following `| 0` is redundant when
175
+ the `Math.imul` call is the expression being closed.
176
+
177
+ `Math.fround` and `Math.imul` are recognized only as ECMAScript intrinsics. A
178
+ lexically shadowed `Math` binding must not be treated as a numeric coercion
179
+ source. The optimization assumes these built-ins are not monkey-patched; code
180
+ that replaces `Math.fround` or `Math.imul` is outside the supported optimization
181
+ semantic model.
182
+
183
+ ## Fundamental Constraint
184
+
185
+ Numtypes annotations do not prove runtime value shape.
186
+
187
+ ```typescript
188
+ function f(a: i32, b: i32): i32 {
189
+ return i32(a + b);
190
+ }
191
+ ```
192
+
193
+ The annotations tell the transformer to dispatch `a + b` as an `i32`
194
+ operation. They do not prove that callers pass values already closed to i32.
195
+ Therefore this cannot be optimized to:
196
+
197
+ ```javascript
198
+ function f(a, b) {
199
+ return a + b; // wrong
200
+ }
201
+ ```
202
+
203
+ The optimizer may use a runtime proof only after it sees a dominating coercion
204
+ expression or assignment from a proven closed expression:
205
+
206
+ ```typescript
207
+ function f(a: i32, b: i32): i32 {
208
+ a = i32(a);
209
+ b = i32(b);
210
+
211
+ return i32(a + b);
212
+ }
213
+ ```
214
+
215
+ can become:
216
+
217
+ ```javascript
218
+ function f(a, b) {
219
+ a = a | 0;
220
+ b = b | 0;
221
+
222
+ return (a + b) | 0;
223
+ }
224
+ ```
225
+
226
+ The operand coercions are redundant. The result coercion is still required
227
+ because i32 addition can overflow.
228
+
229
+ Plain JavaScript can provide proof facts, but user-authored coercions are not
230
+ removed:
231
+
232
+ ```javascript
233
+ function f(a, b) {
234
+ a = a | 0;
235
+ b = b | 0;
236
+
237
+ return ((a | 0) + (b | 0)) | 0;
238
+ }
239
+ ```
240
+
241
+ must remain:
242
+
243
+ ```javascript
244
+ function f(a, b) {
245
+ a = a | 0;
246
+ b = b | 0;
247
+
248
+ return ((a | 0) + (b | 0)) | 0;
249
+ }
250
+ ```
251
+
252
+ If a later numtypes-generated coercion uses the same proven locals, that
253
+ generated coercion may be removed. The hand-written coercions above still stay
254
+ in the output.
255
+
256
+ ## Proof Domain
257
+
258
+ The pass tracks facts for local bindings and temporary expressions:
259
+
260
+ ```text
261
+ unknown
262
+ closed-i32
263
+ closed-u32
264
+ closed-f32
265
+ closed-f64
266
+ ```
267
+
268
+ `closed-f64` means the value is known to be a primitive JavaScript Number. It is
269
+ not a stronger machine-level guarantee such as "stored as a V8 HeapNumber".
270
+
271
+ The implementation tracks local bindings whose symbol is local to the current
272
+ function-like body:
273
+
274
+ - parameters;
275
+ - `let` and `const` locals;
276
+ - compiler-created temporaries, if present;
277
+ - `var` locals, if their function scope and hoisting behavior are modeled
278
+ conservatively.
279
+
280
+ The implementation does not track:
281
+
282
+ - object properties;
283
+ - element accesses;
284
+ - globals;
285
+ - imports;
286
+ - exported mutable bindings;
287
+ - captured mutable bindings after a possible external reentry point.
288
+
289
+ ### Opt-In TypedArray Element Proofs
290
+
291
+ TypedArray element reads are not treated as closed values by default. A type
292
+ annotation such as `Float64Array` or `Int32Array` can be wrong at runtime, and
293
+ out-of-bounds reads return `undefined`:
294
+
295
+ ```javascript
296
+ +values[index] // Number conversion: undefined becomes NaN
297
+ values[index] // raw read: undefined
298
+ ```
299
+
300
+ Therefore removing a generated coercion around a typed-array element read is
301
+ allowed only when the user enables `optimizeTypedArrayElementAccess` and the
302
+ optimizer also proves an in-bounds access.
303
+
304
+ The option is an explicit runtime invariant: the user is asserting that values
305
+ typed as numeric TypedArrays are actually the corresponding intrinsic
306
+ TypedArray objects for the optimized region, and that their buffers are not
307
+ detached while the proof is used. Without that opt-in, generated coercions
308
+ around typed-array reads are preserved.
309
+
310
+ The initial bounds proof is deliberately narrow. It recognizes simple counted
311
+ loops such as:
312
+
313
+ ```typescript
314
+ for (let i = 0; i < values.length; i += 1) {
315
+ out[i] = i32(values[i]);
316
+ }
317
+
318
+ for (let i = 0; i + 3 < values.length; i += 4) {
319
+ out[i / 4] = f64(values[i] + values[i + 1]);
320
+ }
321
+ ```
322
+
323
+ When the proof holds, element reads are treated as:
324
+
325
+ ```text
326
+ Int8Array, Int16Array, Int32Array -> closed-i32
327
+ Uint8Array, Uint8ClampedArray, Uint16Array,
328
+ Uint32Array -> closed-u32
329
+ Float32Array -> closed-f32
330
+ Float64Array -> closed-f64
331
+ ```
332
+
333
+ This proof removes only generated coercions. A user-authored coercion around a
334
+ typed-array read is still preserved.
335
+
336
+ Property and element reads/writes may execute arbitrary code through getters,
337
+ setters, proxies, computed keys, or iterator machinery. They do not by
338
+ themselves mutate a non-captured local primitive binding in the current
339
+ activation record. Therefore they preserve proofs for non-captured locals and
340
+ invalidate proofs for mutable locals captured by nested functions.
341
+
342
+ Expression statements do not create local binding proofs by themselves:
343
+
344
+ ```javascript
345
+ +a; // converts the current value, but does not assign it back to a
346
+ ```
347
+
348
+ The next read of `a` is not proven `closed-f64`. A proof is created by a write
349
+ or declaration that stores the coerced value:
350
+
351
+ ```javascript
352
+ a = +a; // a becomes closed-f64
353
+ const x = +a; // x becomes closed-f64
354
+ ```
355
+
356
+ ## Pass Placement
357
+
358
+ The preferred architecture is:
359
+
360
+ ```text
361
+ source analysis
362
+ -> aggressive correctness lowering
363
+ -> function-level JavaScript coercion optimization
364
+ -> final emit
365
+ ```
366
+
367
+ The optimization pass can also run on already-emitted JavaScript before final
368
+ printing. It does not require numtypes metadata. Numtypes lowering metadata may
369
+ help preserve source maps or debugging information, but it is not part of the
370
+ proof model.
371
+
372
+ The optimizer should recognize coercion idioms structurally from the AST. It
373
+ must still preserve observable JavaScript behavior. A generated coercion may be
374
+ removed only when evaluating the replacement expression has the same effects
375
+ and value as evaluating the original expression. A user-authored coercion must
376
+ not be removed.
377
+
378
+ ## Domain-Specific Elision Rules
379
+
380
+ ### f64
381
+
382
+ For `f64`, the closure operation is unary `+`, which performs `ToNumber`.
383
+
384
+ Generated `+expr` may be removed only when `expr` is already proven
385
+ `closed-f64`. User-authored `+expr` is preserved.
386
+
387
+ If both operands of a numeric operation are proven `closed-f64`, the result of
388
+ ordinary JavaScript arithmetic is also `closed-f64`.
389
+
390
+ ```javascript
391
+ a // removable when a is proven closed-f64
392
+ (a + b) // removable when a + b is proven closed-f64
393
+ ```
394
+
395
+ Example:
396
+
397
+ ```javascript
398
+ a;
399
+ +a;
400
+ ```
401
+
402
+ can become:
403
+
404
+ ```javascript
405
+ a;
406
+ ```
407
+
408
+ only if `a` is already proven `closed-f64`.
409
+
410
+ Without proof, `+a` must stay. Removing it can change observable behavior:
411
+
412
+ ```javascript
413
+ +"1" // 1
414
+ "1" // "1"
415
+ +1n // throws
416
+ 1n // 1n
417
+ ```
418
+
419
+ ### f32
420
+
421
+ For `f32`, the closure operation is `Math.fround`.
422
+
423
+ Generated `Math.fround(expr)` may be removed only when `expr` is already proven
424
+ `closed-f32`. User-authored `Math.fround(expr)` is preserved.
425
+
426
+ If both operands are proven `closed-f32`, JavaScript still evaluates arithmetic
427
+ in Number precision. The operation result is not automatically `closed-f32`.
428
+ The result closure must remain unless a future exactness or range analysis
429
+ proves it unnecessary.
430
+
431
+ ```javascript
432
+ Math.fround(Math.fround(a) + Math.fround(b))
433
+ ```
434
+
435
+ can become:
436
+
437
+ ```javascript
438
+ Math.fround(a + b)
439
+ ```
440
+
441
+ when `a` and `b` are proven `closed-f32`.
442
+
443
+ It cannot become:
444
+
445
+ ```javascript
446
+ a + b
447
+ ```
448
+
449
+ because `a + b` is computed as a JavaScript Number.
450
+
451
+ ### i32
452
+
453
+ For `i32`, the closure operation is `| 0`.
454
+
455
+ Generated operand closures may be removed when the operand is proven
456
+ `closed-i32`.
457
+
458
+ Result closures for arithmetic operators must usually remain:
459
+
460
+ ```javascript
461
+ ((a | 0) + (b | 0)) | 0
462
+ ```
463
+
464
+ can become:
465
+
466
+ ```javascript
467
+ (a + b) | 0
468
+ ```
469
+
470
+ when `a` and `b` are proven `closed-i32`.
471
+
472
+ It cannot become:
473
+
474
+ ```javascript
475
+ a + b
476
+ ```
477
+
478
+ because i32 addition can overflow:
479
+
480
+ ```javascript
481
+ 2147483647 + 1 // 2147483648
482
+ (2147483647 + 1) | 0 // -2147483648
483
+ ```
484
+
485
+ Some operators are closure-preserving for i32:
486
+
487
+ ```text
488
+ ~x
489
+ x | y
490
+ x & y
491
+ x ^ y
492
+ x << y
493
+ x >> y
494
+ ```
495
+
496
+ For these operators, a generated i32 result closure is redundant when every
497
+ required operand is proven closed and the shift count has been safely coerced.
498
+
499
+ Unsigned right shift `>>>` produces a u32-style result, not an i32 result. An
500
+ i32 boundary after `>>>` must not be removed unless the target domain proof is
501
+ explicitly re-established.
502
+
503
+ `Math.imul(left, right)` is an i32 producer. If `left` or `right` are already
504
+ proven `closed-i32`, generated operand coercions may be removed. The call
505
+ result is itself proven `closed-i32`, so a generated direct result closure is
506
+ redundant:
507
+
508
+ ```javascript
509
+ Math.imul(a | 0, b | 0) | 0
510
+ ```
511
+
512
+ can become:
513
+
514
+ ```javascript
515
+ Math.imul(a, b)
516
+ ```
517
+
518
+ when `a` and `b` are proven `closed-i32`.
519
+
520
+ ### u32
521
+
522
+ For `u32`, the closure operation is `>>> 0`.
523
+
524
+ Generated operand closures may be removed when the operand is proven
525
+ `closed-u32`.
526
+
527
+ Result closures for arithmetic operators must usually remain:
528
+
529
+ ```javascript
530
+ ((a >>> 0) + (b >>> 0)) >>> 0
531
+ ```
532
+
533
+ can become:
534
+
535
+ ```javascript
536
+ (a + b) >>> 0
537
+ ```
538
+
539
+ when `a` and `b` are proven `closed-u32`.
540
+
541
+ Most JavaScript bitwise operators return signed int32 results. Therefore u32
542
+ result closures usually remain for:
543
+
544
+ ```text
545
+ |
546
+ &
547
+ ^
548
+ <<
549
+ >>
550
+ ~
551
+ ```
552
+
553
+ Unsigned right shift `>>>` produces a non-negative uint32-range Number, so a
554
+ u32 result closure after `x >>> y` can be redundant when the operands and shift
555
+ count are proven safe.
556
+
557
+ Bitwise `&` with a non-negative int31 literal mask also produces a non-negative
558
+ int32 Number. Therefore a generated u32 closure around expressions such as
559
+ `input[i] & 0xff` may be removed:
560
+
561
+ ```javascript
562
+ (input[i] & 0xff) >>> 0
563
+ ```
564
+
565
+ can become:
566
+
567
+ ```javascript
568
+ input[i] & 0xff
569
+ ```
570
+
571
+ Only masks in `0..0x7fffffff` qualify. A mask with the high signed bit set can
572
+ produce a negative int32 Number and must not be treated as closed-u32.
573
+
574
+ Modulo by a positive integer literal is a u32 producer when the left operand is
575
+ already proven closed-u32. The result is in `0..modulus - 1`, so a generated
576
+ closure around the modulo result is redundant:
577
+
578
+ ```javascript
579
+ ((x >>> 0) % 65521) >>> 0
580
+ ```
581
+
582
+ can become:
583
+
584
+ ```javascript
585
+ (x >>> 0) % 65521
586
+ ```
587
+
588
+ `Math.imul` returns signed int32. A u32 multiplication boundary still needs
589
+ `>>> 0`.
590
+
591
+ The optimizer may sink a u32 result closure from a local assignment when all of
592
+ these are true:
593
+
594
+ - the assignment target is a tracked local binding;
595
+ - the target is already known to hold a u32/low32 value, or the target is a new
596
+ local initialized by the assignment;
597
+ - the expression being closed is a low32 producer such as a bitwise expression,
598
+ an unshadowed `Math.imul(...)`, a u32 numeric literal, or another tracked
599
+ low32 local;
600
+ - later reads that observe the numeric value, such as returns, ordinary call
601
+ arguments, property/index keys, or array/property stores, are re-closed with
602
+ `>>> 0`.
603
+
604
+ Example:
605
+
606
+ ```javascript
607
+ let x = 0x9e3779b9;
608
+ for (let i = 0; i < count; i += 1) {
609
+ x = (x ^ (x << 13)) >>> 0;
610
+ x = (x ^ (x >>> 17)) >>> 0;
611
+ x = (x ^ (x << 5)) >>> 0;
612
+ out[i] = x;
613
+ }
614
+ ```
615
+
616
+ can become:
617
+
618
+ ```javascript
619
+ let x = 0x9e3779b9;
620
+ for (let i = 0; i < count; i += 1) {
621
+ x = x ^ (x << 13);
622
+ x = x ^ (x >>> 17);
623
+ x = x ^ (x << 5);
624
+ out[i] = x >>> 0;
625
+ }
626
+ ```
627
+
628
+ The intermediate signedness is not observable because subsequent bitwise
629
+ operators and `Math.imul` consume only the low 32 bits. The store to `out[i]`
630
+ is a value observation, so the closure is restored there. This is intentionally
631
+ different from proving `closed-u32`: the local may no longer be a non-negative
632
+ u32 Number between the sunk assignment and the next observation.
633
+
634
+ This sinking is not applied to unbounded arithmetic producers such as
635
+ `(x + c) >>> 0` across loop back edges. Repeatedly removing those closures can
636
+ let the JavaScript Number grow past the exact integer range and corrupt low
637
+ bits before the eventual `>>> 0`.
638
+
639
+ ## Transfer Rules
640
+
641
+ ### Coercions
642
+
643
+ A coercion expression creates a runtime proof for its result:
644
+
645
+ ```javascript
646
+ const x = +value;
647
+ const y = value | 0;
648
+ const z = value >>> 0;
649
+ const w = Math.fround(value);
650
+ ```
651
+
652
+ After these declarations:
653
+
654
+ - `x` is proven `closed-f64`;
655
+ - `y` is proven `closed-i32`;
656
+ - `z` is proven `closed-u32`;
657
+ - `w` is proven `closed-f32`.
658
+
659
+ Checked numtypes casts participate only after they have been lowered into these
660
+ JavaScript coercion expressions.
661
+
662
+ If the argument expression is already proven closed in the same domain, the
663
+ coercion can be removed:
664
+
665
+ ```javascript
666
+ const y = +x;
667
+ ```
668
+
669
+ can become:
670
+
671
+ ```javascript
672
+ const y = x;
673
+ ```
674
+
675
+ only when `x` is proven `closed-f64`.
676
+
677
+ ### Assignments
678
+
679
+ An assignment to a tracked local binding updates that binding's proof:
680
+
681
+ ```javascript
682
+ a = +a; // a becomes closed-f64
683
+ a = a + b; // a becomes closed-f64 only if a + b is proven closed-f64
684
+ a = read(); // a becomes unknown
685
+ ```
686
+
687
+ Compound assignments and updates are treated as read-modify-write operations.
688
+ The initial implementation invalidates the target proof for compound
689
+ assignments and updates. A later implementation may preserve a proof when it can
690
+ model the exact lowered write expression and result domain.
691
+
692
+ ### Branches
693
+
694
+ At control-flow joins, facts are merged by intersection:
695
+
696
+ ```text
697
+ closed-i32 + closed-i32 -> closed-i32
698
+ closed-i32 + unknown -> unknown
699
+ closed-i32 + closed-u32 -> unknown
700
+ ```
701
+
702
+ Example:
703
+
704
+ ```javascript
705
+ if (flag) {
706
+ a = a | 0;
707
+ }
708
+
709
+ return +a;
710
+ ```
711
+
712
+ `a` is not proven closed after the branch because the false path does not close
713
+ it.
714
+
715
+ ### Loops
716
+
717
+ Loop body optimization uses the facts available at the loop header. After a
718
+ `for`, `while`, `for...in`, or `for...of` loop, surviving facts are the
719
+ intersection of the zero-iteration entry facts and the facts after one optimized
720
+ iteration. After a `do...while` loop, surviving facts are the facts after the
721
+ optimized body and condition, because the body runs at least once.
722
+
723
+ This is not a full fixed-point CFG. It is deliberately a pragmatic local loop
724
+ summary: a fact may survive only when the visible loop body preserves the same
725
+ domain. Unknown calls, getters, setters, iterator reentry, and computed element
726
+ access still invalidate captured mutable locals.
727
+
728
+ ### Calls
729
+
730
+ Function calls are arbitrary user code.
731
+
732
+ Unknown calls preserve proofs for non-captured local primitive bindings, because
733
+ arbitrary called code cannot reassign those bindings in the current activation
734
+ record. Unknown calls invalidate mutable locals captured by nested functions,
735
+ because a reentrant call may invoke an escaped closure that mutates them.
736
+
737
+ Method calls whose callee is a property or element access are treated as a
738
+ property/element read before argument evaluation. That reentry point preserves
739
+ non-captured local proofs and invalidates captured mutable local proofs unless
740
+ the callee is a recognized intrinsic such as unshadowed `Math.fround` or
741
+ `Math.imul`.
742
+
743
+ The pass must invalidate:
744
+
745
+ - property and element proofs;
746
+ - globals and imports;
747
+ - captured mutable locals when a call, getter, setter, iterator, `await`, or
748
+ `yield` might invoke or expose a closure that can mutate them;
749
+ - any proof derived from values that can be affected by user-defined coercion.
750
+
751
+ Call arguments are still evaluated before the call. If a coercion has observable
752
+ `ToNumber`, `ToInt32`, `ToUint32`, or `Math.fround` effects, it cannot be
753
+ removed without proof.
754
+
755
+ ### Closures
756
+
757
+ Each function-like body is analyzed independently.
758
+
759
+ Captured immutable values:
760
+
761
+ - a `const` local initialized from a proven closed expression can be used as a
762
+ proof inside nested functions if it cannot be reassigned;
763
+ - this is safe only for primitive local bindings, not object properties.
764
+
765
+ Captured mutable values:
766
+
767
+ - a `let` or `var` local referenced by a nested function is unstable;
768
+ - the outer analysis must invalidate it at any point where the nested function
769
+ may have run;
770
+ - the nested function analysis must treat the captured binding as unknown unless
771
+ it can prove all writes itself.
772
+
773
+ Escaped closures:
774
+
775
+ - if a nested function is returned, stored, passed, or otherwise escapes, any
776
+ mutable binding it captures must be treated as externally mutable after the
777
+ escape point.
778
+
779
+ ### Switch And Try
780
+
781
+ `switch` clauses are optimized independently from the facts available after the
782
+ discriminant expression. Clause exit facts are merged by intersection. Case
783
+ expression side effects participate in the clause-local facts, but the
784
+ implementation does not rely on fallthrough-sensitive proof propagation.
785
+
786
+ `try` and `catch` blocks are optimized from the incoming facts and joined by
787
+ intersection. A `finally` block is optimized from that join and its exit facts
788
+ become the facts after the whole `try` statement.
789
+
790
+ ### Async Functions
791
+
792
+ `await` is a suspension point. Other code may run before the function resumes.
793
+
794
+ Across `await`, proofs for non-captured local primitive bindings may survive.
795
+ Proofs for captured mutable locals, properties, elements, globals, imports, and
796
+ heap state must be invalidated.
797
+
798
+ The awaited expression itself can run arbitrary code through thenable
799
+ resolution, so coercions inside or around the awaited expression may be removed
800
+ only with local expression proof.
801
+
802
+ ### Generators
803
+
804
+ `yield` and `yield*` are suspension points.
805
+
806
+ The same invalidation rules as `await` apply. Additionally, `yield*` interacts
807
+ with arbitrary iterators and must be treated as an unknown call plus a
808
+ suspension point.
809
+
810
+ ### Exceptions and Finally Blocks
811
+
812
+ Exceptional control flow participates in joins.
813
+
814
+ If a coercion can throw and a surrounding `try`/`catch` observes that throw, the
815
+ coercion cannot be removed unless the input is already proven closed.
816
+
817
+ `finally` blocks must be modeled as writes that occur on both normal and
818
+ exceptional exits.
819
+
820
+ ## Function-Level Algorithm
821
+
822
+ The implementation should analyze one function-like body at a time.
823
+
824
+ Suggested stages:
825
+
826
+ 1. Build a symbol-based local binding table.
827
+ 2. Mark captured bindings and escaped closures.
828
+ 3. Build or approximate a control-flow graph.
829
+ 4. Seed entry facts:
830
+ - parameters start as `unknown`, even when annotated as numtypes;
831
+ - locals start as `unknown` until their initializer is analyzed;
832
+ - compiler-created temporaries may start with the proof of their initializer.
833
+ 5. Run forward data-flow analysis to a fixed point.
834
+ 6. Record expression proofs for coercion expressions and ordinary operations.
835
+ 7. Rewrite coercions whose input expression proof makes them redundant.
836
+
837
+ The pass may be implemented with a conservative AST walk before a full CFG is
838
+ available, but every unsupported control-flow construct must fall back to
839
+ keeping the coercion.
840
+
841
+ ## Examples
842
+
843
+ ### f64 Parameter Normalization
844
+
845
+ Input:
846
+
847
+ ```typescript
848
+ function dot(a: f64, b: f64, c: f64): f64 {
849
+ a = f64(a);
850
+ b = f64(b);
851
+ c = f64(c);
852
+
853
+ return f64(a * b + c);
854
+ }
855
+ ```
856
+
857
+ Correctness lowering:
858
+
859
+ ```javascript
860
+ function dot(a, b, c) {
861
+ a = +a;
862
+ b = +b;
863
+ c = +c;
864
+
865
+ return +(+((+a * +b)) + +c);
866
+ }
867
+ ```
868
+
869
+ Optimized lowering:
870
+
871
+ ```javascript
872
+ function dot(a, b, c) {
873
+ a = +a;
874
+ b = +b;
875
+ c = +c;
876
+
877
+ return a * b + c;
878
+ }
879
+ ```
880
+
881
+ ### i32 Operand Elision Only
882
+
883
+ Input:
884
+
885
+ ```typescript
886
+ function add(a: i32, b: i32): i32 {
887
+ a = i32(a);
888
+ b = i32(b);
889
+
890
+ return i32(a + b);
891
+ }
892
+ ```
893
+
894
+ Optimized lowering:
895
+
896
+ ```javascript
897
+ function add(a, b) {
898
+ a = a | 0;
899
+ b = b | 0;
900
+
901
+ return (a + b) | 0;
902
+ }
903
+ ```
904
+
905
+ The final `| 0` remains because addition can overflow.
906
+
907
+ ### f32 Operand Elision Only
908
+
909
+ Input:
910
+
911
+ ```typescript
912
+ function add(a: f32, b: f32): f32 {
913
+ a = f32(a);
914
+ b = f32(b);
915
+
916
+ return f32(a + b);
917
+ }
918
+ ```
919
+
920
+ Optimized lowering:
921
+
922
+ ```javascript
923
+ function add(a, b) {
924
+ a = Math.fround(a);
925
+ b = Math.fround(b);
926
+
927
+ return Math.fround(a + b);
928
+ }
929
+ ```
930
+
931
+ The final `Math.fround` remains because JavaScript computes `a + b` as Number.
932
+
933
+ ### Branch Join
934
+
935
+ Input:
936
+
937
+ ```typescript
938
+ function maybe(flag: boolean, a: f64, b: f64): f64 {
939
+ if (flag) {
940
+ a = f64(a);
941
+ }
942
+
943
+ b = f64(b);
944
+
945
+ return f64(a + b);
946
+ }
947
+ ```
948
+
949
+ `b` is proven `closed-f64`, but `a` is not. The result cannot be simplified to
950
+ `a + b`.
951
+
952
+ ### Captured Mutable Binding
953
+
954
+ Input:
955
+
956
+ ```typescript
957
+ function outer(a: f64): () => f64 {
958
+ a = f64(a);
959
+
960
+ function mutate(): void {
961
+ a = readUnknown() as f64;
962
+ }
963
+
964
+ escape(mutate);
965
+
966
+ return function inner(): f64 {
967
+ return f64(a + f64(1));
968
+ };
969
+ }
970
+ ```
971
+
972
+ After `mutate` escapes, `a` is no longer stable. The nested `inner` analysis
973
+ must treat `a` as unknown and keep the required coercions.
974
+
975
+ ### Await Suspension
976
+
977
+ Input:
978
+
979
+ ```typescript
980
+ async function run(a: f64): Promise<f64> {
981
+ a = f64(a);
982
+
983
+ await task();
984
+
985
+ return f64(a + f64(1));
986
+ }
987
+ ```
988
+
989
+ If `a` is a non-captured local primitive binding, the proof for `a` may survive
990
+ the `await`. If `a` is captured by an escaped closure, it must be invalidated at
991
+ the `await`.
992
+
993
+ ## Safety Checklist
994
+
995
+ Before removing a coercion, the optimizer must prove all of the following:
996
+
997
+ - the input expression is already closed in the same domain;
998
+ - removing the coercion does not remove observable conversion or throw behavior;
999
+ - all control-flow paths reaching the coercion carry the proof;
1000
+ - no alias, closure, call, `await`, `yield`, or exceptional edge invalidates the
1001
+ proof;
1002
+ - the replacement preserves the required target domain for the enclosing
1003
+ operation.
1004
+
1005
+ If any item is uncertain, keep the coercion.
1006
+
1007
+ ## Future Work
1008
+
1009
+ Potential later optimizations:
1010
+
1011
+ - range analysis for arithmetic result closure elision;
1012
+ - safe prologue normalization hoisting for repeated parameter coercions;
1013
+ - local temporary introduction to avoid repeated operand coercions;
1014
+ - property and element proof for freshly allocated objects that do not escape;
1015
+ - broader typed-array, DataView-aware, and alias-aware proof rules;
1016
+ - interprocedural summaries for private helper functions;
1017
+ - full fixed-point CFG instead of the current local branch and loop summaries;
1018
+ - closure escape precision beyond the current captured-mutable invalidation;
1019
+ - trace-driven verification that optimized output still produces expected V8
1020
+ lowering tokens.