Skip to content

Latest commit

 

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

A native AOT compiler for ActionScript 3

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.


🎬 Video

as3compiler-aot-promo.mp4

Compilation pipeline

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.

Running

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/zh-cn/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.json

Build & Linking

Full 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

# 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

Build manifest (JSON) fields: target (native/wasm), package (raw/xcode-project/android-project/web, default raw, see compile.md §6), c-compiler, opt, sources (additional C/C++ sources), include-paths, link-libs, link-paths, defines, objects (precompiled .o), exports (C symbols exported to the .wasm export table), 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.

Supported language subset

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 { ... }, regex literal /pattern/flags, Vector literal new <T>[...]
Expressions arithmetic + - * / %, comparison == != < <= > >=, logical && || !, unary - + ! ~, bitwise & | ^ << >> >>>, increment/decrement ++ -- (prefix/postfix), ternary ?:, type checks is / casts as, coercion syntax Type(expr), typeof, delete, membership in, array indexing a[i], property access o.x, compound assignment += -= *= /= %= <<= >>= >>>= &= |= ^=
Statements variable declarations (including multiple var a:T, b:T, c:*;), expression statements, if/else, while, do-while, for, for-in (array indices / dynamic object keys / Dictionary object-reference keys), for-each-in, switch/case/default, break, continue, labeled statements label: while(...){ break label; }, return, throw, try/catch/finally, blocks; arguments inside functions (a formal-parameter array generated on demand)
Functions free functions with typed parameters and return types; optional and default parameters function f(a:int, b:int = 1); 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) + access modifiers public/private/protected/internal + static fields/methods + const constants + 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)
Interfaces interface declaration + implements (interface vtable dispatch) + is/as interface type checks
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)
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), .message property; built-in error subclasses TypeError/RangeError/ArgumentError/SyntaxError; 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 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)); flash.display display list DisplayObject/InteractiveObject/DisplayObjectContainer/Stage/Sprite/Shape/Graphics/Bitmap/BitmapData (layering/coordinates/addChild (triggering addedToStage)/removeChild/hit testing/recursive rendering, as_render_object dispatching by is subtype chain, user Sprite subclasses may act as containers, cacheAsBitmap subtree bitmap caching (toggle invalidates and re-bakes); Stage stage properties stageWidth/stageHeight/fullScreenWidth/fullScreenHeight/displayState/quality/color/align/scaleMode/frameRate and boolean switches, constant classes StageAlign/StageScaleMode/StageQuality/StageDisplayState); 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); 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); 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 eighty-one, flash.display3D pure CPU state machine + resource classes, GPU upload/draw lands in stage eighty-two): 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/setProgram/setVertexBufferAt/setTextureAt/setProgramConstantsFromMatrix (transposed=false uploads the column-major matrix transposed to row-major, true copies verbatim)/createVertexBuffer/createIndexBuffer/createProgram/createTexture/drawTriangles/clear/present (the latter three record state only for now) + read-only driverInfo/profile; resource classes VertexBuffer3D/IndexBuffer3D (uploadFromVector records a CPU copy)/Program3D (upload records bytecode)/Texture/TextureBase (uploadFromBitmapData); 15 constant classes Context3DBlendFactor/Context3DBufferUsage/Context3DClearMask/Context3DCompareMode/Context3DFillMode/Context3DMipFilter/Context3DProfile/Context3DProgramType/Context3DRenderMode/Context3DStencilAction/Context3DTextureFilter/Context3DTextureFormat/Context3DTriangleFace/Context3DVertexBufferFormat/Context3DWrapMode); 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))/LoaderInfo (bytesLoaded/bytesTotal/url + COMPLETE/INIT/OPEN/UNLOAD/PROGRESS/IO_ERROR constants); flash.net/flash.ui (stage sixty-three) URLRequest (url/method/data/contentType, method defaults to "GET")/URLLoader (data/dataFormat + load(request)/close, inheriting EventDispatcher, asynchronously (next frame tick) reading local files, dispatching COMPLETE on success and IO_ERROR on failure, data a GC-tracked string)/URLVariables (dynamic class, undeclared string-keyed properties stored in a runtime slot table, toString() serializes to a query string)/Keyboard (static key-code constant table AZ/NUMBER_0NUMBER_9/SPACE/ENTER/TAB/ESCAPE/BACKSPACE/DELETE/SHIFT/CONTROL/LEFT/RIGHT/UP/DOWN + read-only isAccessible)/Mouse (static hide/show + read-only cursor/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)
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.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 so Starling's version gate passes; the AS-AOT marker is 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, equivalent to new; 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
Semantics / 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; 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/?:/&&/`

Type → C mapping

AS3 C
int int
uint unsigned int
Number double
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; out-of-bounds index throw RangeError; 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>[...])
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 function pointers), fields laid out flat (parent-class fields first); methods compiled as Ret Foo_method(void* this, ...), invoked via obj->vtable->method(obj, ...); 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)

Semantic authority sources

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)

Current limitations (v0.3.92, deliberately minimal)

  • Size optimization (stage seventy-eight): non-exported file-scope functions in the generated C (built-in class method bodies / thunks / free functions) are uniformly marked static, so clang -O2 builds a call graph from main and dead-strips unreachable built-ins (hello.as binary 165 KB → 34 KB); main and [WasmExport] symbols stay global. See docs/zh-cn/size-optimization.md.
  • The regex engine is a self-built ES3 backtracking VM with byte/ASCII semantics (\w/\d/\s cover only ASCII, not UTF-8 Unicode-aware); exec/match non-global capture groups that don't participate return undefined.
  • Under the WASI target, Date resolution degrades to second granularity (gettimeofday is POSIX-specific; WASI degrades via time(NULL), see as_now_ms()).
  • Non-constant initializers for static fields (object literals {}, new X(), function values ClassName.method, etc.), because C forbids non-constant static initialization, are executed at main() entry in class declaration order (each evaluated in the declaring class's static context); instance field initializers execute after super() within the constructor.
  • throw is normalized to Error (strings/primitives are wrapped as Error); catch (e:Type) matches precisely along the super chain (TypeError/RangeError/ArgumentError implemented, catchable by the base catch (e:Error)).
  • finally executes on normal completion, thrown exceptions, and on return/break/continue jumping out of a try block early (stage seventy-seven completes the early-exit stack unwind + finally re-run; try { return } finally {...} no longer skips finally).
  • Uncaught exceptions print to stderr and exit with a non-zero code.
  • Bitwise operations convert operands to 32-bit integers (int/uint), shift counts are taken modulo 32 per AS3 semantics; >> performs arithmetic right shift on signed integers.
  • Memory management (stage fifty-seven, precise GC): the runtime implements a self-built Mark-Sweep garbage collector — gc_alloc uses a segmented heap (1 MiB per segment) + free-list (first-fit + splitting + coalescing) + a gc_header three-color bitfield before each object; the trigger point is the Stage_dispatchFrame frame-boundary safe point (all frame callbacks have returned, leaving only permanent roots in the root set, so no shadow stack is needed), calling gc_step() to advance incrementally, plus System.gc() to manually trigger gc_collect() for a stop-the-world collection. Root set = permanent roots (static fields, module variables, ASC_win_stage, ENTER_FRAME event registry, timers) + built-in root (as_exception). Marking dispatches gc_scan by object type (array/object/Dictionary/closure/class instance/string), class instances mark via vtable→props reflection table along the super chain (tags 3/6/7 dispatch gc_mark_ptr/gc_mark_value); unreachable objects sweep back into the free-list for reuse. GC-2 has moved strings (concatenation, conversion, split/substring, etc.) into the GC heap (as_str_alloc), completely eliminating string arena leaks; only byte buffers (ByteArray grow/compress/uncompress, BitmapData.pixels) still use arena/malloc; closure env and as_vector have moved into the GC heap (GCT_CUSTOM callback marking + GCT_PTR_ARRAY buffers for reference elements). GC-4 introduces incremental marking: a three-color state machine + explicit gray stack + Dijkstra write barriers (gc_write_barrier/gc_write_barrier_value), with gc_step() amortizing a fixed budget of mark/sweep slices per frame (gc_inc.budget, default 500), reducing single-frame pause from O(heap) to O(budget), eliminating the "heap growth → long freeze" linear degradation; new objects during marking start directly BLACK (gc_new side list, restored after sweep), write barriers injected into setters (as_array_set/as_object_set/as_dict_set/as_dyn_set), runtime constructors, and user direct field writes (o.field = v). GC-3 has completed WASI dual-target verification: under --target wasm (wasm32-wasip1), the four assertion examples of GC-1/2/4 (stage57/gc_strings/gc_incremental/gc_barrier) behave identically to native — reclamation back to zero, zero dangling in the object graph, bounded incremental-marking memory (as of stage 89-31 this acceptance could only be reproduced with examples whose exception machinery -O2 eliminates: once setjmp/longjmp lands on a reachable path the sysroot lacks __wasm_setjmp/__c_longjmp and the link fails; fixed in stage 89-35 — the wasm target now links libsetjmp.a and lowers to the standard EH proposal (try_table), and all four examples were re-run with results matching native; cross-target acceptance script temp/wasi-eh/check.sh, see docs/en/compile.md §2). System.totalMemory/freeMemory count GC heap used/total, and after System.gc() returns totalMemory falls back to baseline (examples examples/gc_strings.as: 100000 string concatenations reclaimed to zero; examples/gc_incremental.as: 800 frames of incremental reclamation with bounded memory; examples/gc_barrier.as: 10200 class-instance direct field writes, object chain complete with no dangling after System.gc()); privateMemory is the real process RSS, comparable against Activity Monitor.
  • flash.system.Capabilities is a static read-only subset: version returns an AIR-compatible "<platform-prefix> 50,0,0,0" (so Starling's SystemUtil can parse it), manufacturer is "AS-AOT" (not Adobe), isDebugger is always false, and screenResolutionX/screenResolutionY/screenDPI report 0/72 in headless builds (no SDL2 window backend). Deferred (platform/backend dependent): languages, serverString, hasMP3/hasTLS/hasVideoEncoder/hasEmbeddedVideo/hasStreamingAudio/hasStreamingVideo, maxLevelIDC, hasAccessibility/hasIME/avHardwareDisable/localFileReadDisable/isEmbeddedInAcrobat, and hasMultiChannelAudio(type).
  • Namespaces are implemented as C identifier prefixes (foo.bar.Baz → foo_bar_Baz), classes in package and import resolve across files; short-name → fully-qualified-name uses a global unique mapping, same-name classes in different packages are not yet supported (later definitions override earlier ones); dynamic/abstract and other class modifiers are parsed and ignored.
  • Closures use snapshot capture: anonymous functions capture the current values of outer locals into a heap-allocated environment (surviving after the closure escapes), but modifications to captured variables inside the closure do not affect the outer local (full reference semantics/hoisting deferred). Multi-level nested closure capture is not yet supported.
  • Function values (Function) are invoked under the unified as_fn convention; when a function used as a value has default or rest parameters, the full argument list must be passed at the call site (defaults/rest are only filled at the call site for directly typed calls).
  • Array elements are dynamic types (as_value boxing); multidimensional arrays are naturally supported via nested as_v_obj pointers, but out-of-bounds reads return null (AS3 should be undefined; this subset approximates with null).
  • Object literals {} are a simplified string-keyed associative array (not landing on the Object root class's dynamic properties, still mapped to as_object*); o.x reading a nonexistent key returns null.
  • new Object() / Object() and object literals {} map identically to as_object* (record dynamic associative arrays), not vtable class instances. Object(x) with an argument returns the original value identically for object types, boxing primitives (int/uint/Number/Boolean/String) as as_value returning any — this project does not model Number/String/Boolean wrapper classes, so they cannot masquerade as Object pointers.
  • is/as on interface types use compile-time static type checks (based on the implementing class's implements list), not crossing the runtime dynamic-dispatch boundary.
  • is/as on primitive types (int/uint/Number/Boolean/String) are supported: static scalars are evaluated at compile time; * (any boxing) is determined at runtime by the as_value tag (but int/uint/Number are not distinguished when boxed, all mapping to the number tag, as noted in the README).
  • switch with an int/uint discriminant generates a native C switch (supporting fall-through and break); other types (such as String) degrade to an if/else strict-equality chain (no fall-through). case values must be constants.
  • var x; (no type, no initializer) defaults to int; a variable's type never changes once inferred. Assigning reference types (String/objects/arrays etc.) implicitly to int/uint/Number/Boolean scalars throws a CodegenError (no longer silently truncating to garbage); explicit int(x)/Number(x) etc. conversion functions must still be called explicitly.
  • The rendering backend (Shape/Graphics/Bitmap/TextField real rasterization) links Skia via the C++ glue layer vendor/skia_glue.cc (extern "C" bridging the generated C). Skia is a C++17 library with no C API; this project uses Aseprite precompiled static libraries (m124) located at vendor/skia/ (include/ + lib/, with all transitive dependencies), enabled via the manifest defines: ["ASC_USE_SKIA=1"] (see docs/en/skia.md §6.1); without Skia linked, Stage.render() degrades to a no-op, TextField.textWidth returns 0 (pure C stub), and pure-C paths like BitmapData.getPixel/setPixel work as usual. The first backend is off-screen CPU raster (→ PNG); beginGradientFill currently supports only two-color linear gradients, with multi-stop/matrix/multiple beginFill groups as later sub-stages (see docs/en/skia.md). TextField now supports full layout (SkParagraph, modules/skparagraph): HarfBuzz shaping + UAX#14 breaking + real line height (getHeight()/getLongestLine()), with wordWrap/multiline/scrollV/maxScrollV/numLines/textWidth/textHeight all present, the layout cached on the object (re-laid out only when text/width/style change, auto-relaying out on width change); DisplayObject.cacheAsBitmap bakes the subtree to an off-screen surface and snapshots it into an SkImage drawn once per frame (no more per-object recursive draw commands), toggle invalidates and re-bakes, pixel-identical to direct recursive rendering; incremental redraw (stage sixty-eight, auto cacheAsBitmap): since DisplayObject x/y/rotation/scaleX/scaleY/alpha/visible are plain fields written directly (no setter), a push dirty flag is not used — instead the render entry recomputes a recursive content fingerprint of the subtree each frame (own transform + type-specific content SkPath generation id / Bitmap image / TextField text+format + filter fields + recursive child fingerprints), and a subtree whose fingerprint is unchanged for ASC_AUTO_BAKE_FRAMES (=3) consecutive frames is considered static and auto-baked into _cache_image (reusing the cacheAsBitmap path), so subsequent frames emit a single drawImage instead of re-walking the subtree; the instant the fingerprint moves the bake is dropped and re-baked. The fingerprint is pure integer/pointer FNV-1a mixing, far cheaper than Skia command generation — a render-side dirty detection rather than setter interception, matching cacheAsBitmap semantics (intra-subtree changes are not tracked until the fingerprint differs); clip clipping + viewport scrolling (scrollV = maxScrollV pins to bottom). autoSize/hscroll/selectable/HTML text and multiple TextFormat range rich text remain unimplemented (example examples/textflow.as). Windowed backend: Stage.showWindow(w, h, title) uploads off-screen surface pixels through vendor/window_glue.cc onto an SDL2 window and enters the event loop (defines: ["ASC_USE_WINDOW=1"], linking arm64 libSDL2.a, see docs/en/compile.md §6); mouse input is bridged back to the AS3 event system through function-pointer callbacks (Stage_dispatchMouse hit test + bubble + re-render after events, example examples/window_click.as); without ASC_USE_WINDOW defined, showWindow degrades to a no-op. Retina high-DPI: defines: ["ASC_DISPLAY_HIGH=1"] enables SDL ALLOW_HIGHDPI and creates a physical-pixel surface at the device scale factor (Stage.contentsScaleFactor returns the measured factor), otherwise the surface is stretched 2x by the compositor causing blurry text; when the window is dragged onto a monitor with a different scale, the glue layer reads authoritative physical pixels via SDL_GetWindowSizeInPixels, computes the factor from physical/logical size, and passes it down to the resize callback to rebuild the surface, skipping the resize when any dimension is ≤0 (otherwise the content enlarges/crops or the stage size zeroes). Frame rate follows the display refresh (VSync): SDL_RenderSetVSync enables vertical sync, so SDL_RenderPresent blocks to the display refresh cycle and yields uniform frame intervals — a 50 Hz external display presents 50 fps, a 120 Hz main display 120 fps, with delta-time keeping animation speed correct; this is the only physically correct way to look smooth on a 50 Hz panel (chasing 120 fps with VSync off produces 120-vs-50 beat jitter and stutter). Window scaling control and mouse wheel (stage forty-five): scaleMode/align map to real canvas transforms (noScale keeps content unscaled, showAll/noBorder uniform scaling, exactFit non-uniform; align's eight values distribute remaining space), window resize rebuilds the physical-pixel surface and re-renders via SDL_WINDOWEVENT_SIZE_CHANGED (no more stretching distortion), under noScale stageWidth/Height follow the real window size and dispatch Event.RESIZE; mouse wheel via SDL_MOUSEWHEEL → Stage_dispatchWheel auto-scrolls TextFields (adl-verified 1 delta = 1 line, scrollV -= delta, clamped to [1,maxScrollV]) and dispatches bubbling MouseEvent.MOUSE_WHEEL (example examples/wheel.as). Frame loop and ENTER_FRAME (stage forty-six): the event loop calls on_frame → Stage_dispatchFrame each frame, broadcasting Event.ENTER_FRAME (non-bubbling, dispatched to every registered listener via the global registry — including objects not on the display list, such as GreenSock's private static Shape driving tweens; the event object is reused across objects with zero allocation), driving FPS counters and other frame-driven logic (example examples/frame.as).
  • Stage stage properties (stage forty-one) map to field storage + getters/setters: stageWidth/stageHeight/fullScreenWidth/fullScreenHeight/displayState/quality/color/align/scaleMode/frameRate and boolean switches are readable/writable/read-back; color actually maps to the window background color (sk_canvas_clear), displayState = FULL_SCREEN goes full-screen via SDL_SetWindowFullscreen when showWindow, fullScreenWidth/Height return the display resolution via SDL_GetCurrentDisplayMode (logical points on macOS). frameRate now maps to event-loop frame pacing (stage forty-seven): after setting, the frame loop drives ENTER_FRAME every 1000/frameRate ms, capped to the current display's refresh rate when the explicit rate is higher (avoiding 120-vs-50 beat jitter); when unset (<=0) it reads the display refresh rate (vsync cadence), falling back to 120Hz if unreadable; quality's actual anti-aliasing effect remains deferred; align/scaleMode and window resize are implemented (stage forty-five, see below).
  • The --air-app <app.xml> command (stages forty-two~forty-four) parses the AIR application descriptor (src/air-app.ts hand-written minimal XML parser), auto-generating bootstrap code + a build manifest (recursively scanning src/**/*.as + linking Skia/SDL2), combined with --main-class (default inferred by scanning src/**/Main.as) to compile and link a windowed executable in one command; aligns with adl behavior: <resizable>false</resizable> → ASC_WINDOW_FIXED=1 fixed-size window, <requestedDisplayResolution>high</requestedDisplayResolution> → ASC_DISPLAY_HIGH=1 Retina native-resolution rendering, bootstrap code presets stageWidth/stageHeight before new Main() (so trace in the constructor returns window dimensions rather than 0 0).
  • In flash.geom (stage fifty-eight), Point/Rectangle/Matrix/ColorTransform are pure double field bundles (no GC pointers); Transform holds Matrix/ColorTransform references and participates in GC marking. Transform is wired to DisplayObject.transform (stage sixty-seven): when rendering a display object, sk_canvas_concat applies transform.matrix to the Skia canvas transform (a/c/tx/b/d/ty mapped to SkMatrix; concat is safe even for identity); AS3 identifiers that collide with C keywords or libc/libm/POSIX symbols (class/method/field/local/parameter names) get a leading _ prefixed in C identifiers — incrementally until collision-free (union → _union, index → _index) — while the AS3-side spelling is preserved unchanged in the runtime reflection tables (see docs/zh-cn/c-naming.md, stage seventy-six).
  • In flash.filters (stage sixty-one), filter classes are pure field bundles (BitmapFilter abstract base class + BlurFilter/DropShadowFilter/GlowFilter + BitmapFilterQuality constant class), clone() returns a new instance copying all fields; the alpha field is quantized to 8-bit fixed-point per real AIR as floor(alpha*255)/255 (0.75→191/255≈0.74902); DisplayObject.filters is an ordinary Array for read/write, and the render() backend now applies BlurFilter/DropShadowFilter/outer-glow GlowFilter to the display-object subtree via Skia image filters (blur/shadow/glow genuinely change on-screen pixels, sigma ≈ blurX/3 approximating box-blur diameter; advanced parameters such as inner/knockout/hideObject are not yet distinguished); BitmapData.applyFilter implements only BlurFilter via separable box blur (quality controls box-blur rounds), while DropShadowFilter/GlowFilter applyFilter rasterization remains deferred (calling throws Error).
  • flash.display completion (stage sixty-two) is a minimal model with no timeline symbols and no async loading: MovieClip's totalFrames is read-only in AIR (determined by the symbol timeline), but this subset has no symbol system, so it is modeled as a writable field (default 1, setter validates ≥1) to simulate multi-frame timelines; currentFrame defaults to 0 (real AIR's frameless MovieClip semantics), frame advancement is driven by the as_mc_* frame pool (off-screen uses tickMovieClips() to pump manually, windowed builds are frame-loop driven), wrapping to 1 after totalFrames (looping timeline). SimpleButton only models the four state references (upState/overState/downState/hitTestState), with mouse hover/press visual state switching and hit-test redirection still deferred (itself hit as a leaf by bounds). Loader.load(url) records the URL to contentLoaderInfo, dispatching INIT synchronously and COMPLETE asynchronously (as_set_timeout(0) next frame tick, the same contract as URLLoader, so listeners registered after load() still receive events); Loader.content remains null (image decoding not implemented).
  • flash.net/flash.ui (stage sixty-three) is a minimal model with no network/audio/video backends: URLLoader.load(request) treats the URL as a local filesystem path for whole-file reads (POSIX fopen/fread, also usable under WASI), but dispatches COMPLETE/IO_ERROR asynchronously (as_set_timeout(0) next frame tick, so listeners registered after load() still receive events); data holds the file text (as_str_alloc GC-tracked string, no longer malloc); real async HTTP/Socket loading is not implemented. URLVariables is implemented as a dynamic class (undeclared string-keyed properties stored in a runtime slot table, toString() serializes to a query string); Socket, Sound/SoundChannel/Video, ContextMenu are all deferred (depending on network sockets, audio/video decoding, and native menu backends respectively). Keyboard maps only the key-code constant subset (letters/digits/common control keys/arrow keys); Mouse.cursor is read-only ("auto", the real AIR default), the SDL cursor API is not wired, and hide/show only toggle an internal visibility flag.
  • flash.filesystem (stage sixty-four) is a POSIX file IO minimal model: File treats the path as an ordinary string bundle (nativePath + read-only url = file:// prefix), exists/isDirectory are read-only properties (getters, f.exists rather than f.exists()) via stat(), createDirectory via mkdir (supports recursive creation), deleteFile/deleteDirectory via remove() (empty directory); the static read-only applicationStorageDirectory returns the application's writable per-app storage directory (~/Library/Application Support/as3aot/Local Store on macOS, ~/.as3aot elsewhere, created on demand — a bundled .app launches with a non-writable CWD so . is not a safe write target), applicationDirectory (mapped to CWD "."), desktopDirectory/documentsDirectory/userDirectory (getenv("HOME") joined with Desktop/Documents/bare HOME, resolved at runtime). FileStream holds a FILE* handle (opaque void* field, not GC-tracked), open maps AIR FileMode (read/write/append/update) to C fopen modes, openAsync dispatches ProgressEvent.PROGRESS (bytesLoaded=bytesTotal=file size) + Event.COMPLETE asynchronously on top of the same open; readUTFBytes returns an as_str_alloc GC-tracked buffer (no longer malloc). position/bytesAvailable are read-only (ftell/fseek). NativeWindow/Window (native multi-window) and SQLConnection/SQLStatement (SQLite linking) are all deferred.
  • Browser target (--target wasm --package web, stage seventy-one): uses Emscripten to compile Skia + the C++ glue layer into browser artifacts (.wasm + .js + index.html), running CPU rasterization + emscripten_set_main_loop frame loop inside <canvas>; the window layer switches from SDL2 to canvas blit, and the font backend switches from CoreText to SkFontMgr_New_Custom_Data() + runtime fetch injection (font-urls). Known limitations: fonts load from the network at runtime (offline/intranet unavailable), translucent pixels have color drift when blitted to canvas as premultiplied alpha (opaque pixels are exact), and whole-CJK fonts load slowly on first render; see docs/zh-cn/html5-web.md. Stage3D (stage eighty-nine / thirty): a program whose entire rendering goes through Context3D (e.g. examples/air-starling-demo, 142 AS3 sources) renders correctly on web by linking vendor/stage3d_webgl.cc instead (WebGL2/GLES3 + AGAL→GLSL ES, ASC_RENDER_STAGE3D=1 + ASC_S3D_GLSL=1) — the s3d_* signatures are identical to the native Metal backend, so the generated C needs no change (the GL-side Y-flip, BGRA swap, per-register uniform vec4 vcN/fcN, DEPTH24_STENCIL8 offscreen FBOs and the single WebGL2 context shared with Ganesh are all handled inside the glue); all 12 scenes were entered one by one and match the AIR adl reference (120 fps / 0 discards / 2.88 ms per frame). Stage3D presentation is now GPU-direct (stage eighty-nine / thirty-one): sk_gl_draw_texture wraps the render target's GL texture into a GrBackendTexture and draws it straight into the canvas (the counterpart of Metal's as_skia_mtl_draw_texture), dropping renderMs from 3.21 ms to 0.12–0.15 ms per frame (≈23×), pixel-identical to the readback path (readback is kept as the fallback when ASC_RENDER_GPU is undefined); see docs/zh-cn/html5-web.md §3.3/§6. AGAL compare instructions (stage eighty-nine / thirty-two): the full-mask form of sge/slt/seq/sne translates on the GLSL side to mix(vec4(0.0), vec4(1.0), vec4(greaterThanEqual(a, b))) (mix(genType,genType,genType) is a legal ES 1.00 overload and vec4(bvec4) is allowed by §5.4.2; the bvecN overload of mix is ES 3.00-only). Both targets used to share one sprintf with a ternary format string, and since the two formats consume a different number of varargs while the argument list is fixed, GLSL came out as v0(greaterThanEqual(v0, vec4)) — clicking "Switch Filter" in the web Filters scene (Drop Shadow/Glow) failed fragment compilation (native was unaffected because its branch happens to use only the first four arguments). Each target now has its own sprintf, and all four opcodes work on both ends (examples/stage80.as asserts the exact form on each side). Frame driver and pacing (stage eighty-nine / twenty-nine): the web frame is driven by requestAnimationFrame, and one rAF callback is one vsync; the pacer therefore maps the target interval onto whole ticks (skip = round(1000/frameRate / rAF_period), i.e. present a frame every skip-th vsync), with the rAF period measured as the median of consecutive callback intervals (there is no browser analogue of SDL_GetDisplayMode, so sk_window_get_display_refresh() returns 0 on web). The previous timestamp-deadline pacer halved the rate under rAF timestamp jitter (measured on a 120 Hz panel: p50 8.30 / p95 9.30 ms for an 8.333 ms period) — rAF delivered 120 callbacks/s while only 66 fps were presented, discarding 54.5 callbacks/s (this is the reported "web FPS cannot reach 120"). After the fix, the 120 Hz panel measures 120 fps / 0 discards / 0.113 ms per frame (1.35% of the frame budget, GPU rasterization) with the demo's own corner readout showing FPS:120; the same build on CPU rasterization (putImageData) costs 6.66 ms/frame (80% of the budget) — GPU rasterization is necessary (the existing WebGL2/Ganesh backend suffices) while WebGPU offers no gain and is not even built in (the wasm Skia uses skia_use_webgpu=false/skia_enable_graphite=false). The hard ceiling is the refresh rate of the panel the window sits on (rAF follows the window's display; on a 50 Hz external panel no backend can exceed 50 fps). Frame-rate diagnostics on web use the compile-time define -D ASC_FRAME_STATS=1 (compiled out by default), which publishes frames/loopcalls/skips/renderMs/rafPeriodMs/skip to window.__ascFrameStats once a second.

Examples

  • examples/hello.as — expressions, string concatenation, trace, if/else
  • examples/fib.as — functions, while, for, recursion boundary
  • examples/class.as — classes, fields, methods, new, this
  • examples/features.as — division/modulo, comparison, logic, increment, string equality
  • examples/syntax.as — uint, hexadecimal, do-while, switch, ternary, break/continue, Infinity/NaN
  • examples/ctor.as — parameterized constructors, new with arguments
  • examples/inherit.as — inheritance extends, field/method inheritance
  • examples/override.as — override method overriding, polymorphic dynamic dispatch
  • examples/super.as — constructor super(...), method super.method(...)
  • examples/visibility.as — access modifiers public/private/protected
  • examples/isas.as — is / as type checks and casts
  • examples/array.as — array literals/indexing/push/pop/splice/for-in/multidimensional arrays/heterogeneous elements
  • examples/object.as — object literals {} (associative arrays), property read/write, nested objects
  • examples/any-add.as — dynamic + (one side *): runtime-decided concatenation vs. numeric add, null operands, 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 — String methods, Math, global functions, type conversion, readable object trace
  • examples/oop2.as — const/static fields and methods/get/set accessors
  • examples/interface.as — interface/implements/interface is/as/interface method dispatch
  • examples/stage4.as — stage four synthesis: interfaces + static members + accessors + final + Object.toString + Number default NaN
  • examples/stage5.as — stage five synthesis: default parameters, rest parameters, Function values and anonymous functions
  • examples/stage6.as — stage six synthesis: package/import syntax compatibility layer (namespace flattening)
  • examples/stage7.as — stage seven synthesis: throw/try/catch/finally, built-in Error, exception rethrow and nesting
  • examples/stage8.as — stage eight synthesis: bitwise & | ^ ~ << >> >>> and compound assignment, labeled statements break label/continue label
  • examples/stage9.as — stage nine synthesis: Error subclasses TypeError/RangeError/ArgumentError precise catch and bubbling, Date class calendar accessors
  • examples/stage12/ — stage twelve synthesis (multi-file): package foo cross-file class definitions, import then instantiate and call, same-package internal visibility
  • examples/stage10.as — stage ten synthesis: Vector.<T> type-safe arrays (push/pop/indexed read/write/.length) and out-of-bounds RangeError
  • examples/stage11.as — stage eleven synthesis: closures (capturing outer locals, escaping, independent counting, parameterized closures)
  • examples/stage13.as — stage thirteen synthesis: null boxing semantics (string concatenation/comparison of object null), string arena allocation stress test
  • examples/stage14.as — stage fourteen synthesis: trace/String(Number) 17-digit double shortest representation, Date.getTime() millisecond clock
  • examples/stage15.as — stage fifteen synthesis: module-level const/var visible to free functions (file-level scope), untyped-variable truncation protection, module-level variable ordered initialization
  • examples/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/.length assignment (shrinking)/parameterized constructor new Vector.<int>(3, true)
  • examples/stage18.as — stage eighteen synthesis: primitive-type is/as (static scalar compile-time evaluation + any runtime tag determination)
  • examples/stage19.as — stage nineteen synthesis: Array/Vector join optimization (memcpy pointer advancement), intentionally simplified items verified (closure snapshots, array out-of-bounds null)
  • examples/stage21.as — stage twenty-one synthesis: Number/int/uint toString(radix) (base 2~36), valueOf, MAX_VALUE/MIN_VALUE/NaN/infinity static constants
  • examples/stage22.as — stage twenty-two synthesis: String concat/fromCharCode/localeCompare/valueOf/toLocaleLowerCase/toLocaleUpperCase/startsWith/endsWith
  • examples/stage23.as — stage twenty-three synthesis: Boolean.toString/valueOf, undefined constant, URI encoding/decoding encodeURI/decodeURI/encodeURIComponent/decodeURIComponent/escape/unescape
  • examples/regexp.as — stages twenty-four~twenty-seven synthesis: regex literals/new RegExp, test/exec/capture groups/lastIndex global state machine, String.match/search/replace regex variants ($1/$&/repl:Function), character classes/quantifiers/backreferences/lookahead, i/m/s/x flags, invalid regex throwing SyntaxError
  • examples/stage28.as — stage twenty-eight synthesis: Array()/Object()/Vector() constructor forms (new Array()/new Object() construction + function-style calls without new, Object(x) with-argument identity/boxing semantics)
  • examples/stage33.as — stage thirty-three synthesis: Event/EventDispatcher three-phase event dispatch (capture→target→bubble, stopPropagation/stopImmediatePropagation/clone)
  • examples/stage34.as — stage thirty-four synthesis: display list DisplayObject/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=false parent absorption, visible=false skipping, MouseEvent/KeyboardEvent/FocusEvent)
  • examples/stage36.as — stage thirty-six synthesis: minimal Skia closed loop (one Shape → Stage.render off-screen PNG, no-op without Skia)
  • examples/stage37.as — stage thirty-seven synthesis: Shape/Graphics (beginFill/beginGradientFill/drawRect/drawCircle), Bitmap/BitmapData (getPixel/setPixel), nested Sprite + rotation/alpha recursive rendering
  • examples/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 class Main (Sprite subclass) + implicit super()/addedToStage event-driven + Date/Array/ByteArray/JSON four demos + stage fifty-eight~sixty-four test cases (Geometry/Events/Timer/FilterDisplay/NetUi/File six *Demos.run() assertions + FilterDisplayDemos.visualize() stage filter visualization) + TweenDemo (GreenSock TweenLite tween animation, driving Shape movement via ENTER_FRAME in window mode), linking Skia to output air-native.png
  • examples/window.as — stage thirty-nine synthesis: Stage.showWindow windowing (Shape blue rectangle + TextField text → off-screen Skia → SDL2 upload + event loop), using examples/window.build.example.json to link Skia + arm64 SDL2
  • examples/window_click.as — stage forty: real mouse clicks in the window → AS3 event dispatch (mouseDown/mouseUp/click + bubbling + click color-change re-render), using examples/window_click.build.example.json
  • examples/web/hello-web.as — stage seventy-one: browser HTML5 rendering (--target wasm --package web, Shape blue rectangle + TextField text → canvas blit + emscripten_set_main_loop, runtime font injection via font-urls), using examples/web/hello-web.build.json
  • examples/textflow.as — stage thirty-eight: TextField layout assertions (numLines/maxScrollV/scrollV viewport math, wordWrap wrapping, appendText/textHeight), valid under pure C / Skia builds
  • examples/wheel.as — stage forty-five: mouse wheel semantics (1 delta = 1 line, scrollV -= delta, clamped [1,maxScrollV], no dispatch on miss), pure C regression
  • examples/frame.as — stage forty-six: ENTER_FRAME broadcast semantics (stage/child/grandchild depth-first dispatch), pure C regression
  • examples/stage41.as — stage forty-one synthesis: Stage stage 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.cacheAsBitmap subtree bitmap caching (default false, get/set readback, inheritance across Shape/TextField/Stage subclasses, toggle-invalidation safety), off-screen (included in regression)
  • examples/cacheasbitmap.as — cacheAsBitmap render verification (offscreen + manifest: a container subtree (rect + circle) rendered to a PNG once with cacheAsBitmap on and once off, byte-identical output; the bake is pixel-identical to direct recursive rendering), requires examples/cacheasbitmap.build.json to link Skia
  • examples/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), requires examples/autobake.build.json to link Skia (off-screen, included in regression)
  • examples/stage81.as — stage eighty-one: Stage3D + Context3D skeleton (Stage.stage3Ds single slot + requestContext3D lazy creation + synchronous CONTEXT3D_CREATE dispatch + Context3D state 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 (GLSL mix(vec4(0.0), vec4(1.0), vec4(greaterThanEqual(a, b))) / MSL select(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 (Vector3D four-component/geometric ops/static axis constants + Matrix3D column-major matrix multiply/invert/transpose/transform-vector/rotation-scale-translation constructors + decompose/recompose round-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 the new <T>[...] literal, off-screen (included in regression)
  • examples/stage65.as — stage sixty-five: flash.system.Capabilities environment query (version (fixed AIR-compatible "50,0,0,0"), os/cpuArchitecture conditional-compile constants, cpuAddressSize + supports64BitProcesses/supports32BitProcesses, fixed desktop values playerType/manufacturer/isDebugger/touchscreenType/screenColor/pixelAspectRatio/hasAudio, locale-derived language, screenResolutionX/screenResolutionY/screenDPI), off-screen (included in regression)
  • examples/stage64.as — stage sixty-four: flash.filesystem (File path bundle + exists/isDirectory/resolvePath/createDirectory/deleteFile/deleteDirectory + static directories applicationDirectory/userDirectory/documentsDirectory/desktopDirectory, FileStream open/openAsync (async PROGRESS+COMPLETE)/close/readUTFBytes/writeUTFBytes read/write roundtrip + read-only position/bytesAvailable, FileMode constants), off-screen (included in regression)
  • examples/stage63.as — stage sixty-three: flash.net/flash.ui (URLRequest value bundle + default method, URLLoader asynchronous local file read (pumped via tickTimers()) success/failure dispatching COMPLETE/IO_ERROR, URLVariables dynamic properties + toString() serialization, Keyboard key-code constant subset + isAccessible, Mouse static hide/show + read-only cursor), off-screen (included in regression)
  • examples/stage62.as — stage sixty-two: flash.display completion (MovieClip frame timeline play/stop/gotoAndPlay/gotoAndStop + wrapping, SimpleButton four states, Loader/LoaderInfo async load (INIT sync/COMPLETE next frame) + constants), off-screen (included in regression)
  • examples/stage61.as — stage sixty-one: flash.filters filters (BitmapFilter/BlurFilter/DropShadowFilter/GlowFilter defaults/custom construction/clone/is checks, BitmapFilterQuality constants, DisplayObject.filters read/write roundtrip, BitmapData.applyFilter box blur and deferred rasterization throwing), off-screen (included in regression)
  • examples/stage60.as — stage sixty: flash.utils.Timer (repeated triggering/TIMER_COMPLETE/stop/reset/delay setter validation + repeatCount int truncation), off-screen (included in regression)
  • examples/stage59.as — stage fifty-nine: flash.events event subclasses (TimerEvent/ProgressEvent/ErrorEvent/IOErrorEvent/DataEvent + full Event constant assertions), off-screen (included in regression)
  • examples/stage58.as — stage fifty-eight: flash.geom 2D geometry (Point/Rectangle/Matrix/ColorTransform/Transform assertions + DisplayObject.transform matrix read-back and render wiring), off-screen (included in regression)
  • examples/stage57.as — stage fifty-seven synthesis: precise GC leak assertion (after 50000 temporary record+array objects, System.gc() reclaims to zero, totalMemory does 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 after System.gc()), off-screen (included in regression)

Each example, after compilation, leaves a same-named .c file beside it so you can directly read the compiler-generated C code.

Testing

Run node test.ts (or npm test) to compile and run all examples under examples/, reporting pass/fail item by item, exiting with a non-zero code on any failure.

Acknowledgments

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.

About

Compile ActionScript 3 source code ahead-of-time (AOT) into native machine code, producing native executables — while keeping the AS3 syntax you already know.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages