scalar CRT math where it wins; float2string via Luau's emitter - byte-identical, 2.4x - #3908
Merged
Conversation
…wins the benchmarks race The vecmath lane trick (extract, set_x, polynomial, extract) is 2-3x the CRT on the scalar path: the exp loop's 9.45 ns/iter against expf's 3.26 under the AOT unit's own flags on the 3990X, with the same shape on the M1. Winners by two-box probe: exp, exp2, pow (exact at tie speed) and log2 (v_log2_est_p5 was an estimate where the JIT is exact) go CRT; log, sin, cos, tan stay on the lane where it wins. benchmarks/core/math/scalar_crt.das races every pair through the UnitTest binds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J97ymQXmMxGTmHYUhLrgVd
…on the hot path, byte-identical
The presentation half of Luau's lnumprint.cpp (MIT, LUAU.LICENSE vendored and installed) drives the
dragonbox digits fmt already instantiates; fmt's crossover and spelling are kept, so every finite
float32 - all 2^32 of them, swept in tests-cpp - and sampled doubles print byte-for-byte what
fmt::format_to("{}") produced. Both sinks swap: string(f) in runtime_string.cpp and "{f}" in
string_writer.cpp; the {:.9} FIXEDFP path stays on fmt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J97ymQXmMxGTmHYUhLrgVd
fmt honours the sign bit and x86's default quiet NaN is negative, so "{0f/0f}" prints -nan; the
guard compared against "nan" alone and emitted -nanf into MSL instead of raising the error.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J97ymQXmMxGTmHYUhLrgVd
… the math Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J97ymQXmMxGTmHYUhLrgVd
…nd as rules, the bit patterns get names The test's narrowing ban lands in tests-cpp/small/REVIEW.md; the fixed-length-copy invariants in src/misc/REVIEW.md; the fixture bit patterns become named constants; the UnitTest twins keep a one-line contract doc; the header's buffer clause reads one way now. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J97ymQXmMxGTmHYUhLrgVd
…r constant, the -nan fixture, the twins and wiring pins, six rule documents hardened The emitter classifies specials from the bits (a -ffinite-math-only build folds isfinite/isnan/signbit); DAS_F2S_BUFFER_SIZE sizes every caller buffer by compilation; the msl guard asks is_finite(v) and its rejection gets the _fc_nan_literal fixture and needle; tests/math/test_crt_twins.das pins the switched scalars to the CRT exactly and validates all eight twin binds; sprint-format pins the lexical-cast wiring; the byte-identity test splits by width, prints its bits in hex, names its strides, and gains the two-digit-scientific boundaries; ci, src/misc, tests-cpp, tests-cpp/small and dasMetal checklists re-worded under two dragon passes; the simulate ledger records the log2 trade. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J97ymQXmMxGTmHYUhLrgVd
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J97ymQXmMxGTmHYUhLrgVd
…s live in the ledger Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J97ymQXmMxGTmHYUhLrgVd
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J97ymQXmMxGTmHYUhLrgVd
Contributor
There was a problem hiding this comment.
Pull request overview
Aligns scalar float transcendental math across tiers by switching selected scalar ops to CRT implementations, and replaces fmt’s default float-to-string path with a faster emitter that preserves byte-identical spelling; also hardens the Metal MSL emitter to reject non-finite float literals.
Changes:
- Switch scalar float
Exp/Exp2/Log2/PowinSimPolicyto CRT (expf/exp2f/log2f/powf) while keeping vec4 paths on vecmath. - Introduce
float2string/double2string(Luau-derived presentation + fmt dragonbox digits) and route default float/double formatting through it in runtime sinks. - Add/extend tests and “fail-closed” fixtures, plus license + bundle-gate updates for the vendored Luau emitter code.
Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| tests/msl/test_msl_fail_closed.das | Adds a fail-closed assertion for non-finite float literals in MSL emission. |
| tests/msl/_fail_closed/_fc_nan_literal.das | New fixture that triggers a non-finite float constant for MSL rejection. |
| tests/math/test_crt_twins.das | New tests pin scalar math builtins to CRT “twin” functions. |
| tests/debug/test_sprint_format.das | Adds a small pin for default float/double string spelling via formatting + string(...). |
| tests-cpp/small/test_float2string.cpp | New C++ tests enforcing byte-identical output vs fmt for float/double conversion (plus exhaustive optional sweep). |
| tests-cpp/small/REVIEW.md | Documents that weakening the byte-for-byte float2string vs fmt comparison is a defect. |
| tests-cpp/REVIEW.md | Broadens checklist rule for any conditionally-skipping C++ tests (env vars, missing artifacts, etc.). |
| src/simulate/runtime_string.cpp | Routes float/double lexical casts through float2string/double2string. |
| src/misc/string_writer.cpp | Routes default (non-fixed) float/double StringWriter output through float2string/double2string. |
| src/misc/REVIEW.md | Adds guardrails for fixed-size copies and special-value classification in luau_float2string.cpp. |
| src/misc/LUAU.LICENSE | Adds Luau MIT license file for vendored emitter code. |
| src/misc/luau_float2string.cpp | New float/double shortest-print implementation (Luau-derived emitter + fmt dragonbox digits). |
| plans/benchmark_followups.md | Records follow-up plan for M1 AOT exp-loop residual and notes scalar/vector divergence. |
| modules/dasUnitTest/unitTest.h | Adds inline CRT wrapper functions (crt_*f) for benchmarking/testing. |
| modules/dasUnitTest/test_handles.cpp | Registers CRT math bindings in the UnitTest module initialization. |
| modules/dasUnitTest/test_crt_math.cpp | Implements Module_UnitTest::addCrtMath bindings for CRT wrappers. |
| modules/dasUnitTest/module_unitTest.h | Declares addCrtMath on Module_UnitTest. |
| modules/dasUnitTest/CMakeLists.txt | Adds the new CRT binding source file to the module build. |
| modules/dasMetal/REVIEW.md | Tightens/clarifies checklist wording for newly rejected constructs requiring fail-closed fixtures. |
| modules/dasMetal/metal/msl_emit.das | Changes float literal emission to reject non-finite values via is_finite. |
| include/daScript/simulate/sim_policy.h | Switches scalar Exp/Exp2/Log2/Pow to CRT functions. |
| include/daScript/simulate/ARCHITECTURE.md | Documents/sanctions the hot-path scalar CRT switch and its motivation. |
| include/daScript/misc/float2string.h | Adds public header for the new float-to-string API and buffer sizing contract. |
| CMakeLists.txt | Wires new header/source into the build and installs the Luau license into bundles. |
| ci/smoke_test_bundle.sh | Extends third-party license presence gate to require LUAU.LICENSE. |
| ci/REVIEW.md | Updates checklist wording for bundle gate “must not shrink rejects” rule. |
| benchmarks/README.md | Documents the new scalar CRT benchmark. |
| benchmarks/core/math/scalar_crt.das | New benchmark comparing builtins vs CRT twins across scalar math functions. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
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.
Behavior change: scalar float
exp/exp2/log2/powresults shift a few ulps in interp and AOT (they now call the CRT, matching the JIT bit-for-bit;log2was a 5th-order estimate and is now exact); float-to-string keeps its spelling byte-for-byte but runs ~2.4x faster; a-nanfloat literal in a Metal kernel is now rejected instead of emitting invalid MSL.Two wins the dasProfile records asked for, one borrowed emitter, and the review round's hardening.
What changed, numbers, validation
Scalar math: CRT where it wins
SimPolicy<float>::Exp/Exp2/Log2/Powscalar arms callexpf/exp2f/log2f/powf; thevec4farms stay on vecmath, where four lanes pay for the setup. Winner table from a two-box probe of the exact AOT shape (ns/iter, 1e6 inputs, best of 7; lane = the oldv_extract_x(...(v_set_x(x)))composition):v_log2_est_p5was an estimate where the JIT is exact; the ~0.4 ns x64 cost is ledgered ininclude/daScript/simulate/ARCHITECTURE.md(sanctioned hot-path additions)modules/dasUnitTestbinds the 8 CRT twins (crt_expf...);benchmarks/core/math/scalar_crt.dasraces every pair (interp + JIT),benchmarks/core/math/exp.dascarries the exp rows;tests/math/test_crt_twins.daspins the switched builtins to the CRT exactly and the twins against the double CRT. The two benchmark arms ofscalar_crt.dasshare their loop shape deliberately (theexp.dasrow pattern).Full re-profile (dasProfile, 5 samples/lane, cold boxes) - das rows that moved: zen2 exp loop AOT 9215 -> 3412 us (level with JIT 3383, C++ 3240); M1 exp loop interp 9279 -> 7347 us. The M1's AOT exp loop moved only 3592 -> 3414 with exp verifiably CRT - the residual is the AOT loop shape (
das_iterator+ the keptrcp_estlane), ledgered inplans/benchmark_followups.md.float2string: Luau's emitter over fmt's dragonbox
The presentation half of Luau's
lnumprint.cpp(MIT;src/misc/LUAU.LICENSEvendored, installed asLUAU.LICENSE, smoke-list enforced) drives the dragonbox digits fmt already instantiates; fmt's crossover and spelling are kept. Both sinks swap:string(f)(das_lexical_cast_fp_f/_d) and"{f}"(StringWriter); the{:.9}FIXEDFP path stays on fmt. One change covers all three tiers (the JIT calls the runtime).tests-cpp/small/test_float2string.cpp- sampled sweep per PR (~460k floats + 2M doubles + specials + 2-digit-scientific boundaries), and an exhaustive arm over every finite float32 (all 2^32 bit patterns) gated byDASLANG_F2S_EXHAUSTIVE=1. No CI lane sets the env; run manually on both boxes:DASLANG_F2S_EXHAUSTIVE=1 bin/Release/tests-cpp-small.exe --test-case="*every finite float32*"- 3990X 128 workers all clean, M1 10 workers all clean.isfinite/isnan/signbit- a-ffinite-math-onlybuild (the Dagor/EASTL config) folds those;src/misc/REVIEW.mdnow bans them there.DAS_F2S_BUFFER_SIZE(48) infloat2string.hsizes every caller's buffer, so the overshoot invariant propagates by compilation.--codegen(zen2 2530, M1 2047).Metal
msl_float_literalrejects non-finite values viais_finite(v)instead of matching printed spellings (the swap surfaced that-nan- x86's default quiet NaN under fmt's sign handling - slipped through as-nanf).tests/msl/_fail_closed/_fc_nan_literal.das+ its needle pin the rejection; mutation-verified (removing the guard went green before, red now).Validation
make-prchain green: sync (20 files), REVIEW.das gates, review-md walk, dupes (only the benchmark's own paired arms), jit-smoke;preflight --full18 passed / 0 failed / 4 environmental skips (no clang-cl on the box; DLL-flavor relink gates - CI covers them).Claims - stated, not tested
log2/poware now exact while thefloat4arms keep the vecmath estimates -floatandfloat4diverge for those two; deliberate, ledgered, the vec arms are the follow-up if it bites.exp_stdstays bound although it now equalsexp- it is the extern-vs-intrinsic A/B row under JIT.DASLANG_F2S_EXHAUSTIVEhas no row inskills/internal/environment_variables.md- that index covers variables daslang itself reads; test-binary knobs (DASLANG_HUGE_HEAP_TESTSprecedent) stay out.Not done
plans/benchmark_followups.md.v_log2_est_p5,v_pow) stay; ledgered.exp_estas a JIT intrinsic (sub-ns, vectorized) - ledgered, no consumer today.🤖 Generated with Claude Code
https://claude.ai/code/session_01J97ymQXmMxGTmHYUhLrgVd