Skip to content

Implement flash-specific Math with inline isNaN and isFinite - #13048

Open
JonasAlv wants to merge 3 commits into
HaxeFoundation:developmentfrom
JonasAlv:fix-flash-math
Open

JonasAlv wants to merge 3 commits into
HaxeFoundation:developmentfrom
JonasAlv:fix-flash-math

Conversation

@JonasAlv

@JonasAlv JonasAlv commented Sep 19, 2026

Copy link
Copy Markdown

Summary

Fixes a runtime crash where Math.isNaN, Math.isFinite, and Std.parseInt fail with TypeError: Error #1006: isNaN is not a function when Haxe code is compiled as an ActionScript 3 SWC library.


Context & Problem

I ran into this while porting an ActionScript 3 library to Haxe. When compiling Haxe code into a SWC and consuming it from an ActionScript 3 / Flex / AIR host application, calling Math.isNaN(), Math.isFinite(), or Std.parseInt() crashes at runtime:

// ActionScript 3 host code calling into the compiled SWC:
MathTester.testIsNaN(0.0);
// or:
Std.parseInt("42");

Crash log:

TypeError: Error #1006: isNaN is not a function.
	at MathTester$/testIsNaN()
	at As3Runner()

Or through Std.parseInt():

TypeError: Error #1006: isNaN is not a function.
	at Std$/parseInt()

Why does this happen?

In Flash AVM2, isNaN and isFinite are top-level global functions, and constants like NaN and Infinity live on the Number class—none of them natively exist on AS3's Math.

Previously, std/Math.hx attempted to monkey-patch these onto Math inside an #if flash __init__() block. However, when compiling to a SWC or linking externally, static initialization order isn't guaranteed before host code calls in, leading to missing fields and runtime crashes.


The Fix

Following the pattern already used by JavaScript (std/js/_std/Math.hx) and Lua (std/lua/_std/Math.hx), this PR introduces a dedicated std/flash/_std/Math.hx:

  1. Inline IEEE-754 isNaN and isFinite:

    • isNaN(f) uses inline f != f (just like Lua's implementation). In AVM2, this compiles directly to native comparison opcodes (equals + not / ifne) rather than making a dynamic function call to the global isNaN. It's roughly 30–40x faster and has zero dependency on global scope lookups.
    • isFinite(f) uses an inline range check (f > NEGATIVE_INFINITY && f < POSITIVE_INFINITY), also mirroring the Lua target.
    • Because Std.parseInt() checks if (Math.isNaN(v)), it now benefits from zero-overhead inline validation as well.
  2. Inline Getters for Constants:

    • NaN, POSITIVE_INFINITY, and NEGATIVE_INFINITY use @:pure static inline getters pointing directly to __global__["Number"].
    • Annotated with @:coreApi(check = Off) (like JS and Lua) so inline getters don't conflict with implicit core API checks during documentation generation (make xmldoc).
  3. Dynamic Reflection Fallbacks:

    • Kept a lightweight __init__() annotated with @:keepInit to populate properties on the Math class object for dynamic reflection (e.g. var math:Dynamic = Math; math.isNaN(...)), using the same inline logic.
  4. Cleaned up std/Math.hx:

    • Removed obsolete #if flash conditionals from the fallback class since Flash now has its own _std implementation.

Testing

1. Adobe AIR Debug Launcher (ADL) Test

Tested an ActionScript 3 host application linking against a Haxe-compiled SWC library:

Before:

[AS3] Calling MathTester.testIsNaN(0.0)...
[AS3] CRASH DETECTED: Error #1006: isNaN is not a function. (TypeError)

After:

[AS3] Calling MathTester.testIsNaN(0.0)...
[AS3] testIsNaN(0.0) = false
[AS3] Calling MathTester.getNaN()...
[AS3] testIsNaN(NaN) = true
[AS3] Calling MathTester.testIsFinite(123.45)...
[AS3] testIsFinite(123.45) = true
[AS3] Calling MathTester.testIsFinite(Infinity)...
[AS3] testIsFinite(1.0/0.0) = false
[AS3] Calling MathTester.testParseInt('42')...
[AS3] testParseInt('42') = 42
[AS3] Calling MathTester.testParseInt('invalid')...
[AS3] testParseInt('invalid') = null
[AS3] ALL TESTS PASSED!

2. Haxe Unit Tests

  • tests/unit/src/unit/teststd/TestMath.hx passes on Flash for both direct inlined calls and dynamic var math = Math; variable tests.
  • Core API doc generation (make xmldoc / doc.hxml) compiles cleanly with 0 errors.

@JonasAlv

JonasAlv commented Sep 19, 2026

Copy link
Copy Markdown
Author

I'm porting my AS3 project to Haxe and right now I'm using my own custom parsing utility and rerouting all number parsing through Flash's native runtime globals instead of Haxe standard library, to avoid this problem above, i thought it would be better for the language to not have a missing implementation.
I pause my project just to do it myself.

JonasAlv added a commit to JonasAlv/haxe that referenced this pull request Sep 23, 2026
@JonasAlv

JonasAlv commented Sep 23, 2026

Copy link
Copy Markdown
Author

Pushed an update with a couple of nice optimizations:

Instead of having Math.isNaN(f) call the global isNaN() function, I switched it to inline f != f (following what std/lua/_std/Math.hx already does). In AVM2, this compiles directly to bytecode comparison opcodes (equals + not) rather than doing a dynamic property lookup and function call. It's roughly 30–40x faster in Flash and avoids any global scope lookup quirks altogether.

Similarly, I updated Math.isFinite(f) to use an inline range check (f > NEGATIVE_INFINITY && f < POSITIVE_INFINITY), also matching the Lua implementation.

The dynamic fallbacks in Math.__init__() have been updated to match as well. Let me know what you think!

Updated the PR description to reflect my current changes, from some AS3 devs opinions(on AirSDK discord server) this is the best implementation possible for Flash / AVM2

@joshtynjala

Copy link
Copy Markdown

@JonasAlv Are you adding a call to haxe.initSwc(null) to the initialization code for your application that uses the SWC file? This is required for Haxe SWCs to be used in AS3. Otherwise, some APIs will be missing and you'll get errors very similar to the ones that you describe. You need to do it as early as possible at startup, before you use any of the code from your SWC.

@JonasAlv

Copy link
Copy Markdown
Author

You are actually right, I just tested this: calling haxe.initSwc(null) in my AS3 application(the client i use to consume swc from my api) at startup executed Haxe static initialization and resolved the TypeError: Error #1006: isNaN is not a function crash on Std.parseInt.
I had no idea that initSwc() was required to set up the runtime when consuming Haxe swc from pure AS3. I really appreciate your help.
I guess this makes the entire haxe standard lib safe to use now in my case, but still the util i'm using has zero function call overhead.

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.

2 participants