Files
sanspie cf68c71f05 Stage 6: coroutines, signals, ScriptRef, workspace CLI, editor tooling
Language: yield/signal/coroutine become keywords, start is contextual.
Coroutine methods (func Name() coroutine) with start and
yield / wait / frames / fixed / await; signals with Emit/Connect and
Connection handles; ScriptRef<T> and node.GetScript<T>() for fields,
plain methods and signals of other scripts. Diagnostics AES2050-AES2059.

Bytecode v3: ScriptRefs, XFields, XMethods and Connects tables resolved by
name in vm.Link, per-script signals, coroutine flag on functions; new
opcodes START/YIELD/EMIT/CONNECT/DISCONNECT/CONNECTED/COSTOP/CORUN/
SELFREF/GETSCRIPT/GETXF/SETXF/CALLR, checked by verify.

Runtime: script/coroutine scheduler (frames/fixed counters, time heap,
await lists; only due coroutines are visited) and script/signal registry
(snapshot per Emit, deferred removal, no allocations in steady state).
Hot reload cancels coroutines of changed scripts, rebinds connections by
name and signature, then runs Reloaded.

Tooling: script/build removed, aego check/test/dump run on the workspace
(test builds include _test.aes); langsvc completion, hover, signatures and
semantic tokens for the new syntax; editor highlighting, snippets with
placeholders, coroutine/signal profiler graphs and stats.

Docs (script.md sections 16-19, editor.md, ARCHITECTURE), test fixtures and
golden files, platformer enemies wired to the player, scriptreload demo,
and run.Config.FrameTime for deterministic headless fixed steps.

Modules (6.5): engine/module registry and ComponentBuilder are the one
registration path for engine subsystems and native code; render2d,
render3d and script/engine are modules over scene/frame, the script
component moved to scene/scriptcomp, and scene/components and
scene/systems are gone. Editor inspector, templates, picking shapes and
extension hooks are driven by descriptors and editor/builtins; sdk gains
Component/EditorHook; depcheck checks the module graph and the
aego_runtime graph.

Also: stop tracking built tool binaries; gl33 activates the scratch unit
before binding for texture updates.

Verification: typed component method adapters (Method0..2, Proc0..2)
with direct field access and no allocations; app.Build as the single
construction path for run and the editor; modules attach per scene
document; depcheck aego_editor pass; diagnostic codes in one place and
every code covered by a test; indexed coroutine heaps with eager
cancel; dead component references and nil intrinsics fault with
AES6004, host faults keep their code; GC roots of struct fields checked
in verify and vm.Link; 255-field limit; AES4010 for script packages
shadowing host modules; architecture registry D111-D147.

Golden: the harness renders on the thread that owns the GL context,
ReadPixels returns rows in memory order (render targets are top-down per
D17), and the first rendering references are committed.
2026-10-12 14:22:08 +03:00

109 lines
5.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package bytecode defines the Aego Script instruction set, the compiled module
// format, and the encoder/decoder and disassembler over them. It is the shared
// contract between the compiler (which emits it), the verifier (which proves it
// safe before execution), and the VM (which runs it).
//
// # Instruction encoding
//
// Each instruction is a 32-bit word, Lua-style, in one of three formats:
//
// op:8 a:8 b:8 c:8 (FormatABC)
// op:8 a:8 bx:16 (FormatABx)
// op:8 a:8 sbx:16 (FormatAsBx, sbx signed)
//
// opInfo is the single source of truth for each opcode's format and the kind of
// its operands; the disassembler and verifier both read it, so an opcode is
// described in exactly one place.
//
// # Registers and calling convention
//
// A function runs in a window of R[0..Registers). Parameters arrive in R[0..n).
// A call lays its arguments contiguously above the callee-visible frame:
//
// CALL a bx calls function bx with arguments in R[a..a+params) and leaves
// results in R[a..a+results).
// CALLX a bx same, but bx indexes the extern table (another package's
// function, resolved by name at link time, not by index).
// CALLH a bx same, but bx indexes the host-import table.
// RET a b returns b values starting at R[a].
//
// Operand roles that the encoding alone does not make obvious:
//
// GETL a b c a = b's lane c (c is a lane index 0..3 → x,y,z,w)
// SETL a b c a's lane c = scalar b
// GETCF a b c a = field c of component-ref b
// SETCF a b c field c of component-ref b = a
// INDEX a b c a = b[c] (b slice, c index)
// SETINDEX a b c a[b] = c (a slice, b index, c value base)
// MOVEN a b c copy c consecutive slots R[b..b+c) to R[a..a+c) (flat structs)
// VEC2/3/4 a b pack consecutive scalars at R[b..] into the vector in R[a]
//
// # Zero-bit invariant (load-bearing, not checked by the verifier)
//
// vm.Value is two words {A, B}. EQ compares *both* words and EQV compares all
// four lanes, so every instruction and adapter that writes a register must
// leave the unused bits zero — this invariant is what lets one EQ serve every
// one- and two-word scalar/reference type:
//
// - a scalar (int, bool, enum, NodeRef) writes A and must zero B;
// - a float writes the low 32 bits of A and must zero the high 32 bits and B;
// - Vec2 fills lanes x,y and must zero z,w; Vec3 must zero w.
//
// The constructors (LOADI/LOADK, VEC2-4, ITOF/FTOI, LEN, …) satisfy it, and
// GETCF writes whatever Comps.GetField returns — so the bind adapters must build
// their values through those constructors, never by stuffing raw bits. The
// verifier does not police this; a VEC3-then-copy-as-Vec2 that left a stale z
// would make `v == Vec2{1,2}` spuriously false. Keep it in mind for every write.
//
// # Equality
//
// Because the invariant guarantees unused words are zero, one EQ covers every
// value whose identity is its bits:
//
// EQ both words A,B int, bool, enum, NodeRef, AssetRef, ComponentRef
// EQF IEEE float32 the low 32 bits of A
// EQS string contents
// EQV four lanes by IEEE Vec2/Vec3/Color/Rect (Quat is not comparable; q ≡ −q)
//
// AssetRef (a 128-bit id) and ComponentRef (NodeHandle + index) live in both
// words, which EQ already compares; no separate two-word opcode is needed. NE is
// compiled as EQ-family + NOT.
//
// # Slices and nil
//
// The zero value of a slice is nil, which has no element type. APPEND is an ABC
// instruction with no room for a type operand, so it cannot allocate a typed
// backing object on its own. ENSURE r t therefore runs before the first APPEND
// to a possibly-nil slice: it carries the element type (ABx) and replaces a nil
// slice in r with an empty typed object. The alternative — making every slice
// variable and instance field a pre-allocated empty object — costs an
// allocation per slot, so the extra opcode is cheaper.
//
// # Integer arithmetic and faults
//
// Integer arithmetic (ADDI, SUBI, MULI, NEGI) wraps modulo 2^64, like Go.
// DIVI and MODI by zero raise fault AES6006 rather than crashing the host. To
// stay total, MinInt64 / -1 is defined as MinInt64 and MinInt64 % -1 as 0 (Go
// panics on the former; the VM must not). Floating arithmetic is plain IEEE-754
// float32 with no faults.
//
// # Encoding-derived limits
//
// See the Max* constants. MaxRegisters (255) and MaxComponentFields (255) follow
// from uint8 operands; MaxSliceElemSlots (32) from the uint32 TypeDef.Mask;
// MaxJumpOffset (32767) from the int16 jump displacement, which also caps a
// function at that many instructions — the compiler must reject a larger
// function with a source-level "function too large" error, since it cannot emit
// a spanning jump.
//
// # Disassembler
//
// Disassemble / DisassembleSource render a module as text. This is not cosmetic:
// it is how compiler output is golden-tested (so a regression reads as
// "ADDF r3 became ADDI r3" with a source line, not "byte 1432 differs"), how the
// compiler-vs-VM question is answered during development (and by `aego dump`),
// and — via the same Function line table that produces the "; file:line source"
// comments — how runtime faults and a future step debugger map back to source.
// The source callback on DisassembleSource exists for that last use.
package bytecode