Compile ActionScript 3 source code ahead-of-time (AOT) into native machine code, producing native executables — while keeping the AS3 syntax you already know.
Design philosophy: the compiler frontend only handles
lexing → parsing → generating readable C, delegating optimization and machine-code generation entirely to mature C compilers — no reinventing the wheel.
as3compiler-aot-promo.mp4
ActionScript source (.as)
│ lexer.ts lexical analysis (tokens)
▼
│ parser.ts recursive-descent parsing (AST)
▼
│ codegen.ts semantics + C code generation
▼
C source (.c)
│ build orchestration src/build.ts
│ (build manifest JSON + -I/-L/-l/-D + --target)
▼
┌──────┴───────┐
│ native │ cc/clang -O2 -lm → native executable (Mach-O / ELF / PE)
│ wasm │ clang --target=wasm32-wasip1 → WASI .wasm
└──────────────┘
"Generating a single readable .c" is the default form (simple to build, easy to read), not a law against linking third-party libraries:
when implementing heavy lifting such as graphics (skia/cairo/SDL), the correct approach is to link mature libraries (-lskia) rather than hand-rolling rasterization.
See "Build & Linking" and AGENTS.md §2.9.
Install (gives the as-aot command): npm install -g as3compiler (or locally npm link).
If not installed, use node src/index.ts ... or npm run compile -- ... equivalently.
Requirements: Node ≥ 22.6 (native support for running .ts directly, no compilation step), and a system cc/clang;
--target wasm needs the WASI SDK (WASI_SDK_HOME pointing at the SDK root, or an LLVM clang with a wasm32 backend),
and running the output requires a runtime supporting the exception-handling proposal (wasmtime/wasmer, see docs/en/compile.md §2);
--target wasm --package web needs the Emscripten SDK (EMSDK_HOME pointing at the emsdk root, see
docs/en/html5-web.md).
# Compile (default output: a file named after the input without extension, keeping the generated .c for reading)
as-aot examples/hello.as
# Compile and run
as-aot examples/fib.as --run
# Specify output name / specify the C compiler
as-aot examples/class.as -o build/class --cc clang --run
# Generate a macOS .app Xcode project (--package xcode-project: open/build/debug in Xcode, product is an .app)
as-aot src/Main.as --target native --package xcode-project -o build/Main
# Generate a browser HTML5-rendered page (--target wasm --package web, requires the Emscripten SDK)
as-aot examples/web/hello-web.as --manifest examples/web/hello-web.build.jsonFull compilation instructions (CLI arguments, build manifest JSON, multi-target backends, WASI toolchain) are in
compile.md.
Supports multiple targets and linking third-party libraries:
# WASI target (requires the WASI SDK or a clang with a wasm32 backend)
as-aot examples/hello.as --target wasm
# Link third-party libraries (skia/cairo/SDL etc.): include paths / library paths / libraries / macro definitions
as-aot app.as -I vendor/include -L vendor/lib -l skia -D USE_SKIA=1
# Embed bitmap asset classes from an SWC (stage ninety-five): named assets become BitmapData subclasses,
# source can directly `new logo(0, 0)`, or reflect via getDefinitionByName("logo") then new
as-aot app.as --swc assets/skin.swc
# Build manifest (JSON, paths resolved relative to the manifest directory)
as-aot app.as --manifest examples/skia-link.build.example.json
# Export top-level functions for direct JS calls (default WASI command modules only export _start)
as-aot examples/wasm-native/fib.as --target wasm --export fib
# Declarative exports: mark functions/static methods with [WasmExport] metadata to auto-export and generate .exports.json
as-aot examples/wasm-native/export-meta.as --target wasm
# Generate a browser HTML5-rendered page (--target wasm --package web, requires the Emscripten SDK)
as-aot examples/web/hello-web.as --manifest examples/web/hello-web.build.json
# Only generate C and print the compile command, without actually compiling (verify the command without a toolchain)
as-aot examples/hello.as --target wasm --dry
# Keep debug info: all three backends add -g, and wasm's default DWARF stripping is turned off (source-level debugging in DevTools)
as-aot examples/wasm-native/fib.as --target wasm --debug-infoBuild manifest (JSON) fields: target (native/wasm), package (raw/xcode-project/android-project/web,
default raw, see compile.md §6), c-compiler, opt, debug-info (keep debug info, default false, see below),
sources (additional C/C++ sources),
include-paths, link-libs, link-paths, defines, objects (precompiled .o), exports (C symbols exported to the .wasm export table),
swc-paths (a list of .swc libraries; their named bitmap assets are extracted at compile time, re-encoded to PNG after un-premultiplying,
and embedded into the product, decoded by Skia at runtime; vector shapes and display trees (DefineShape*/DefineSprite/PlaceObject*) as well as
nine-slice (DefineScalingGrid) and button four-states (DefineButton2) are likewise baked at compile time into runtime drawing calls —
see docs/en/swc.md §5/§6/§9),
font-urls (font byte-stream URL list for --package web, injected into Skia at runtime),
plus the xcode-project app metadata bundle-id/display-name/icon/deployment-target (see compile.md §6.4). CLI arguments override manifest fields of the same name.
Path-like fields resolve relative to the manifest file's directory. Example at
examples/skia-link.build.example.json.
Per-target layered linking (targets): top-level fields are the common defaults; a targets.<native|wasm> block applies only to the matching target
and replaces wholesale (replace, not append) the same-named top-level values — so one manifest can serve multiple targets with mutually exclusive link sets
(the typical case: native links curl, but WASI has no sockets and -lcurl makes wasm-ld fail, so it must be dropped for wasm). Merge order:
default configuration → manifest top level → manifest targets.<final target> → CLI overrides. Example
examples/flash-net-layered.build.example.json, see compile.md §4.1.
Debug info (--debug-info / manifest debug-info, off by default): by default the product carries no DWARF on any of the three backends — native under -O2
has none anyway; emcc -O2 strips it itself; only the wasi backend's libc.a ships DWARF and wasm-ld keeps it by default (even an empty
int main drags in ~19 KB), so the wasm link adds -Wl,--strip-debug by default (fib.wasm 421 KB → 133 KB; only .debug_* is dropped,
the name function-name section is kept ⇒ traps still print symbolized stacks). Turning on --debug-info adds -g to all three backends and stops
stripping wasm (fib.wasm ~640 KB, containing line numbers of the C we generate ⇒ source-level debugging in DevTools).
On Windows, --air-app uses Win32 + SDL2 + Skia D3D12 direct-to-GPU. The bit width is decided by the AIR descriptor's
<application><architecture> ("32"/"64", default 32), corresponding to vendor/*/windows-x86|windows-x64:
<!-- 32-bit (AIR default, also the default behavior when <architecture> is absent) -->
<application><architecture>32</architecture></application>
<!-- 64-bit -->
<application><architecture>64</architecture></application><renderMode>direct</renderMode>(orgpu) →ASC_RENDER_WINGPU=1+ASC_RENDER_D3D=1, additionally linkingvendor/d3d_glue.ccandd3d12/dxgi/d3dcompiler(a process-levelID3D12Device/command queue/GrDirectContext, window-levelCreateSwapChainForHwnddouble back buffer + fence frame sync +ResizeBuffers);cpu/autokeep CPU raster + SDL streaming texture blit.- The GPU seam is backend-neutral: the generated C and runtime only speak
sk_gpu_*/sk_window_show_gpu/as_skia_gpu_*/w->is_gpu;ASC_RENDER_WINGPUmeans "this window's compositing runs on the GPU", and the concrete backend is named byASC_RENDER_METAL(macOS) /ASC_RENDER_D3D(Windows), with the only branch point invendor/window_glue.cc(sk_attach_gpu). - All differences from macOS are selected at build time, not by runtime branching: on Windows we do not link
metal_glue.mm/objc/Cocoa framework, do not add-lm/-lz(m.lib/z.libdo not exist on Windows), and setAS_HAVE_ICONV=0(non-UTF charsets loudly throw). Archive names follow gn's Windows rules (<target>.lib, nolibprefix) ⇒ a target literally namedlibpnglinks as-llibpng; this conclusion comes from the realbuild.ninjaread out of a macOS-sidegn gen target_os="win". - Stage3D is unavailable on Windows and does not silently downgrade: when the descriptor/source uses
Context3D, the build reportsAirAppError(the AGAL→HLSL translator is not yet started, seeTODO.mdstage 126). - The dependency libraries are built from source by
vendor/build-windows-deps.ps1(Skia m124skia_use_direct3d=true, SDL2, curl, zlib, nghttp2; both x86/x64 widths, LLVMclang-cl, bit width verified withdumpbin, a mismatch hard-fails). Full details, the list of assumptions not verified on real hardware, and the Windows first-run checklist are inwin32.md.
| Category | Supported |
|---|---|
| Types | int, uint, Number, Boolean, String, void, Array, Vector.<T>, Function, RegExp, Class (class reference), Dictionary (object-reference-keyed associative table), * (untyped → dynamic any boxing), class names (object types), interface names |
| Literals | integer, float, hexadecimal (0xFF), string (single/double quotes), true/false, null, Infinity/NaN, array literal [...], object literal { ... } (keys may be numeric: {0: "a"}, stage 106), E4X XML literal <a b="1">…</a> (nested/self-closing/attributes/comments/CDATA, stage 106; {expr} interpolation is loudly rejected), regex literal /pattern/flags, Vector literal new <T>[...] |
| Expressions | arithmetic + - * / %, comparison == != < <= > >=, logical && || !, unary - + ! ~, bitwise & | ^ << >> >>>, increment/decrement ++ -- (prefix/postfix), ternary ?:, null coalescing ?? (lowest precedence, short-circuit, the left side is only hoisted into a boxed temporary when necessary, stage 106), comma operator (a, b) (lowest precedence, result is the right operand, stage 106), E4X descendant axis x..name / x..* (pre-order, excluding self, stage 106), E4X computed names x.ns::[expr] (child axis ⇒ XMLList) and x.@[expr] (attribute axis ⇒ String) (stage 108; the ns qualifier folds per "namespace transparency"), type checks is / casts as (supporting dotted fully-qualified names), coercion syntax Type(expr), typeof, delete, membership in, array indexing a[i], property access o.x, compound assignment += -= *= /= %= <<= >>= >>>= &= |= ^=, logical assignment ||= &&= |
| Statements | variable declarations (including multiple var a:T, b:T, c:*;), expression statements, if/else, while, do-while, for, with (obj) stmt (object members as the innermost scope), for-in (array indices / dynamic object keys / Dictionary object-reference keys), for-each-in (including the bare-identifier iteration form with var omitted), switch/case/default, break, continue, labeled statements label: while(...){ break label; }, return, throw, try/catch/finally (multi-branch catch catch (e:A) … catch (e:B) … matched in source order, clearing as_exception on a hit, rethrowing if none match, stage 106), blocks; new C (omitted argument list, equivalent to new C()) and a[i] ||= v / a[i] &&= v (short-circuit assignment with an index target; the receiver/index must be side-effect-free); arguments inside functions (a formal-parameter array generated on demand) |
| Functions | free functions with typed parameters and return types; omitted parameter types (defaults to * per spec, stage 106); optional and default parameters function f(a:int, b:int = 1); any-typed parameter function f(message:* = ""); rest parameters function f(...args:Array); Function type and function values (anonymous function expressions, functions as arguments/return values); closures (anonymous functions capturing outer local variables, captured variables heap-allocated to escape) |
| Classes | fields + methods + parameterized constructors + new + this + inheritance extends + override (vtable dispatch) + super calls (including the implicit no-arg super() when there is no explicit super) + super.property property read·write (field/getter/setter) + access modifiers public/private/protected/internal + static fields/methods + const constants + declaring multiple fields on one line private var a:T = 1, b:T = 2; (each declarator gets an independent slot, sharing the statement's modifiers, stage 108) + get/set accessors + final classes/methods + dynamic classes (dynamic class, undeclared properties route to a runtime slot table) + static methods as Function values (ClassName.method or bare identifiers) + dynamic class instantiation new (expr as Class)() (no-arg factory via class reference) + class-body { ... } static initializer blocks and bare statements in the class body (stage 106; order per adl measurement: first static field initializers in declaration order, then class-body blocks in source order) + import inside the class body (tolerated and ignored) and surplus ; + get/set usable as ordinary identifiers (var set:int) |
| Interfaces | interface declaration (including extends A, B multiple parent interfaces, with methods transitively merged along the parent chain + cycle detection, stage 106) + implements (including implements A, B, interface vtable dispatch) + is/as interface type checks (runtime as_iface_lookup, folded only when statically known to be true) + interface accessors (iface.getter/iface.setter = v/iface.getter() — the zero-argument "called as a method" form — all go through the interface vtable's getter/setter slots; an accessor with arguments always throws loudly; stage 110 completed assignment and method-call forms) + interface accessors can be satisfied by "built-in accessor-type properties" (stage 118: a class inheriting Sprite/InteractiveObject's x/y/visible/alpha/mouseEnabled… satisfies get x():Number; set x(v:Number):void; even without writing a getter, matching AIR — AIR declares these as accessor pairs; a user's public var x is still rejected, likewise matching AIR) |
| Packages/Modules | multi-file compilation (multiple .as files merged into one compilation unit); package namespace isolation (C identifier prefix); import cross-file/cross-package short-name resolution; internal package-local visibility (rejected across packages); custom namespaces public namespace/use namespace/ns::member (transparent at AOT compile time, no runtime visibility semantics) |
| Root classes | Object built-in root class: implicit base class of all classes, toString() returns the runtime class name; Error built-in error type: new Error(message, id), .message property; built-in error subclasses TypeError/RangeError/ArgumentError/SyntaxError/ReferenceError; built-in Date class (millisecond timestamp + calendar accessors); flash.events event system Event/EventDispatcher/MouseEvent/KeyboardEvent/FocusEvent/TimerEvent/ProgressEvent/ErrorEvent/IOErrorEvent/DataEvent (addEventListener/dispatchEvent capture→target→bubble three phases, preventDefault/stopPropagation/stopImmediatePropagation; Event completes the CANCEL/CLEAR/CLOSE/CONNECT/COPY/CUT/DEACTIVATE/EXIT_FRAME/FRAME_CONSTRUCTED/FULLSCREEN/ID3/INIT/MOUSE_LEAVE/OPEN/PASTE/RENDER/SCROLL/SELECT/SOUND_COMPLETE/TAB_*/UNLOAD constants, event subclasses (stage fifty-nine) TimerEvent (TIMER/TIMER_COMPLETE), ProgressEvent (PROGRESS + bytesLoaded/bytesTotal), ErrorEvent (ERROR + text), IOErrorEvent (IO_ERROR, inheriting text), DataEvent (DATA + data), VsyncStateChangeAvailabilityEvent (VSYNC_STATE_CHANGE_AVAILABILITY + read-only available (the AIR surface, measured to be constantly false) and read-only refreshRate (the enhancement surface: AIR has no refresh-rate API at all, see "Beyond AIR" and enhancements.md §4.8 E16))); flash.display display list DisplayObject/InteractiveObject/DisplayObjectContainer/Stage/Sprite/Shape/Graphics/Bitmap/BitmapData (layering/coordinates/addChild (triggering addedToStage)/removeChild/hit testing/recursive rendering; geometry and hit testing (stage 94-6): Shape/Sprite/MovieClip all have graphics (Sprite/MovieClip as a lazy getter), width/height are derived accessors (the content bounding box through the object's own transform; stroke expansion counts, TextField being the exception — it stores real size and the getter reports "size × scaleX"), assigning to width/height is scaling (scaleX = scaleX * value / cur, writing 0 for empty content); hit testing is transform-aware (the parent-chain matrix is folded level by level down to the local bounding box, hitTestPoint takes stage coordinates); geometry/coordinate API family (stage 94-9): getRect/getBounds (content bounding box, the former excluding and the latter including strokes; targetCoordinateSpace == null ⇒ its own local space, == self ⇒ short-circuits to the exact local box), localToGlobal/globalToLocal (walking the full ancestor matrix chain, returning a new Point, a null point throwing TypeError #2007), hitTestObject (intersects two stage-space bounding boxes, strokes included, empty objects never intersect) and hitTestPoint both on DisplayObject (so Shape can use them too); MouseEvent.localX/localY and the text insertion-point index use the same folding, as_render_object dispatches by is subtype chain, user Sprite subclasses may act as containers, cacheAsBitmap subtree bitmap caching (toggle invalidates and re-bakes, the bake being pixel-identical to direct recursive rendering; incremental-redraw auto cacheAsBitmap: a static subtree's fingerprint drives automatic baking); Stage stage properties stageWidth/stageHeight/fullScreenWidth/fullScreenHeight/displayState/quality/color/align/scaleMode/frameRate/vsyncEnabled and boolean switches, plus read/write focus (InteractiveObject; assignment dispatches focusOut→focusIn per adl semantics and is the enabling condition for copy) (frameRate is, per AIR, a single application-wide value: writing it in any window/any Stage takes effect globally, and the frame loop dispatches only one application frame per tick, not stacking with the number of windows — stage 89-73; vsyncEnabled is likewise a single application-level value, measured to default to true, and when false removes the "cap when an explicit frameRate exceeds the panel refresh rate" — stage 129), constant classes StageAlign/StageScaleMode/StageQuality/StageDisplayState); flash.display.Screen (stage 89-71): Screen.mainScreen/Screen.screens/Screen.getScreensForRectangle(rect) + instance bounds/visibleBounds/colorDepth (static properties + a one-argument static method, no getScreens(); bounds is the full display mode and visibleBounds the usable area, two different quantities); every access returns a fresh wrapper object (Screen.screens[0] !== Screen.mainScreen, measured on adl), only the cached Rectangle is a live object; new Screen() throws Error #2012; the flash.display.NativeWindow family (stage 89-71, multi-window): NativeWindow/NativeWindowInitOptions + constant classes NativeWindowSystemChrome/NativeWindowType/NativeWindowRenderMode/NativeWindowDisplayState/NativeWindowResize, new NativeWindow(opts) immediately opens its own OS window (with visible=false on return, default frame 400×232 and client area 400×200, frame size taken from NSWindow), each window has its own Stage (scaleMode="showAll"/align=""; frameRate is application-level and a new window inherits the current value rather than carrying its own 24fps — stage 89-73), bounds/x/y/width/height are window-geometry proxies, close() goes through frame-boundary deferred finalization (closed is still false on the calling frame) and dispatches Event.CLOSE, plus activate/minimize/maximize/restore/orderToFront/orderToBack and 4 statics isSupported/supportsTransparency/supportsMenu/supportsNotification; inherits EventDispatcher per AIR (stage 89-72) — so a window can addEventListener, and both Event.CLOSE/ENTER_FRAME reach it (this is both the API shape and a struct layout contract: listeners/parent must precede _win, otherwise dispatchEvent reads an out-of-bounds pointer and crashes); flash.text text TextField/TextFormat (SkParagraph full layout: HarfBuzz shaping + UAX#14 breaking + real line height, multiline/wordWrap/scrollV/maxScrollV/numLines/textWidth/textHeight + background/backgroundColor background fill + border/borderColor border (default false/0x000000, four x∈{0,width}/y∈{0,height}, drawn above the background, scaling with the field, not counted into textWidth/textHeight; all three color properties are 24-bit RGB and discard the alpha byte on write — stage 94-8); TextFormat.align horizontal alignment left/center/right/justify is implemented — with a layout width it delegates per-line alignment to SkParagraph, and with wordWrap=false (no layout width) it does a whole-block translation at the draw origin per AIR's 2px text padding, centering/right-aligning without wrapping just like AIR; this is exactly the behavior Starling's TrueTypeCompositor relies on for centered native text); flash.utils.Dictionary (new Dictionary(weakKeys): an associative table keyed by object reference, supporting in/delete/for-in, the weak-reference parameter accepted but not modeled, keys strongly held for the program lifetime); ByteArray's multibyte / boolean / AMF surface (stage 94-4): readMultiByte(length, charSet)/writeMultiByte(value, charSet)/readBoolean/writeBoolean/readObject()/writeObject(object) + objectEncoding (default ObjectEncoding.AMF3) + integer-indexed read/write ba[i] (reading a gap yields 0; writing goes through ToUint32's low byte, out-of-bounds auto-extends with zero fill, and does not affect position); charSet goes through the system iconv (native builds link -liconv), 'unicode'/'utf-16' are always UTF-16LE without BOM and the endian property has no effect, ''/unknown charsets fall back to the OS legacy encoding (MacRoman on Apple, CP1252 elsewhere), a character the target charset cannot express is hand-written '?' (not relying on //TRANSLIT), a truncated multibyte sequence lossily decodes to the empty string, an out-of-bounds read throws EOFError #2030; writeBoolean writes 1 byte, readBoolean treats nonzero as true; an AMF3 object-graph codec (write + read, byte-for-byte aligned with adl 51.4.1) covers null/undefined/bool/int (the compact U29 form)/double/String (U29S length by UTF-8 bytes)/Date/ByteArray/Array (dense section + associative section)/Vector.<T> (0x0d0x10)/Dictionary/typed object (trait table + reference table + own fields before superclass fields); flash.net.registerClassAlias/getClassByAlias are wired, aliases working along the superclass chain (measured: an alias registered under a base-class name is written into subclass instances); readObject/writeObject share the same codec between ByteArray and URLStream, and AMF0 is not yet implemented (objectEncoding = AMF0 throws loudly rather than silently writing AMF3); flash.utils.Proxy (stage 94-1): dynamic class X extends Proxy uses the flash_proxy namespace's ten interceptors to take over dynamic property access (getProperty/setProperty/deleteProperty/hasProperty/callProperty/getDescendants/nextNameIndex/nextName/nextValue/isAttribute), real slots take precedence, and an uncovered operation throws #2088…#2107 per adl measurement; flash.utils.Timer (stage sixty): a repeating timer, inheriting EventDispatcher, constructor Timer(delay, repeatCount=0), delay setter validates negative/non-finite values throwing RangeError; repeatCount setter does not validate (int truncation, negatives preserved, NaN→0), currentCount/running read-only, start/stop/reset; dispatches TimerEvent.TIMER per interval, TimerEvent.TIMER_COMPLETE when repeatCount>0 is exhausted, repeatCount=0 repeats indefinitely; driven by the frame loop as_timer_tick (off-screen uses tickTimers() to pump manually, WASI degrades to second granularity); flash.geom 2D geometry (stage fifty-eight) Point/Rectangle/Matrix/ColorTransform/Transform (Point static distance/interpolate/polar and instance add/subtract/offset/normalize/setTo/copyFrom/clone/equals/toString; Rectangle read-only top/bottom/left/right and intersection/union/contains/containsPoint/containsRect/intersects/equals/inflate/offset/clone/setEmpty/isEmpty/toString; Matrix identity/translate/scale/rotate/concat/invert/transformPoint/deltaTransformPoint/createBox/createGradientBox; ColorTransform 8 channels + concat; Transform holds identity matrix/colorTransform, now wired to DisplayObject.transform (Matrix applied to the Skia canvas transform)); Vector3D/Matrix3D (stage seventy-nine, flash.geom 3D geometry, pure logic, no GPU): Vector3D x/y/z/w four components (geometric ops normalize/dotProduct/crossProduct/distance/angleBetween/length/lengthSquared use xyz only, w the homogeneous coordinate; add/subtract/scaleBy/negate four-component; equals(allFour), toString + static constants X_AXIS/Y_AXIS/Z_AXIS); Matrix3D 4×4 column-major (rawData a column-major 16-element Vector.<Number>, identity/append/prepend/invert (in-place, returning Boolean invertibility)/transpose/transformVector/transformVectors/deltaTransformVector/pointAt/interpolate/recompose/decompose/copyFrom/clone + translation/scale/rotation constructors + static identity/interpolate, decompose/recompose round-trip consistent); AGALTranslator (stage eighty, AGAL bytecode kernel, flash.display3D pure-static bridge, not an AS3 runtime API): translate(bytes:ByteArray, target:String):String translates AGAL1/2/3 bytecode into readable MSL ("msl") or GLSL ES ("glsl") source — parses the 7-byte header + variable-length instruction tokens (dest [num:16][mask:8][type:8], source [num:16][offset:8][swizzle:8][type:8][reltype:8][relsel:16], sampler [num:16][lod:8][0:8][samplerbits:32]), 28 base instructions with swizzle/write-mask expansion + AGAL2 control flow (ife/ine/ifg/ifl/els/eif) + derivatives (ddx/ddy) + tex sampling; register type is shader-relative encoding (vc=fc=1, vt=ft=2, op=oc=3, fs=vs=5) dispatched by vertex/fragment; basic validation (magic/version/opcode/truncation/instruction count) throws Error); Stage3D/Context3D (stage 81flash.display3D CPU state machine + optional Metal GPU backend): Stage.stage3Ds returns a Vector.<Stage3D> (single slot), Stage3D.requestContext3D(mode) lazily creates a Context3D and synchronously dispatches Event.CONTEXT3D_CREATE, context3D/x/y/visible read/write; Context3D state machine configureBackBuffer/setBlendFactors/setDepthTest/setCulling/setStencilActions/setScissorRectangle/setStencilReferenceValue/setProgram/setVertexBufferAt/setTextureAt/setProgramConstantsFromMatrix (transposed=false uploads the column-major matrix transposed to row-major, true copies verbatim)/setProgramConstantsFromVector (AGAL3, uploading Vector.<Number> to vc/fc; numRegisters defaulting to -1 means deriving the register count from the vector length — Starling's BlurFilter/ColorMatrixFilter pass only a vector, see stage 89-21)/createVertexBuffer/createIndexBuffer/createProgram/createTexture/createCubeTexture/createRectangleTexture/setCubeTextureAt/setRectangleTextureAt/drawTriangles/drawTrianglesInstanced (AGAL3 instancing)/clear/present/drawToBitmapData/setRenderToTexture/setRenderToBackBuffer + read-only driverInfo/profile/maxBackBufferWidth/maxBackBufferHeight; resource classes VertexBuffer3D/IndexBuffer3D (uploadFromVector/uploadFromByteArray record a CPU copy, the latter reading little-endian float32/uint16 widened into the vector)/Program3D (upload records bytecode)/Texture/TextureBase (uploadFromBitmapData records the source BitmapData and synchronously uploads the GPU texture, see below; for miplevel != 0 it uploads that level by the region rule measured on adl — taking the source bitmap's top-left lw×lh (lw = max(1, width>>level), read by the source's row stride), computing the layer count from max(width,height) and rejecting out-of-range levels, while setting the texture's "has chain" flag; stage 128)/CubeTexture (six-face BitmapData descriptor)/RectangleTexture (an NPOT single-face descriptor; createRectangleTexture(..., optimizeForRenderToTexture=true) really allocates a GPU texture usable as a render target — Starling's Texture.empty(...) non-compressed/non-mip textures all take this path, and the filter off-screen pass depends on it, see stage 89-21); Texture.uploadCompressedTextureFromByteArray (stage 89-21): format 3/5 in the ATF container (raw DXT1/DXT5) is CPU-decoded to straight-through alpha ARGB and goes through the normal texture upload path (no premultiply, matching adl handing raw DXT5 to the GPU), while other format/cubemap cases throw Error #3680 loudly (no silent blank texture); 16 constant classes Context3DBlendFactor/Context3DBufferUsage/Context3DClearMask/Context3DCompareMode/Context3DCubeMapFace (int)/Context3DFillMode/Context3DMipFilter/Context3DProfile/Context3DProgramType/Context3DRenderMode/Context3DStencilAction/Context3DTextureFilter/Context3DTextureFormat/Context3DTriangleFace/Context3DVertexBufferFormat/Context3DWrapMode; **GPU backend (stage 82ASC_RENDER_STAGE3D=1 + links vendor/stage3d_glue.mm (raw Metal: MTLBuffer vertex/index/constant, MTLTexture 2D, MSL compilation, off-screen render-to-texture), drawTriangles/drawTrianglesInstanced are the sole synchronization point, deferred-uploading all bound resources and drawIndexedPrimitives-ing to screen (instancing via instanceCount); a frame's draws batch into a single MTLCommandBuffer, committing + waiting only once at the frame boundary (stage 104: previously every drawTriangles did commit + waitUntilCompleted, measured at cpu=0.01ms/gpu=0.04ms while wait≈0.5ms ⇒ that 0.5ms was pure round-trip latency, and with Starling's fps monitor on, just 1.6 extra draws per frame directly ate 0.8draws/batch 7wait 0.93ms per frame, Benchmark peak object count +39%, and the stats-on gap narrowed from −14% to −6%); blend state (stage 89-22): Metal bakes blend state into MTLRenderPipelineState (no per-draw setter), so the glue retains the compiled vertex/fragment functions and caches pipelines by blend-factor pair, selecting by the current setBlendFactors when encoding each draw (lazily building on miss, LRU cap 12); depth/stencil state is likewise cached in a keyed LRU (S3D_MAX_DSS slots + s3d_select_dss, so away3d alternating depth state per draw no longer creates a new MTLDepthStencilState each draw — measured dss=2 over 1680 draws, stage 128) — so changing blend mode mid-way on the same Program3D now truly takes effect (Starling's Blend Modes scene previously changed the label but not the image), and drawToBitmapData reads back the current render target's pixels (render override preferred, ARGB↔BGRA conversion), otherwise pure-C mode is only a state machine (as_s3d_* no-op wrappers keep it link-clean); web target (stage 89-30): the same generated C in the browser instead links vendor/stage3d_webgl.cc (WebGL2/GLES3) + build-time ASC_S3D_GLSL=1, with shaders emitted as GLSL ES by the same AGAL translator (ASC_AGAL_TARGET selects MSL/GLSL at build time) — the s3d_* signatures are identical on both ends, so the Context3D state machine and the as_s3d_* wrappers are unchanged; the GL side's Y-axis flip (gl_Position.y = -gl_Position.y in the vertex stage, so texture v=0 and readback row order both match AS3), BGRA↔RGBA swap, per-register uniform vec4 vcN/fcN, the DEPTH24_STENCIL8 off-screen FBO and separate front/back stencil APIs, caching sampler state per texture object, and sharing a single WebGL2 context with Ganesh (every s3d_* entry that touches GL calls sk_gr_reset_context()) are all handled inside vendor/stage3d_webgl.cc, and examples/air-starling-demo's 12 scenes each render in the browser and match the AIR adl reference (120 fps / 0 drops; see docs/en/html5-web.md §3.3); presentation currently goes through CPU readback (as_s3d_readback_render + as_skia_canvas_draw_bgra, measured at uploadFromBitmapData uploads synchronously, so it is legal for the caller to BitmapData.dispose() immediately after — so the AOT likewise builds the MTLTexture immediately in uploadFromBitmapData (Texture remembers the Context3D's glue handle, creating it via s3d_texture_from_pixels but not binding a sampler slot, with Context3D_submit then calling s3d_bind_texture by slot), rather than deferring the upload to the next drawTriangles (deferring would read empty/stale data from a pixels already freed after dispose(): Starling's glyph rasterization is literally "draw → fromBitmapData → dispose", which once left the text texture never uploaded and the sampler slot retaining the previous atlas page's contents, rendering as colorful garbage); flash.filters filters (stage sixty-one) BitmapFilter (abstract base class, clone())/BlurFilter (blurX/blurY/quality + clone)/DropShadowFilter (distance/angle/color/alpha/blurX/blurY/strength/quality/inner/knockout/hideObject + clone)/GlowFilter (color/alpha/blurX/blurY/strength/quality/inner/knockout + clone) + constant class BitmapFilterQuality (LOW/MEDIUM/HIGH); DisplayObject.filters read/write (Array access); BitmapData.applyFilter (BlurFilter implemented via separable box blur, DropShadowFilter/GlowFilter rasterization deferred throwing errors); flash.display completion (stage sixty-two) MovieClip (currentFrame/totalFrames + play/stop/gotoAndPlay/gotoAndStop, frame-loop driven, currentFrame wraps to 1 when out of bounds, totalFrames writable to simulate multi-frame timelines)/SimpleButton (upState/overState/downState/hitTestState four state references)/Loader (content/contentLoaderInfo + load(url) async dispatch of INIT (sync)/COMPLETE (next frame tick); an http(s):// URL is explicitly recognized and goes through the same transport seam as URLLoader — native + ASC_HAVE_CURL runs as_http_perform on a worker / web + ASC_HAVE_FETCH starts a fetch and decodes in the frame-boundary pump (stage 89-56 wired up the web side; before that this branch had only a curl implementation on web, so every remote Loader.load reported "this build has no HTTP backend" without sending a single request), with no backend dispatching a distinguishable honest ioError; cross-origin images in the browser are subject to the same-origin policy: the image can display but its pixels cannot be read, and Skia presentation needs pixels, so the CDN must return Access-Control-Allow-Origin, otherwise we truthfully report a CORS ioError)/LoaderInfo (bytesLoaded/bytesTotal/url + COMPLETE/INIT/OPEN/UNLOAD/PROGRESS/IO_ERROR constants); flash.net/flash.ui (stage sixty-three, the API surface completed at stage 89-48; http(s):// networking opt-in at 89-49) URLRequest (the full AIR property surface: url (default null)/method (default "GET")/data (any, default null)/contentType (default null, measured on adl; the documented application/x-www-form-urlencoded is the send default, not the property value — only a body-carrying request that did not declare one sends that MIME string, and a bodyless request sends none, stage 89-69)/requestHeaders (default an empty Array, directly push-able)/authenticate/cacheResponse/followRedirects/manageCookies/useCache (all default true)/idleTimeout (default 0)/userAgent (an OS-derived Flash/AIR-form UA string)/digest; the latter 6 are, per the official "initialized from the URLRequestDefaults.X property", read from URLRequestDefaults at construction + instance method useRedirectedURL(sourceRequest, wholeURL=false, pattern=null, replace=null) (first a domain/whole-segment replacement by wholeURL, then pattern→replace on the result, supporting both String and RegExp pattern))/URLRequestMethod (GET/POST/PUT/DELETE/HEAD/OPTIONS six constants)/URLRequestHeader (name/value, constructor defaults ""/"")/URLRequestDefaults (7 static properties authenticate/cacheResponse/followRedirects/idleTimeout/manageCookies/useCache/userAgent, read/write matching AIR defaults; setLoginCredentialsForHost deferred — needs an authenticating HTTP stack)/URLLoader (data/dataFormat/bytesLoaded/bytesTotal + load(request)/close(), inheriting EventDispatcher, constructor URLLoader(request=null) starting the load when passed; asynchronous (next frame tick); reads local files for file:///relative paths, and http(s):// really networks with native + ASC_HAVE_CURL (build manifest link-libs: ["curl"]) (GET/POST/status codes/HTTPS), dispatching a distinguishable honest ioError without a backend; event order open (only when the request reaches transport) → progress → httpStatus (carrying only status; 0 for local/backendless loads) → complete (on failure: httpStatus(0) → empty data → ioError); data is published per dataFormat as a GC-tracked string (text)/ByteArray (binary)/URLVariables (variables); bytesLoaded/bytesTotal are constantly 0 during loading and set to the byte count on completion (AIR behavior); close() really aborts an in-flight load (no further terminal events afterward), throwing an invalid stream error with no stream)/URLVariables (dynamic class, undeclared string-keyed properties written into a runtime slot table, toString() serializing to a query string, decode(source) decoding a query string into the instance (values percent-decoded, keys not — matching AIR))/Keyboard (static key-code constant table AZ/NUMBER_0~NUMBER_9/SPACE/ENTER/TAB/ESCAPE/BACKSPACE/DELETE/SHIFT/CONTROL/LEFT/RIGHT/UP/DOWN + read-only isAccessible)/Mouse (static hide/show + writable cursor/read-only supportsCursor/supportsNativeCursor); flash.filesystem (stage sixty-four) File (nativePath + read-only properties url (file:// form)/exists/isDirectory (getter, via stat()) + resolvePath/createDirectory/deleteFile/deleteDirectory + static read-only applicationStorageDirectory/applicationDirectory/desktopDirectory/documentsDirectory/userDirectory (the latter resolved at runtime via getenv("HOME")), POSIX stat/mkdir/remove)/FileStream (open/openAsync (async dispatch of ProgressEvent.PROGRESS + COMPLETE)/close/readUTFBytes/writeUTFBytes + read-only position/bytesAvailable, FILE* handle as opaque void* not GC-tracked)/FileMode (READ/WRITE/APPEND/UPDATE constants); flash.net.SharedObject (stage 89-40): SharedObject.getLocal(name, localPath?) returns a local shared object (inheriting EventDispatcher, fqn = flash.net::SharedObject, not dynamic, throwing ReferenceError #1056 on assignment to an undeclared property just like AIR), data a read-only-getter Object (a key-value table, supporting data[k] read/write/delete/for-in), size returning the byte count of the representation to be persisted, flush() returning SharedObjectFlushStatus.FLUSHED ("flushed")/throwing loudly on failure, clear() emptying the data and deleting the on-disk file, close() having no effect on a local object (an empty implementation), setDirty() an empty implementation, setProperty writing data; getLocal with the same name returns the same instance (a process-wide instance table, static fields as permanent GC roots); defaultObjectEncoding/preventBackup/client/objectEncoding/fps accessors and ObjectEncoding (AMF0=0/AMF3=3/DEFAULT=3), SharedObjectFlushStatus (FLUSHED/PENDING) constant classes all modeled on measured AIR values; persistence to [<localPath>/]<name>.json under applicationStorageDirectory, automatically flushed at application exit (an atexit hook, matching AIR's "write local shared objects when the application closes" behavior — even if flush() was never called); getRemote/connect/send require running on a Flash Media Server and this runtime throws loudly (rather than silently pretending success) |
| Builtins | trace(...); Array methods push/pop/shift/unshift/splice/slice/indexOf/join/concat, .length; String methods charAt/charCodeAt/indexOf/lastIndexOf/substring/substr/slice/split/match/search/replace/toUpperCase/toLowerCase/concat/localeCompare/valueOf/toLocaleLowerCase/toLocaleUpperCase/startsWith/endsWith, static String.fromCharCode, .length; Number/int/uint methods toFixed/toExponential/toPrecision/toString(radix)/valueOf, Number static constants MAX_VALUE/MIN_VALUE/NaN/POSITIVE_INFINITY/NEGATIVE_INFINITY, int/uint static constants MAX_VALUE/MIN_VALUE; Math constants PI/E/LN10/LN2/LOG10E/LOG2E/SQRT1_2/SQRT2 and methods abs/floor/ceil/round/sqrt/pow/min/max/random/sin/cos/tan/asin/acos/atan/atan2/exp/log; global functions parseInt/parseFloat/isNaN/isFinite; flash.utils.getTimer() (milliseconds since process startup, not a Unix timestamp, used for 60Hz frame-loop timing); flash.utils.setTimeout(closure, delay) / flash.utils.clearTimeout(id) (scheduling/canceling one-shot function callbacks, triggered by the frame loop after delay ms, returning a uint timer id; callbacks bound via function-value closure); flash.utils.setInterval(closure, delay, ...args):uint / flash.utils.clearInterval(id) (stage 94-3): shares the same table and the same id counter as setTimeout/clearTimeout (measured: consecutive calls give ids 1,2,3…), so the two cancel each other (clearInterval can cancel a timeout's id, clearTimeout can cancel an interval's id), and an unknown/0 id is a silent no-op; a repeating timer schedules the next one after the callback ends, fires at most once per frame, and stops when clearInterval is called inside its own callback; a negative/NaN delay throws RangeError #2066; a null closure still consumes an id and never fires; flash.utils.getQualifiedClassName(value:*):String (stage eighty-six, reflection: returns the object's fully-qualified class name package::Class, primitives returning "null"/"Number"/"Boolean"/"String"/"void"/"Array"/"Function") and flash.utils.getQualifiedSuperclassName(value:*):String (stage 94-3: the qualified name of the value's class's superclass, walking multi-level chains up, returning null when there is no superclass) and flash.utils.getDefinitionByName(name:String):Object (returns a class reference by name, closing a round-trip with the former, supporting both ::/. separators; from stage 127 built-in classes are also in the registry (getDefinitionByName("flash.display.BitmapData") works), and a miss throws AIR's Error #1065: Variable <last segment> is not defined. (errorID = 1065); describeType(value):XML returns a <type name="package::Class"/> description tree, implemented in stage ninety-three); flash.system.System memory statistics (totalMemory(uint)/totalMemoryNumber(Number)/freeMemory(Number)/privateMemory(Number) four purely static read-only properties: totalMemory is the runtime self-managed heap used bytes (returns 0 beyond 4 GiB), totalMemoryNumber same value unclamped, freeMemory is the arena allocated-but-unused gap, privateMemory is the real process resident memory (macOS mach_task_basic_info/Linux getrusage×1024/Windows GetProcessMemoryInfo/WASI degrades to totalMemory); flash.system.Capabilities (stage sixty-five): a final static read-only class (same pattern as System, no instantiable class) — version (a fixed AIR-compatible "<platform-prefix> 50,0,0,0", aligned with Adobe AIR's final major, the AS-AOT marker carried by manufacturer), os (Mac OS/Windows/WASI/Linux via conditional compile), cpuArchitecture (ARM/x86), cpuAddressSize (32/64), supports64BitProcesses/supports32BitProcesses (derived from address size), playerType ("Desktop"), manufacturer ("AS-AOT"), isDebugger (false), touchscreenType ("none"), language (locale-derived ISO 639-1), screenResolutionX/screenResolutionY (SDL2 primary-display size, 0 in headless builds), screenDPI (72.0 fallback, no per-display DPI query), screenColor ("color"), pixelAspectRatio (1.0), hasAudio (true); type conversion String(x)/Number(x)/Boolean(x)/int(x)/uint(x); built-in constructor calls Array(...)/Object(...)/Vector.<T>(...) (also allowed without new, but Vector.<T>(arrayLike) is a "conversion", not a "construction" — semantically different from new Vector.<T>(length), see the type-mapping table below); Object(x) with an argument returns identity, boxing primitives as any; URI encoding/decoding encodeURI/decodeURI/encodeURIComponent/decodeURIComponent/escape/unescape; Boolean methods toString/valueOf; undefined global constant; object literals {} (string-keyed associative arrays); top-level JSON class stringify/parse (recursive serialization/parsing of objects, arrays, and primitives); exceptions throw/try/catch/finally (based on setjmp/longjmp); Date class (constructor overloads new Date()/new Date(ms)/new Date(year,month,day,...)/new Date(str), static Date.parse(str), accessors getTime/getFullYear/getMonth/getDate/getDay/getHours/getMinutes/getSeconds, formatting toDateString()/toUTCString()); RegExp class (regex literals /pattern/flags and new RegExp(pattern, flags): test/exec/source/global/ignoreCase/multiline/dotall/extended/lastIndex, supporting the five flags i/m/s/g/x; an embedded self-built ES3 backtracking regex VM: capturing/non-capturing groups, backreferences, lookahead, lazy/greedy quantifiers, character classes and \d\w\s escapes; String.match/search/replace regex variants: match returns the capture array or null, search returns the first match position, replace supports $1~$9/$&/$$ substitutions and function replacement repl:Function); Vector.<T> type-safe arrays push/pop/join/indexOf/slice/concat/splice/forEach/map/filter/sort/reverse, read/write .length, parameterized constructor new Vector.<T>(length, fixed), vector literal new <T>[...], indexed read/write with bounds checks; Array.sortOn(field, options) (sorting by object field, supporting Array.NUMERIC(16)/Array.DESCENDING(2) and NUMERIC | DESCENDING combinations), Array.concat accepting dynamic elements; Function.apply(thisArg, argsArray) (calling a function value with arguments taken from an array); dynamic method calls (when a receiver of type Object or any misses a static signature, dispatched by name through the method reflection table as_dyn_call to class methods) |
| Semantics | Every subexpression is evaluated only once (stage 89-38): AS3's evaluation rule is "each operand in an operation is evaluated once, left to right", but several shapes of generated C inline the same operand in multiple places (member writes include a write barrier, property-getter reads go through the vtable, as tests then takes the value, interface == compares two fields, a Function value call reads the closure and its env, regex arguments read compiled/global, Boolean(str) reads the string twice) — so any operand that may have side effects (where isPureExpr is false) is first stored in a temporary (hoistImpure), so box.get().label = "Z" runs the getter once (three times before the fix), and h.getFn()() and s.replace(h.mkRe(), …) each run once (twice before the fix); module-level var initialization (the other path of emitTopLevel), a for init, and a throw value got the same sequenceValueExpr pass. Truthiness semantics (stage 89-38, measured against AIR 51.4.1): Boolean(x)/if (x) are always true for a non-null reference (object/array/Vector/Dictionary/class instance/interface value/function) (previously judged by ToNumber(x) != 0, which was false for any object), and always false for NaN (C's if (d) treats NaN as true, so a new as_num_truthy(x) = x != 0 && !isnan(x) was added, and both static Number conditions and the any boxing path use it); / is always Number (AS3 semantics); %'s zero divisor is defined per AS3 semantics (5 % 0 yields NaN, var r:int = 5 % 0 yields 0, INT_MIN % -1 yields 0), guarded by value via as_int_rem/as_uint_rem (C's integer % is UB on a zero divisor / INT_MIN % -1, and traps per spec under wasm); + auto-concatenates and boxes when encountering strings, and when a dynamic (*) operand is involved the runtime value decides (stage 89-34): emitBinary calls the as_add_v runtime helper (ES3 ToPrimitive — a String at runtime, or an object that stringifies, concatenates; otherwise the operands add), boxing the result as any. Measured: var a:Array=["x","y"]; a[0]+a[1] yields "xy" (was 0), var d:*="x"; d + 1 yields "x1" (was 1), null + 1 yields 1 (was a C compile error, (NULL + 1)). Known simplification: object/array boxes still render through as_v_str_val on the concatenation side, so [Array] + 1 gives [Array]1, not AS3's 1,21; trace/String(Number) output the IEEE-754 double's shortest 17-digit representation (trace(1/3) → 0.3333333333333333); Date.getTime() returns the real millisecond timestamp (gettimeofday); is/as perform runtime type checks via the vtable super chain; array elements are dynamic types (as_value boxing), heterogeneous elements handled automatically; exceptions jump via setjmp/longjmp to the nearest active catch, finally always executes (also available on the wasm target: stage 89-35 connected the EH link — -lsetjmp + the standard exception-handling proposal's try_table, and 9 examples containing try/catch produce byte-identical output across native/wasm); bitwise operations convert operands to 32-bit integers per AS3 semantics (>>> is unsigned right shift); dynamic index/property access on Object/any types goes through the reflection table (field offset + method table) onto real class instances, falling back to record slots on miss (obj[key]/obj.prop/obj[key] = v/obj.prop = v); conditional contexts (if/while/?:/&&/||/!) determine any values per AS3 truthiness (null/undefined/empty string are falsy, non-zero numbers and non-empty objects are truthy); a static String uses the same rule (an empty string is falsy, via as_str_truthy evaluated once, stage 125); the top-level script scope is the whole top-level statement tree (stage 89-34): a var/const written inside a block / if / for init / switch case / try shares the same C file-scope global as a top-level declaration (for (var i… emits for (g_i = 0; …), and trace(i) after the loop reads that same slot), and closures read/write that global directly instead of a snapshot (three closures over one loop variable all see the final value); the loop variable of a top-level for-in/for-each is likewise hoisted to a script-scope global (stage 89-37): its C type is not in the source (it depends on the iterable — int index for Array, char* key for a dynamic object, a boxed value for Dictionary/dynamic objects, the element type for Vector.<T>, with source annotations taking precedence), so collectScriptDecls and emitModuleVars derive it with the same loopVarType rule, and thus trace(k) after the loop reads the final key (matching AIR; measured on AIR the variable indeed survives). Loop variables inside a function body still stay block-local per this subset's function-level hoisting convention |
| AS3 | C |
|---|---|
int |
int |
uint |
unsigned int |
Number |
double |
int64 |
int64_t (opt-in enhancement: the type name does not exist in AIR, taking effect only when written; literal 123L, conversion int64(x)) |
uint64 |
uint64_t (likewise; literal 123UL/123LU, conversion uint64(x)) |
Boolean |
bool |
String |
char* (concatenation/boxing via as_str_* runtime helpers) |
Array |
as_array* (dynamic growth, heterogeneous elements boxed as as_value; element type is dynamic *) |
Vector.<T> |
as_vector_<T>* (monomorphized by element type T: contiguous element array T* data + length/capacity; an out-of-bounds index throws RangeError #1125; compile-time element type enforcement; push/pop/join/indexOf/slice/concat/splice/forEach/map/filter/sort/reverse, read/write .length, parameterized constructor new Vector.<T>(length, fixed), vector literal new <T>[...]). T may be * (stage 98-1: elements are as_value-boxed) and Class/Dictionary (stage 109: Class was already as_class* and Dictionary already had a reflection name, so adding the monomorphization decision ⇒ the wall at away3d's 7 Vector.<Class> call sites is cleared, with element slots as GCT_PTR_ARRAY + write barrier); dynamic access on a */Object receiver (.length read/write, integer-indexed read/write, the 16 methods) is available since stage 98-2 (out-of-bounds/negative index #1125), forwarding to the same monomorphized helpers as the static path. The two call forms differ semantically (stage 109, item-by-item against adl 51.4.1): Vector.<T>(arrayLike) is a conversion (element-by-element converting copy, length read dynamically per the array-like protocol, a scalar/null throwing TypeError #1034, zero arguments throwing ArgumentError #1112, a sealed instance per ReferenceError #1069); new Vector.<T>(length) is a construction (the argument must be uint, otherwise ArgumentError #2005; the fixed formal is accepted but not enforced in this subset) |
Function |
as_fn (a pointer to the closure record { thunk, env }; each function used as a value generates a thunk that unboxes the boxed argument list, calls the typed implementation, and boxes the result back into as_value; closures capturing outer locals additionally generate an environment struct + heap-allocated constructor, with env pointing at that environment) |
Object literal {} |
as_object* (string-keyed associative array, property values boxed as as_value) |
RegExp |
RegExp* ({ vtable; as_regex* compiled; source; flags; lastIndex; global/ignoreCase/multiline/dotall/extended }; compiled is the compiled artifact of the embedded backtracking VM, compiled at construction time, compile errors throw SyntaxError) |
Class Foo |
struct Foo + Foo*; first member is Foo_vtable* (vtable: super chain + method/getter/setter function pointers, a unified flat slot list keeping a subclass vtable layout-compatible with the parent prefix); fields laid out flat (parent-class fields first); methods compiled as Ret Foo_method(void* this, ...), invoked via obj->vtable->method(obj, ...); instance getters/setters are likewise virtual dispatch (obj->vtable->get_prop(obj) / obj->vtable->set_prop(obj, v)), while static accessors and super.property remain direct static calls; classes within namespaces use package-prefixed names (foo.bar.Baz → foo_bar_Baz) |
Interface I |
struct I (reference pair: void* obj + I_vtable* vt); method dispatch via ref.vt->method(ref.obj, ...); each implementing class generates an interface vtable instance Class_I_vt |
Error / exceptions |
struct Error (vtable + message); throw normalized to as_throw(Error*), exception state stored in the global as_exception, jump stack using setjmp/longjmp; TypeError/RangeError/ArgumentError are Error subclasses (same layout, independent vtables), catch (e:Type) uses as_is to match precisely along the super chain |
static members |
class-level fields/methods: file-scope globals Class_field / Class_method(...) (no this receiver) |
trace |
generates printf format strings by type; objects print as class names (as_obj_to_str reads the vtable's name) |
Before implementing new features, consult the authoritative specification to confirm real AS3 semantics, never reverse-engineering from memory or C behavior. The complete tiered list,
red-line comparisons, and decision divergences are in docs/en/as3-semantics.md. Core sources:
- ES4 draft specification (syntax/type system/coercion canon):
http://archives.ecma-international.org/2006/misc/es4lang-Jan06.pdf - AVM2 Overview (object model trait/slot/dispatch semantics, borrowing semantics only, not generating ABC):
http://hackipedia.org/raw/File%20formats/Containers/F4V,%20Flash%20Video/ActionScript%20Virtual%20Machine%202%20(AVM2)%20Overview%20by%20Adobe%20(2007-05).pdf - avmplus source (tie-breaker when the spec is ambiguous):
https://github.com/adobe/avmplus - AS3 language reference (standard library surface):
https://airsdk.dev/reference/actionscript/3.0/ - ECMA-262 3rd edition (source of numeric conversions
ToInt32/NaN/Infinity)
Our product is a readable C file handed to clang -O2, able to link Skia/libcurl/SQLite, benefit from LTO/PGO, interoperate with host C,
and compile to native/WASI/web — so we can add enhancements where AIR does not define / does not support / has no counterpart (enhancements must be opt-in
and never silent). The criterion: the same input that adl runs correctly but we cannot ⇒ a legacy defect; something adl already errors on or an API that does not exist ⇒ an enhancement.
Implemented (6 / 16):
- Display refresh-rate query —
VsyncStateChangeAvailabilityEventgains a read-onlyrefreshRate:Number(AIR has no refresh-rate API at all; the read-onlyavailableis measured to be constantlyfalse) - Multi-format image decoding — WebP / BMP / ICO (
--features formats; default reports#2124just like AIR) - Camera RAW / DNG —
Loader.load("*.dng")yields an image directly (--features raw, native) - SVG runtime decoding —
Loader.load("*.svg")→Bitmap(--features svg, native) - 64-bit integers —
int64/uint64(AIR has no such type name) - LTO / PGO build switches — manifest
lto/pgo, CLI--lto/--pgo
The full 16-item list (the rest being Lottie, native shaders, FFI, server-side rendering…) and the five criteria are in
docs/en/enhancements.md.
examples/hello.as— expressions, string concatenation,trace,if/elseexamples/fib.as— functions,while,for, recursion boundaryexamples/class.as— classes, fields, methods,new,thisexamples/features.as— division/modulo, comparison, logic, increment, string equalityexamples/syntax.as—uint, hexadecimal,do-while,switch, ternary,break/continue,Infinity/NaNexamples/ctor.as— parameterized constructors,newwith argumentsexamples/inherit.as— inheritanceextends, field/method inheritanceexamples/override.as—overridemethod overriding, polymorphic dynamic dispatchexamples/super.as— constructorsuper(...), methodsuper.method(...)examples/visibility.as— access modifierspublic/private/protectedexamples/isas.as—is/astype checks and castsexamples/array.as— array literals/indexing/push/pop/splice/for-in/multidimensional arrays/heterogeneous elementsexamples/object.as— object literals{}(associative arrays), property read/write, nested objectsexamples/any-add.as— dynamic+(one side*): runtime-decided concatenation vs. numeric add,nulloperands, and+=reusing the same path (stage 89-34)examples/any-dot-write.as— dot writes through a dynamically-typed (*) receiver, incl. compound writes: reflectable fields land on the real field, an unknown property on a sealed instance neither crashes nor corrupts, record slots are added, and an array's element buffer is not clobbered (assertion regression for the stage 89-33 crash fix)examples/stdlib.as—Stringmethods,Math, global functions, type conversion, readable objecttraceexamples/oop2.as—const/staticfields and methods/get/setaccessorsexamples/interface.as—interface/implements/interfaceis/as/interface method dispatchexamples/stage4.as— stage four synthesis: interfaces + static members + accessors +final+Object.toString+NumberdefaultNaNexamples/stage5.as— stage five synthesis: default parameters, rest parameters,Functionvalues and anonymous functionsexamples/stage6.as— stage six synthesis:package/importsyntax compatibility layer (namespace flattening)examples/stage7.as— stage seven synthesis:throw/try/catch/finally, built-inError, exception rethrow and nestingexamples/stage8.as— stage eight synthesis: bitwise& | ^ ~ << >> >>>and compound assignment, labeled statementsbreak label/continue labelexamples/stage9.as— stage nine synthesis:ErrorsubclassesTypeError/RangeError/ArgumentErrorprecisecatchand bubbling,Dateclass calendar accessorsexamples/stage12/— stage twelve synthesis (multi-file):package foocross-file class definitions,importthen instantiate and call, same-packageinternalvisibilityexamples/stage10.as— stage ten synthesis:Vector.<T>type-safe arrays (push/pop/indexed read/write/.length) and out-of-boundsRangeErrorexamples/stage11.as— stage eleven synthesis: closures (capturing outer locals, escaping, independent counting, parameterized closures)examples/stage13.as— stage thirteen synthesis:nullboxing semantics (string concatenation/comparison of object null), string arena allocation stress testexamples/stage14.as— stage fourteen synthesis:trace/String(Number)17-digit double shortest representation,Date.getTime()millisecond clockexamples/stage15.as— stage fifteen synthesis: module-levelconst/varvisible to free functions (file-level scope), untyped-variable truncation protection, module-level variable ordered initializationexamples/stage16.as— stage sixteen synthesis:String.replace(string variant, replaces first),Number.toFixed/toExponential/toPrecision(including exponent leading-zero stripping)examples/stage17.as— stage seventeen synthesis:Vector.<T>join/indexOf/.lengthassignment (shrinking)/parameterized constructornew Vector.<int>(3, true)examples/stage18.as— stage eighteen synthesis: primitive-typeis/as(static scalar compile-time evaluation +anyruntime tag determination)examples/stage19.as— stage nineteen synthesis:Array/Vectorjoin optimization (memcpy pointer advancement), intentionally simplified items verified (closure snapshots, array out-of-bounds null)examples/stage21.as— stage twenty-one synthesis:Number/int/uinttoString(radix)(base 2~36),valueOf,MAX_VALUE/MIN_VALUE/NaN/infinity static constantsexamples/stage22.as— stage twenty-two synthesis:Stringconcat/fromCharCode/localeCompare/valueOf/toLocaleLowerCase/toLocaleUpperCase/startsWith/endsWithexamples/stage23.as— stage twenty-three synthesis:Boolean.toString/valueOf,undefinedconstant, URI encoding/decodingencodeURI/decodeURI/encodeURIComponent/decodeURIComponent/escape/unescapeexamples/regexp.as— stages twenty-four~twenty-seven synthesis: regex literals/new RegExp,test/exec/capture groups/lastIndexglobal state machine,String.match/search/replaceregex variants ($1/$&/repl:Function), character classes/quantifiers/backreferences/lookahead,i/m/s/xflags, invalid regex throwingSyntaxErrorexamples/stage28.as— stage twenty-eight synthesis:Array()/Object()/Vector()constructor forms (new Array()/new Object()construction + function-style calls withoutnew,Object(x)with-argument identity/boxing semantics)examples/stage33.as— stage thirty-three synthesis:Event/EventDispatcherthree-phase event dispatch (capture→target→bubble,stopPropagation/stopImmediatePropagation/clone)examples/stage34.as— stage thirty-four synthesis: display listDisplayObject/DisplayObjectContainer/Stage/Sprite(addChild/removeChild/getChildAt/getChildByName/contains/setChildIndex/root/stage)examples/stage35.as— stage thirty-five synthesis: interaction events and hit testing (inside-out dispatch,mouseChildren=falseparent absorption,visible=falseskipping,MouseEvent/KeyboardEvent/FocusEvent)examples/stage36.as— stage thirty-six synthesis: minimal Skia closed loop (oneShape→Stage.renderoff-screen PNG, no-op without Skia)examples/stage37.as— stage thirty-seven synthesis:Shape/Graphics(beginFill/beginGradientFill/drawRect/drawCircle),Bitmap/BitmapData(getPixel/setPixel), nestedSprite+rotation/alpharecursive renderingexamples/stage38.as— stage thirty-eight synthesis:TextField/TextFormat(SkParagraph text rendering,textWidth/textHeight)examples/air-native/— multi-file synthesis (boot.as+src/demo/*.as): Air-style document classMain(Spritesubclass) + implicitsuper()/addedToStageevent-driven +Date/Array/ByteArray/JSONfour demos + stage fifty-eight~sixty-four test cases (Geometry/Events/Timer/FilterDisplay/NetUi/Filesix*Demos.run()assertions +FilterDisplayDemos.visualize()stage filter visualization) +TweenDemo(GreenSockTweenLitetween animation, drivingShapemovement via ENTER_FRAME in window mode), linking Skia to outputair-native.pngexamples/air-starling-demo/— end-to-end Starling 2.x demo (flash.display3Drender pipeline, shaders, events, tweening), running in a windowexamples/window.as— stage thirty-nine synthesis:Stage.showWindowwindowing (Shapeblue rectangle +TextFieldtext → off-screen Skia → SDL2 upload + event loop), usingexamples/window.build.example.jsonto link Skia + arm64 SDL2examples/window_click.as— stage forty: real mouse clicks in the window → AS3 event dispatch (mouseDown/mouseUp/click+ bubbling + click color-change re-render), usingexamples/window_click.build.example.jsonexamples/textflow.as— stage thirty-eight: TextField layout assertions (numLines/maxScrollV/scrollVviewport math,wordWrapwrapping,appendText/textHeight), valid under pure C / Skia buildsexamples/wheel.as— stage forty-five: mouse wheel semantics (1 delta = 1 line,scrollV -= delta, clamped[1,maxScrollV], no dispatch on miss), pure C regressionexamples/frame.as— stage forty-six: ENTER_FRAME broadcast semantics (stage/child/grandchild depth-first dispatch), pure C regressionexamples/stage41.as— stage forty-one synthesis:Stagestage property read-back (stageWidth/stageHeight/color/quality/align/scaleMode/displayState/frameRate/boolean switches) + four constant classes, off-screen (included in regression)examples/stage67.as— stage sixty-seven:DisplayObject.cacheAsBitmapsubtree bitmap caching (default false, get/set readback, inheritance acrossShape/TextField/Stagesubclasses, toggle-invalidation safety), off-screen (included in regression)examples/cacheasbitmap.as—cacheAsBitmaprender verification (offscreen + manifest: a container subtree (rect + circle) rendered to a PNG once withcacheAsBitmapon and once off, byte-identical output; the bake is pixel-identical to direct recursive rendering), requiresexamples/cacheasbitmap.build.jsonto link Skiaexamples/autobake.as— incremental-redraw auto cacheAsBitmap verification (offscreen + manifest: a static subtree rendered across several frames triggers fingerprint-driven auto-baking, f4/f5 pixel-identical to the direct f1; moving the subtree invalidates and re-bakes, moved/f8 identical and distinct from f1), requiresexamples/autobake.build.jsonto link Skia (off-screen, included in regression)examples/bakecrisp.as— bake resolution andBitmapData.drawgeometry behavior (aligned withadl)examples/stage83.as— Stage3D alignment hardening (CubeTexture/RectangleTexture, render-to-texture pixel assertions, instanced drawing)examples/stage86.as—flash.utilsreflection (getQualifiedClassName/getDefinitionByName/dynamic instantiation)examples/stage87.as—Context3Dskeleton method completion (stencil/scissor/buffer upload readback/drawTriangles)examples/stage88.as— AS3 language-feature gap fill (object-literal string keys, logical assignment,for each, namespaces, etc.)examples/stage89.as— AS3 language-feature gaps, second batch (strict equality, generic default parameters,formultiple variable declarations, etc.)examples/stage82.as— Metal end-to-end colored triangle (AGAL→MSL translation +Context3Dfull chain + pixel readback)examples/stage81.as— stage eighty-one:Stage3D+Context3Dskeleton (Stage.stage3Dssingle slot +requestContext3Dlazy creation + synchronousCONTEXT3D_CREATEdispatch +Context3Dstate machine (blend/depth/cull/program/vertex streams/textures/matrix constants) full-path calls + 15 constant class value assertions), off-screen (included in regression)examples/stage80.as— stage eighty: AGAL bytecode kernel (hand-written vertex+fragment AGAL1 bytecode per the official AGALMiniAssembler encoding, asserting the MSL/GLSL translation contains the expected instructions, register mapping and swizzle/write-mask expansion, the full-mask form of the compare instructions (GLSLmix(vec4(0.0), vec4(1.0), vec4(greaterThanEqual(a, b)))/ MSLselect(float4(0.0), float4(1.0), a >= b)) and the partial-mask component-ternary form, and that an invalid opcode throws), off-screen (included in regression)examples/stage79.as— stage seventy-nine: Stage3D geometric prerequisites (Vector3Dfour-component/geometric ops/static axis constants +Matrix3Dcolumn-major matrix multiply/invert/transpose/transform-vector/rotation-scale-translation constructors +decompose/recomposeround-trip assertions), off-screen (included in regression)examples/stage66.as— stage sixty-six:Vector.<T>higher-order/sequence methods (slice/concat/splice/forEach/map/filter/sort/reverse) and thenew <T>[...]literal, off-screen (included in regression)examples/stage65.as— stage sixty-five:flash.system.Capabilitiesenvironment query (version(fixed AIR-compatible"50,0,0,0"),os/cpuArchitectureconditional-compile constants,cpuAddressSize+supports64BitProcesses/supports32BitProcesses, fixed desktop valuesplayerType/manufacturer/isDebugger/touchscreenType/screenColor/pixelAspectRatio/hasAudio, locale-derivedlanguage,screenResolutionX/screenResolutionY/screenDPI), off-screen (included in regression)examples/stage64.as— stage sixty-four:flash.filesystem(Filepath bundle +exists/isDirectory/resolvePath/createDirectory/deleteFile/deleteDirectory+ static directoriesapplicationDirectory/userDirectory/documentsDirectory/desktopDirectory,FileStreamopen/openAsync(asyncPROGRESS+COMPLETE)/close/readUTFBytes/writeUTFBytesread/write roundtrip + read-onlyposition/bytesAvailable,FileModeconstants), off-screen (included in regression)examples/stage63.as— stage sixty-three:flash.net/flash.ui(URLRequestvalue bundle + defaultmethod,URLLoaderasynchronous local file read (pumped viatickTimers()) success/failure dispatchingCOMPLETE/IO_ERROR,URLVariablesdynamic properties +toString()serialization,Keyboardkey-code constant subset +isAccessible,Mousestatichide/show+ read-onlycursor), off-screen (included in regression)examples/http-request-api.as—flash.netsemantic surface (URLRequestproperty defaults,URLRequestDefaults,URLVariables.decode,useRedirectedURL)examples/loader-url.as—Loader.loadsource dispatch and error codes (#2124/#2035, remote via the transport seam)examples/urlloader-contract.as—URLLoadercontract (event order,close(),dataFormat, failure error#2032)examples/urlloader-network-unsupported.as— remote URL honest failure under a backendless buildexamples/stage62.as— stage sixty-two:flash.displaycompletion (MovieClipframe timelineplay/stop/gotoAndPlay/gotoAndStop+ wrapping,SimpleButtonfour states,Loader/LoaderInfoasyncload(INIT sync/COMPLETE next frame) + constants), off-screen (included in regression)examples/stage61.as— stage sixty-one:flash.filtersfilters (BitmapFilter/BlurFilter/DropShadowFilter/GlowFilterdefaults/custom construction/clone/ischecks,BitmapFilterQualityconstants,DisplayObject.filtersread/write roundtrip,BitmapData.applyFilterbox blur and deferred rasterization throwing), off-screen (included in regression)examples/stage60.as— stage sixty:flash.utils.Timer(repeated triggering/TIMER_COMPLETE/stop/reset/delaysetter validation +repeatCountint truncation), off-screen (included in regression)examples/stage59.as— stage fifty-nine:flash.eventsevent subclasses (TimerEvent/ProgressEvent/ErrorEvent/IOErrorEvent/DataEvent+ fullEventconstant assertions), off-screen (included in regression)examples/stage58.as— stage fifty-eight:flash.geom2D geometry (Point/Rectangle/Matrix/ColorTransform/Transformassertions +DisplayObject.transformmatrix read-back and render wiring), off-screen (included in regression)examples/stage57.as— stage fifty-seven synthesis: precise GC leak assertion (after 50000 temporaryrecord+arrayobjects,System.gc()reclaims to zero,totalMemorydoes not grow linearly), off-screen (included in regression)examples/gc_strings.as— stage fifty-seven GC-2: strings in the GC heap leak assertion (after 100000 string concatenations,System.gc()reclaims to zero), off-screen (included in regression)examples/gc_incremental.as— stage fifty-seven GC-4: incremental marking bounded-memory assertion (800 frames each allocating temporaries,gc_step()reclaims slices per frame, heap does not grow linearly with frame count), off-screen (included in regression)examples/gc_barrier.as— stage fifty-seven GC-4: write-barrier stress assertion (200 class-instance chains, 10200 nodes after direct field writes, object graph complete with no dangling afterSystem.gc()), off-screen (included in regression)examples/gc_midframe.as— mid-frameSystem.gc()assertion (guarding the conservative stack-scan root source)examples/closure_const.as— assertion that a typedconstcaptured by a closure behaves correctlyexamples/sharedobject.as—flash.net.SharedObjectlocal storage (read/write/flush/disk readback/size/clear)examples/gc_alloc_threshold.as— automatic reclamation at the allocation threshold for non-GUI targetsexamples/gc_bytes.as—ByteArray/BitmapDatabyte buffers included in the GCexamples/loop-once.as— loop conditions andforupdates evaluated only once per iterationexamples/field-shadow.as— same-named field shadowing each occupying its own C slot + container-element increment + branch-local side effects not leaking outexamples/null-eq.as— null has only one representation (==/!=/===/!==/typeof/truthiness/is Object)examples/async-io.as— async IO contract (dataisnullbefore the terminal event,PROGRESSbeforeCOMPLETE, concurrent requests)examples/socket.as—flash.netsocket layer (Socket/ServerSocket/XMLSocketloopback assertions)examples/proxy.as—flash.utils.Proxy/flash_proxyinterceptionexamples/with.as— thewithstatementexamples/stage94c.as—getQualifiedSuperclassName+setInterval/clearIntervalexamples/stage94d.as—ByteArraymultibyte/boolean/indexed read-write/AMF3 object read-writeexamples/stage94e.as— Batch A legacy hard-problem push (as Object, Function-value member calls, reflection, AMF3 traits)examples/stage94f.as—DisplayObjectgeometry and transform-aware hit testingexamples/stage94g.as— editableTextFieldmodel and defaultsexamples/stage94h.as—TextField.border/borderColorand the 24-bit behavior of color propertiesexamples/stage94i.as—DisplayObjectgeometry/coordinate API family (getRect/getBounds/localToGlobal/hitTestObject)examples/stage94j.as— dispatch semantics ofMouseEvent.DOUBLE_CLICKexamples/stage94k.as—#1009/#1006semantics for member access on an empty receiverexamples/stage94l.as—Tab/Shift+Tabfocus traversalexamples/stage94m.as—TextField.restrictand multilineReturnexamples/stage94n.as— visual-line navigation (PageUp/PageDown+ soft-wrapUp/Down)examples/stage94o.as—displayAsPasswordmaskingexamples/stage94p.as— IME composing state (marked text) previewexamples/stage94q.as— escape decoding in string literalsexamples/stage94r.as— theTextFieldborder is a 1px screen-space line (not polluting local metrics)examples/stage94s.as—hitTestPoint'struebranch is the content area (not the bounding box)examples/stage94t.as—int64/uint6464-bit integers (enhancement, seedocs/en/enhancements.md§4.7)examples/textline.as—TextField.getLineMetrics/TextLineMetricsexamples/bytearray-index.as— the full semantics of theByteArrayindex formexamples/vector-coerce.as—Vector.<T>(arrayLike)is a conversion,new Vector.<T>(length)is a constructionexamples/vector-dynamic.as—Vector.<*>and dynamic access on a*receiverexamples/embed.as—[Embed]assets (image/byte/audio →Bitmap/ByteArray/Soundsubclasses)examples/strcoerce.as— implicit object → string coercionexamples/flash-net-layered.build.example.json— a minimal example of per-target manifest layering (not a.as; for build-layer assertions)examples/builtin-class.as— the built-in class registry & Class values +in/delete/indexingexamples/wasm-native/—--target wasm(WASI) products, including the--exportand[WasmExport]export surfacesexamples/vsyncevent.as—VsyncStateChangeAvailabilityEvent(the AIR surface + therefreshRateenhancement surface) andStage.vsyncEnabled
Each example, after compilation, leaves a same-named .c file beside it so you can directly read the compiler-generated C code.
This project stands on the shoulders of the following projects and third-party libraries, for which we are grateful:
- TypePHP (official site) — an AOT compiler that compiles PHP into native binaries. The overall approach of "the compiler frontend only translates, delegating optimization and machine-code generation to mature C compilers — no reinventing the wheel", along with the build manifest (
project.yml) and multi-target backend design, is borrowed from it. - Ruffle (official site) — the open-source Rust reimplementation of Adobe Flash. Its
flash.events.*event flow,flash.display.*display list, and interactive hit-testing implementations for AS3/AVM2 are the authoritative reference for this project's GUI/event-system semantics. - Skia (official site) — the 2D graphics library and rendering backend. This project links Skia for rasterization via the C++ glue layer (using the Aseprite precompiled static libraries m124).
- SDL (official site) — the cross-platform window/input library, the windowed backend (
Stage.showWindow) uploads and runs its event loop via SDL2. - miniaudio (v0.11.25) — a single-header audio library, the audio backend for
flash.media(its own translation unitvendor/audio_glue.c;Sound/SoundChannel/SoundMixer/SoundTransformetc. are driven by it onto Core Audio; on the web it lands on Web Audio, and WASI honestly reports the absence of a backend). - libcurl (official site) — the HTTP/HTTPS client for
flash.net. The native build links the static archives undervendor/curl/(curl 8.11.1 + nghttp2 1.64.0 for HTTP/2 + zlib 1.3.1 to decode gzip/deflate, with TLS via the system SecureTransport), leaving nolibcurl.4.dylibdynamic dependency in the product. - Emscripten (official site, 3.1.44) — the toolchain for
--target wasm --package webbrowser products (build-tools/emsdk), compiling and linking the wasm Skia viaemcc; the web audio backend also uses its bundledemscripten/webaudio.h(AudioWorklet). - HarfBuzz — the text-shaping engine pulled in with Skia's precompiled static libraries, performing the
flash.textTextField/TextFormatlayout (SkParagraph + HarfBuzz shaping + UAX#14 line breaking).