Skip to content

wasm: define fast floating-point integer conversions - #2268

Open
leventov wants to merge 2 commits into
meta:static_hfrom
convex-in-prod:upstream-defined-wasm-conversions
Open

leventov wants to merge 2 commits into
meta:static_hfrom
convex-in-prod:upstream-defined-wasm-conversions

Conversation

@leventov

@leventov leventov commented Oct 23, 2026 •

Copy link
Copy Markdown

Summary

The Wasm fast conversion helpers currently use C casts and then round-trip the integer back to a double to check the range. The casts are undefined for out-of-range inputs, so LLVM can optimize away that check before Wasm instruction selection. This can miscompile dynamic unsigned shifts and other callers relying on range rejection.

Use the defined Wasm saturating conversion intrinsics when __wasm_nontrapping_fptoint__ is available. The 64-bit intrinsic helpers retain the existing mitigation used on other architectures: a saturated integer can otherwise round back to the overflowing double.

For Wasm targets without that feature, guard every C cast with ordered range comparisons. These reject NaN, infinities, and overflow while preserving the 32-bit helpers' truncation contract for fractional inputs near the integer endpoints. Exclusive 64-bit upper bounds avoid rounding INT64_MAX and UINT64_MAX up into accepted overflowing doubles, so the guarded path does not need saturated-result masking. Comments explain these constraints. Native-target implementations are unchanged.

Test Plan

  • Compiled a direct harness against the final header with Clang 24, --target=wasm32 -ffreestanding, in C11 and C++14, at both -O0 and -O3, with explicit -mnontrapping-fptoint and -mno-nontrapping-fptoint. Executed all eight configurations in Node's WebAssembly runtime. Each passed 400,424 conversion checks over 100,106 inputs: integer boundaries and adjacent doubles, fractional endpoints, infinities, NaN, subnormals, and 100,000 deterministic generated IEEE-754 values. Checks cover acceptance, returned values, valid 32-bit truncation, and absence of 64-bit false positives.
  • Inspected generated assembly for all eight configurations: saturating conversion instructions are present only with the feature enabled.
  • Compiled and ran unittests/Support/test_sh_fp_trunc.cpp with the repository's GoogleTest sources at -O3 on x86-64. All seven tests passed, including the new dynamic non-integer, overflow, and fractional endpoint regressions.
  • Before the feature guard, compiling all four helpers with -mno-nontrapping-fptoint failed because the builtins require nontrapping-fptoint.
  • The original upstream header fails the optimized Wasm round-trip regression: it accepts -2147483649 as representable by int32_t.
  • Added dynamic conversion cases in ConversionsTest.cpp and the optimized/unoptimized Static Hermes unsigned-shift regression remain in the PR. The full HermesSupportTests and Static Hermes engine test targets have not been run on this extracted branch.

Minimal reproducer for the undefined round-trip optimization:

#include "hermes/Support/sh_tryfast_fp_cvt.h"
int accepts(double x) {
  int32_t value;
  return sh_tryfast_f64_to_i32(x, value);
}

After optimized Wasm compilation, accepts(-2147483649.0) must return zero, with or without nontrapping-fptoint.

C floating-point casts have undefined behavior outside the destination
integer range, even if lowering eventually uses a saturating instruction.
LLVM can remove the round-trip check used by the fast conversion helpers
and accept an out-of-range input.

Use Wasm saturating conversion intrinsics instead. Retain the established
64-bit restriction that prevents saturated integers rounding back to an
overflowing double. Add dynamic integer-conversion and unsigned-shift
regressions.
@meta-cla meta-cla Bot added the CLA Signed Do not delete this pull request or issue due to inactivity. label Oct 23, 2026
Comment thread include/hermes/Support/sh_tryfast_fp_cvt.h
The saturating conversion builtins require __wasm_nontrapping_fptoint__.
Using them unconditionally prevents compilation for Wasm targets with
that feature disabled.

Gate the builtins on the target feature and range-check C casts on other
Wasm targets. Ordered comparisons reject NaN and infinities, while the
32-bit bounds preserve truncation for fractional inputs whose integer
parts fit. Exclusive 64-bit upper bounds avoid rounding INT64_MAX and
UINT64_MAX into accepted overflowing doubles. The guarded path therefore
does not need the saturated-result mitigation.

Add dynamic helper regressions for non-integers, overflow, and fractional
32-bit endpoint inputs. Verify the actual header with Clang 24 in C and
C++ at O0 and O3, with the Wasm feature enabled and disabled: all eight
configurations pass 400,424 conversion checks each. Feature-disabled
assembly contains no saturating conversions. All seven focused native
conversion unit tests pass.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed Do not delete this pull request or issue due to inactivity.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants