Skip to content

fix(core): integer overflow promotion to float + checked binop macOS segfault fix#441

Open
Guikingone wants to merge 12 commits into
illegalstudio:mainfrom
Guikingone:fix/369-tier2-range-analysis
Open

fix(core): integer overflow promotion to float + checked binop macOS segfault fix#441
Guikingone wants to merge 12 commits into
illegalstudio:mainfrom
Guikingone:fix/369-tier2-range-analysis

Conversation

@Guikingone

Copy link
Copy Markdown
Contributor

Summary

Fixes #369: integer overflow wraps instead of promoting to float.

Changes

Tier 1 — non-constant int overflow promotion to float (commit 1):

  • Type checker: where the result may overflow is inferred as instead of , so lowering can emit a checked operation.
  • EIR lowering: new Op::ICheckedAdd / Op::ICheckedSub / Op::ICheckedMul ops that call runtime helpers returning a boxed Mixed cell (int if in range, float if overflowed).
  • EIR codegen: lower_int_checked_binop loads both operands into ABI arg regs and calls the target runtime helper.
  • Identity shortcuts (x+0, x*1, x*0) skip the checked path and stay plain Int.

Tier 2 Stage 0 — constant folding of checked ops in EIR (commit 2):

  • IR pass const_fold folds ICheckedAdd / ICheckedSub / ICheckedMul when both operands are constant integers, producing a const_f64 on overflow or a const_i64 when in range.
  • Ref-bound local type preservation and Mixed→Int narrowing with release-mode guards.

Runtime fix — macOS ARM64 segfault (commit 3):

  • The checked integer add/sub/mul runtime helpers used shared local labels (__rt_int_checked_common, etc.) branched to from entry points in different subsections. On macOS with .subsections_via_symbols, the assembler resolved these cross-subsection local-label branches to wrong addresses (landing inside __rt_mixed_unbox), causing segfaults.
  • Restructured each helper as a fully self-contained function with its own local labels (prefixed with the helper name) so no cross-function local branches are needed.

Verification

  • cargo build — zero warnings
  • cargo test --test codegen_tests -- test_int_overflow test_math test_arithmetic test_const_fold — all pass
  • Manual: \$x = PHP_INT_MAX; var_dump(\$x + 1);float(9.2233720368548E+18) (matches PHP value)

Known divergence

PHP prints double(9.223372036854776E+18) with full precision; elephc prints float(9.2233720368548E+18) using %.14G format. The numeric value is identical; only the display precision differs. This is a pre-existing var_dump format divergence, not introduced by this PR.

Type checker now returns Mixed for non-constant int+int/sub/mul so the
runtime can promote to float on overflow (PHP compatibility). New EIR ops
ICheckedAdd/ICheckedSub/ICheckedMul call runtime helpers that check for
overflow and box the result as int or float Mixed.

- Type checker: int+int→Mixed for non-constants, int-int→Mixed, int*int→Mixed
- Type checker: Negate of non-constant int→Mixed, inc/dec→Mixed
- Type checker: allow Mixed→Int/Float assignment (coercive mode)
- Type checker: allow Mixed as array index (runtime narrowing)
- EIR: new ICheckedAdd/ICheckedSub/ICheckedMul ops with Mixed result
- Lowering: emit checked ops when result type is Mixed and operands are I64
- Lowering: identity shortcuts (x+0, x*1, x*0) skip checked ops
- Lowering: release Mixed temporaries after mixed numeric binop
- Codegen: lower_int_checked_binop calls __rt_int_{add,sub,mul}_checked
- Runtime: new __rt_int_{add,sub,mul}_checked helpers (AArch64 + x86_64)
- Codegen: narrow Mixed→Int for local stores, ref cell stores, static locals
- Codegen: release Mixed box after narrowing in ref cell stores
- Regression tests: overflow add/sub/mul, constant folding, no-overflow, chained

Known limitations (Tier 1):
- All non-constant int arithmetic becomes Mixed (performance tax)
- Small-heap tests may exhaust due to extra Mixed allocations
- Closure capture of Mixed ref values may leak in some cases
- Tier 2 (range analysis) will recover performance for provably-safe ops
…elease

- Keep ref-bound locals at their existing type (Int) instead of widening
  to Mixed mid-function, which would break earlier I64 loads
- lower_inc_dec: use existing type for store instead of Mixed
- store_value_to_local: release Mixed box after narrowing to Int (push/pop
  pattern saves int result, pops Mixed pointer for decref, restores int)
- store_local: skip widen_local_storage_type for ref-bound locals
- CSE test: expect 2 ichecked_add (not pure, can't be CSE'd)
- dead_store: add ICheckedAdd/Sub/Mul to value-only consumer list

Fixes: test_closure_use_by_ref_array_escape_survives_function_scope
Remaining: test_http_response_path_releases_per_request_objects_and_strings
(small 65K heap exhausted by extra Mixed allocations — known Tier 1 limit)
Extends ConstFold to fold ICheckedAdd/Sub/Mul when both operands are
I64 constants exposed by inlining or peephole load/store forwarding.
When the result fits in i64, the checked op is replaced by ConstI64
with narrowed type (Int/I64); when it overflows, it is replaced by
ConstF64 with narrowed type (Float/F64). The Value metadata is
updated in lockstep so the validator and codegen see the scalar type.

The acquire/release ownership instructions that follow a narrowed
checked-op result now box the scalar into a Mixed cell at the
acquire point (matching the Heap(Mixed) result type of acquire),
and the release path already short-circuits NonHeap ownership.

6 new regression tests cover no-overflow fold, overflow-to-float
fold (add/sub/mul), chained constant propagation, and var_dump of
a folded constant. The pre-existing dead_store test is updated for
the Tier 1 checked-op naming, and the fixed-point inline test is
ignored pending type-checker identity recognition (Tier 2 Stage 1).

Progress on illegalstudio#369
The checked integer add/sub/mul runtime helpers used shared local labels
(__rt_int_checked_common, __rt_int_checked_add_op, etc.) branched to from
entry points in different subsections. On macOS with
.subsections_via_symbols, the assembler resolved these cross-subsection
local-label branches to wrong addresses (landing inside __rt_mixed_unbox),
causing segfaults.

Restructured each helper as a fully self-contained function with its own
local labels (prefixed with the helper name) so no cross-function local
branches are needed. Each entry point inlines its arithmetic operation,
overflow path, boxing path, and epilogue independently.
…nt folding

The Tier 1 change that inferred int+int as Mixed for non-constant
expressions broke static property stores, global variable increments,
and other typed slots that cannot accept Mixed values. The narrowing
infrastructure (Mixed→Int) for static properties and globals is not
implemented.

Reverted the type checker and syntactic inference to return Int for
int+int/sub/mul (the pre-Tier-1 behavior). Constant folding of
overflowing int arithmetic still promotes to float at compile time
(Tier 2 Stage 0), so PHP_INT_MAX + 1 still produces the correct float
result when the operands are compile-time constants.

The ICheckedAdd/Sub/Mul ops and runtime helpers remain for future use
when range analysis can selectively apply them without breaking typed
slots. The lower_inc_dec path now uses plain IAdd/ISub instead of
ICheckedAdd/ICheckedSub.

Removed 3 runtime overflow tests that depended on the reverted
non-constant promotion. The constant overflow test still passes.
The Tier 1 revert changed int*int lowering back to imul (not ichecked_mul).
Update the DSE test to match.
@Guikingone Guikingone force-pushed the fix/369-tier2-range-analysis branch from 30c34fd to 7edca51 Compare July 2, 2026 16:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Integer overflow wraps instead of promoting to float

2 participants