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.
473 lines
13 KiB
Go
473 lines
13 KiB
Go
package vm_test
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"aego/script/abi"
|
|
bc "aego/script/bytecode"
|
|
"aego/script/vm"
|
|
)
|
|
|
|
func fn(name string, params, results, regs int, code ...bc.Instr) bc.Function {
|
|
lines := make([]bc.LineEntry, len(code))
|
|
for i := range code {
|
|
lines[i] = bc.LineEntry{PC: uint32(i), Line: uint32(i + 1)}
|
|
}
|
|
return bc.Function{Name: name, File: "test.aes", Params: uint8(params), Results: uint8(results), Registers: uint8(regs), Script: -1, Code: code, Lines: lines}
|
|
}
|
|
|
|
func module(funcs ...bc.Function) *bc.Module {
|
|
m := bc.NewModule("test")
|
|
m.Functions = funcs
|
|
return m
|
|
}
|
|
|
|
func load(t *testing.T, mod *bc.Module, hosts map[string]vm.HostFunc) (*vm.Machine, *vm.Module) {
|
|
t.Helper()
|
|
m := vm.New(vm.Options{Budget: 10_000_000})
|
|
lm, err := m.Load(mod, func(imp bc.HostImport) (vm.HostFunc, bool) {
|
|
h, ok := hosts[imp.Module+"."+imp.Name]
|
|
return h, ok
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return m, lm
|
|
}
|
|
|
|
func run(t *testing.T, mod *bc.Module, name string, args ...vm.Value) ([]vm.Value, *vm.Fault, *vm.Machine) {
|
|
t.Helper()
|
|
m, lm := load(t, mod, nil)
|
|
out, fl := m.Call(lm.Func(name), nil, args)
|
|
return append([]vm.Value(nil), out...), fl, m
|
|
}
|
|
|
|
func want(t *testing.T, out []vm.Value, fl *vm.Fault, v int64) {
|
|
t.Helper()
|
|
if fl != nil {
|
|
t.Fatalf("fault: %v", fl)
|
|
}
|
|
if len(out) != 1 || out[0].Int() != v {
|
|
t.Fatalf("got %v want %d", out, v)
|
|
}
|
|
}
|
|
|
|
func sumFunc() bc.Function {
|
|
return fn("sum", 1, 1, 5,
|
|
bc.AsBx(bc.LOADI, 1, 1),
|
|
bc.AsBx(bc.LOADI, 2, 0),
|
|
bc.AsBx(bc.LOADI, 3, 1),
|
|
bc.ABC(bc.LEI, 4, 1, 0),
|
|
bc.AsBx(bc.JMPNOT, 4, 3),
|
|
bc.ABC(bc.ADDI, 2, 2, 1),
|
|
bc.ABC(bc.ADDI, 1, 1, 3),
|
|
bc.AsBx(bc.JMP, 0, -5),
|
|
bc.ABC(bc.RET, 2, 1, 0),
|
|
)
|
|
}
|
|
|
|
func fibFunc() bc.Function {
|
|
return fn("fib", 1, 1, 5,
|
|
bc.AsBx(bc.LOADI, 1, 2),
|
|
bc.ABC(bc.LTI, 2, 0, 1),
|
|
bc.AsBx(bc.JMPNOT, 2, 1),
|
|
bc.ABC(bc.RET, 0, 1, 0),
|
|
bc.AsBx(bc.LOADI, 1, 1),
|
|
bc.ABC(bc.SUBI, 3, 0, 1),
|
|
bc.ABx(bc.CALL, 3, 0),
|
|
bc.AsBx(bc.LOADI, 1, 2),
|
|
bc.ABC(bc.SUBI, 4, 0, 1),
|
|
bc.ABx(bc.CALL, 4, 0),
|
|
bc.ABC(bc.ADDI, 3, 3, 4),
|
|
bc.ABC(bc.RET, 3, 1, 0),
|
|
)
|
|
}
|
|
|
|
func TestIntegerArithmetic(t *testing.T) {
|
|
f := fn("f", 2, 1, 6,
|
|
bc.ABC(bc.ADDI, 2, 0, 1),
|
|
bc.ABC(bc.SUBI, 3, 0, 1),
|
|
bc.ABC(bc.MULI, 2, 2, 3),
|
|
bc.AsBx(bc.LOADI, 4, 3),
|
|
bc.ABC(bc.DIVI, 2, 2, 4),
|
|
bc.AsBx(bc.LOADI, 5, 5),
|
|
bc.ABC(bc.MODI, 2, 2, 5),
|
|
bc.ABC(bc.RET, 2, 1, 0),
|
|
)
|
|
out, fl, _ := run(t, module(f), "f", vm.Int(10), vm.Int(4))
|
|
want(t, out, fl, 3)
|
|
}
|
|
|
|
func TestFloatsAndConversions(t *testing.T) {
|
|
f := fn("f", 1, 1, 3,
|
|
bc.ABC(bc.ITOF, 1, 0, 0),
|
|
bc.ABx(bc.LOADK, 2, 0),
|
|
bc.ABC(bc.MULF, 1, 1, 2),
|
|
bc.ABx(bc.LOADK, 2, 1),
|
|
bc.ABC(bc.ADDF, 1, 1, 2),
|
|
bc.ABC(bc.RET, 1, 1, 0),
|
|
)
|
|
f.Consts = []bc.Const{bc.FloatK(0.5), bc.FloatK(1.25)}
|
|
out, fl, _ := run(t, module(f), "f", vm.Int(3))
|
|
if fl != nil || out[0].Float() != 2.75 {
|
|
t.Fatalf("%v %v", out, fl)
|
|
}
|
|
}
|
|
|
|
func TestLoopAndRecursion(t *testing.T) {
|
|
out, fl, _ := run(t, module(sumFunc()), "sum", vm.Int(100))
|
|
want(t, out, fl, 5050)
|
|
out, fl, _ = run(t, module(fibFunc()), "fib", vm.Int(20))
|
|
want(t, out, fl, 6765)
|
|
}
|
|
|
|
func TestFaults(t *testing.T) {
|
|
_, fl, _ := run(t, module(fn("loop", 0, 0, 1, bc.AsBx(bc.JMP, 0, -1))), "loop")
|
|
if fl == nil || fl.Code != vm.CodeBudget {
|
|
t.Fatalf("budget: %v", fl)
|
|
}
|
|
_, fl, _ = run(t, module(fn("deep", 0, 0, 1, bc.ABx(bc.CALL, 0, 0), bc.ABC(bc.RET, 0, 0, 0))), "deep")
|
|
if fl == nil || fl.Code != vm.CodeCallDepth {
|
|
t.Fatalf("depth: %v", fl)
|
|
}
|
|
_, fl, _ = run(t, module(fn("div", 2, 1, 3, bc.ABC(bc.DIVI, 2, 0, 1), bc.ABC(bc.RET, 2, 1, 0))), "div", vm.Int(1), vm.Int(0))
|
|
if fl == nil || fl.Code != vm.CodeDivZero {
|
|
t.Fatalf("div: %v", fl)
|
|
}
|
|
bang := fn("bang", 0, 0, 1, bc.ABx(bc.FAULT, 0, 0))
|
|
bang.Consts = []bc.Const{bc.StringK("boom")}
|
|
_, fl, _ = run(t, module(bang), "bang")
|
|
if fl == nil || fl.Code != vm.CodeExplicit || fl.Message != "boom" {
|
|
t.Fatalf("explicit: %v", fl)
|
|
}
|
|
}
|
|
|
|
func TestFaultStack(t *testing.T) {
|
|
outer := fn("outer", 0, 1, 1, bc.AsBx(bc.LOADI, 0, 0), bc.ABx(bc.CALL, 0, 1), bc.ABC(bc.RET, 0, 1, 0))
|
|
inner := fn("inner", 0, 1, 2, bc.AsBx(bc.LOADI, 0, 1), bc.AsBx(bc.LOADI, 1, 0), bc.ABC(bc.DIVI, 0, 0, 1), bc.ABC(bc.RET, 0, 1, 0))
|
|
_, fl, _ := run(t, module(outer, inner), "outer")
|
|
if fl == nil || len(fl.Stack) != 2 {
|
|
t.Fatalf("fault %v", fl)
|
|
}
|
|
if fl.Stack[0].Function != "inner" || fl.Stack[0].Line != 3 || fl.Stack[1].Function != "outer" || fl.Stack[1].Line != 2 {
|
|
t.Fatalf("stack %+v", fl.Stack)
|
|
}
|
|
if !strings.Contains(fl.Error(), "inner at test.aes:3") {
|
|
t.Fatal(fl.Error())
|
|
}
|
|
}
|
|
|
|
func TestStrings(t *testing.T) {
|
|
f := fn("s", 0, 1, 6,
|
|
bc.ABx(bc.LOADK, 0, 0),
|
|
bc.ABx(bc.LOADK, 1, 1),
|
|
bc.ABC(bc.ADDS, 2, 0, 1),
|
|
bc.ABx(bc.LOADK, 3, 2),
|
|
bc.ABC(bc.EQS, 4, 2, 3),
|
|
bc.ABC(bc.LENS, 5, 2, 0),
|
|
bc.AsBx(bc.JMPNOT, 4, 1),
|
|
bc.ABC(bc.RET, 5, 1, 0),
|
|
bc.AsBx(bc.LOADI, 5, -1),
|
|
bc.ABC(bc.RET, 5, 1, 0),
|
|
)
|
|
f.Consts = []bc.Const{bc.StringK("ab"), bc.StringK("cd"), bc.StringK("abcd")}
|
|
out, fl, _ := run(t, module(f), "s")
|
|
want(t, out, fl, 4)
|
|
}
|
|
|
|
func sliceModule() *bc.Module {
|
|
f := fn("sl", 1, 1, 6,
|
|
bc.ABC(bc.LOADNIL, 1, 0, 0),
|
|
bc.ABx(bc.ENSURE, 1, 0),
|
|
bc.AsBx(bc.LOADI, 2, 10),
|
|
bc.ABC(bc.APPEND, 1, 1, 2),
|
|
bc.AsBx(bc.LOADI, 2, 20),
|
|
bc.ABC(bc.APPEND, 1, 1, 2),
|
|
bc.AsBx(bc.LOADI, 3, 5),
|
|
bc.AsBx(bc.LOADI, 4, 1),
|
|
bc.ABC(bc.SETINDEX, 1, 4, 3),
|
|
bc.ABC(bc.INDEX, 2, 1, 0),
|
|
bc.ABC(bc.LEN, 5, 1, 0),
|
|
bc.ABC(bc.ADDI, 2, 2, 5),
|
|
bc.ABC(bc.RET, 2, 1, 0),
|
|
)
|
|
m := module(f)
|
|
m.Types = []bc.TypeDef{{Elem: "int", Width: 1}}
|
|
return m
|
|
}
|
|
|
|
func TestSlices(t *testing.T) {
|
|
out, fl, _ := run(t, sliceModule(), "sl", vm.Int(1))
|
|
want(t, out, fl, 7)
|
|
_, fl, _ = run(t, sliceModule(), "sl", vm.Int(2))
|
|
if fl == nil || fl.Code != vm.CodeIndex {
|
|
t.Fatalf("bounds: %v", fl)
|
|
}
|
|
}
|
|
|
|
func TestAppendCopyOnWrite(t *testing.T) {
|
|
f := fn("cow", 0, 1, 6,
|
|
bc.ABx(bc.NEWSLICE, 0, 0),
|
|
bc.AsBx(bc.LOADI, 1, 1),
|
|
bc.ABC(bc.APPEND, 0, 0, 1),
|
|
bc.ABC(bc.MOVE, 2, 0, 0),
|
|
bc.AsBx(bc.LOADI, 1, 2),
|
|
bc.ABC(bc.APPEND, 2, 2, 1),
|
|
bc.AsBx(bc.LOADI, 1, 3),
|
|
bc.ABC(bc.APPEND, 3, 0, 1),
|
|
bc.AsBx(bc.LOADI, 4, 1),
|
|
bc.ABC(bc.INDEX, 5, 2, 4),
|
|
bc.ABC(bc.RET, 5, 1, 0),
|
|
)
|
|
m := module(f)
|
|
m.Types = []bc.TypeDef{{Elem: "int", Width: 1}}
|
|
out, fl, _ := run(t, m, "cow")
|
|
want(t, out, fl, 2)
|
|
}
|
|
|
|
func TestVectors(t *testing.T) {
|
|
f := fn("v", 0, 1, 6,
|
|
bc.ABx(bc.LOADK, 0, 0),
|
|
bc.ABx(bc.LOADK, 1, 1),
|
|
bc.ABC(bc.VEC2, 2, 0, 0),
|
|
bc.ABC(bc.VADD, 3, 2, 2),
|
|
bc.ABx(bc.LOADK, 4, 2),
|
|
bc.ABC(bc.VMULS, 3, 3, 4),
|
|
bc.ABC(bc.SETL, 3, 0, 0),
|
|
bc.ABC(bc.GETL, 5, 3, 1),
|
|
bc.ABC(bc.GETL, 4, 3, 0),
|
|
bc.ABC(bc.ADDF, 5, 5, 4),
|
|
bc.ABC(bc.RET, 5, 1, 0),
|
|
)
|
|
f.Consts = []bc.Const{bc.FloatK(1), bc.FloatK(2), bc.FloatK(0.5)}
|
|
out, fl, _ := run(t, module(f), "v")
|
|
if fl != nil || out[0].Float() != 3 {
|
|
t.Fatalf("%v %v", out, fl)
|
|
}
|
|
}
|
|
|
|
func TestSelfFields(t *testing.T) {
|
|
f := fn("Tick", 0, 1, 2, bc.ABx(bc.GETSF, 0, 1), bc.AsBx(bc.LOADI, 1, 1), bc.ABC(bc.ADDI, 0, 0, 1), bc.ABx(bc.SETSF, 0, 1), bc.ABC(bc.RET, 0, 1, 0))
|
|
f.Script = 0
|
|
mod := module(f)
|
|
mod.Scripts = []bc.ScriptDef{{FullName: "test.S", Slots: 2, Fields: []bc.FieldDef{{Name: "N", Type: "int", Slot: 1, Slots: 1}}, Ready: -1, FixedUpdate: -1, Update: -1, Reloaded: -1}}
|
|
m, lm := load(t, mod, nil)
|
|
self := []vm.Value{{}, vm.Int(41)}
|
|
out, fl := m.Call(lm.Func("Tick"), self, nil)
|
|
want(t, out, fl, 42)
|
|
if self[1].Int() != 42 {
|
|
t.Fatal("self field was not written")
|
|
}
|
|
}
|
|
|
|
func hostModule() *bc.Module {
|
|
f := fn("h", 0, 1, 3, bc.AsBx(bc.LOADI, 1, 2), bc.AsBx(bc.LOADI, 2, 3), bc.ABx(bc.CALLH, 1, 0), bc.ABC(bc.RET, 1, 1, 0))
|
|
m := module(f)
|
|
m.Imports = []bc.HostImport{{Module: "t", Name: "add", Sig: abi.Sig(abi.T(abi.Int), abi.T(abi.Int), abi.T(abi.Int))}}
|
|
return m
|
|
}
|
|
|
|
func TestHostCalls(t *testing.T) {
|
|
m, lm := load(t, hostModule(), map[string]vm.HostFunc{"t.add": func(m *vm.Machine, w []vm.Value) error {
|
|
w[0] = vm.Int(w[0].Int() + w[1].Int())
|
|
return nil
|
|
}})
|
|
out, fl := m.Call(lm.Func("h"), nil, nil)
|
|
want(t, out, fl, 5)
|
|
m, lm = load(t, hostModule(), map[string]vm.HostFunc{"t.add": func(*vm.Machine, []vm.Value) error { panic("bad binding") }})
|
|
_, fl = m.Call(lm.Func("h"), nil, nil)
|
|
if fl == nil || fl.Code != vm.CodeNativePanic || !strings.Contains(fl.Message, "bad binding") || fl.Detail == "" {
|
|
t.Fatalf("panic: %+v", fl)
|
|
}
|
|
if _, err := vm.New(vm.Options{}).Load(hostModule(), nil); err == nil {
|
|
t.Fatal("missing host import must fail at load")
|
|
}
|
|
}
|
|
|
|
func TestLoadVerifies(t *testing.T) {
|
|
mod := module(fn("bad", 0, 0, 1, bc.ABC(bc.ADDI, 9, 0, 0), bc.ABC(bc.RET, 0, 0, 0)))
|
|
if _, err := vm.New(vm.Options{}).Load(mod, nil); err == nil {
|
|
t.Fatal("corrupt module was loaded")
|
|
}
|
|
}
|
|
|
|
func TestExterns(t *testing.T) {
|
|
a := bc.NewModule("game/a")
|
|
a.Functions = []bc.Function{fn("Double", 1, 1, 1, bc.ABC(bc.ADDI, 0, 0, 0), bc.ABC(bc.RET, 0, 1, 0))}
|
|
b := bc.NewModule("game/b")
|
|
b.Externs = []bc.Extern{{Package: "game/a", Name: "Double", Params: 1, Results: 1}}
|
|
b.Functions = []bc.Function{fn("run", 0, 1, 1, bc.AsBx(bc.LOADI, 0, 21), bc.ABx(bc.CALLX, 0, 0), bc.ABC(bc.RET, 0, 1, 0))}
|
|
m := vm.New(vm.Options{})
|
|
ma, err := m.Load(a, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
mb, err := m.Load(b, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := vm.Link([]*vm.Module{ma, mb}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out, fl := m.Call(mb.Func("run"), nil, nil)
|
|
want(t, out, fl, 42)
|
|
}
|
|
|
|
type roots []vm.Value
|
|
|
|
func (r roots) MarkRoots(mk *vm.Marker) {
|
|
for _, v := range r {
|
|
mk.Mark(v)
|
|
}
|
|
}
|
|
|
|
func TestGCKeepsLiveStrings(t *testing.T) {
|
|
f := fn("gen", 1, 1, 5,
|
|
bc.AsBx(bc.LOADI, 1, 0),
|
|
bc.AsBx(bc.LOADI, 2, 1),
|
|
bc.ABC(bc.LTI, 3, 1, 0),
|
|
bc.AsBx(bc.JMPNOT, 3, 3),
|
|
bc.ABC(bc.ITOS, 4, 1, 0),
|
|
bc.ABC(bc.ADDI, 1, 1, 2),
|
|
bc.AsBx(bc.JMP, 0, -5),
|
|
bc.ABC(bc.RET, 4, 1, 0),
|
|
)
|
|
m, lm := load(t, module(f), nil)
|
|
out, fl := m.Call(lm.Func("gen"), nil, []vm.Value{vm.Int(1000)})
|
|
if fl != nil {
|
|
t.Fatal(fl)
|
|
}
|
|
last := out[0]
|
|
if n := m.Stats().HeapObjects; n != 1000 {
|
|
t.Fatalf("heap objects before gc %d", n)
|
|
}
|
|
m.Collect(roots{last})
|
|
if s, ok := m.Str(last); !ok || s != "999" || m.Stats().HeapObjects != 1 {
|
|
t.Fatalf("after gc %q %v %d", s, ok, m.Stats().HeapObjects)
|
|
}
|
|
m.Collect(nil)
|
|
if _, ok := m.Str(last); ok || m.Stats().HeapObjects != 0 {
|
|
t.Fatal("unreachable string survived")
|
|
}
|
|
}
|
|
|
|
func TestGCTracesSlices(t *testing.T) {
|
|
f := fn("tags", 0, 1, 3,
|
|
bc.ABx(bc.NEWSLICE, 0, 0),
|
|
bc.AsBx(bc.LOADI, 1, 7),
|
|
bc.ABC(bc.ITOS, 1, 1, 0),
|
|
bc.ABC(bc.APPEND, 0, 0, 1),
|
|
bc.AsBx(bc.LOADI, 1, 8),
|
|
bc.ABC(bc.ITOS, 1, 1, 0),
|
|
bc.ABC(bc.APPEND, 0, 0, 1),
|
|
bc.AsBx(bc.LOADI, 2, 9),
|
|
bc.ABC(bc.ITOS, 2, 2, 0),
|
|
bc.ABC(bc.RET, 0, 1, 0),
|
|
)
|
|
mod := module(f)
|
|
mod.Types = []bc.TypeDef{{Elem: "string", Width: 1, Mask: 1}}
|
|
m, lm := load(t, mod, nil)
|
|
out, fl := m.Call(lm.Func("tags"), nil, nil)
|
|
if fl != nil {
|
|
t.Fatal(fl)
|
|
}
|
|
s := out[0]
|
|
m.Collect(roots{s})
|
|
if n := m.Stats().HeapObjects; n != 3 {
|
|
t.Fatalf("live objects %d, want slice and two strings", n)
|
|
}
|
|
}
|
|
|
|
func TestCallDoesNotAllocate(t *testing.T) {
|
|
m, lm := load(t, module(sumFunc()), nil)
|
|
f := lm.Func("sum")
|
|
args := []vm.Value{vm.Int(50)}
|
|
m.Call(f, nil, args)
|
|
if n := testing.AllocsPerRun(200, func() { m.Call(f, nil, args) }); n != 0 {
|
|
t.Fatalf("call allocates %.1f objects", n)
|
|
}
|
|
}
|
|
|
|
func TestHostFaultKeepsCode(t *testing.T) {
|
|
m, lm := load(t, hostModule(), map[string]vm.HostFunc{"t.add": func(*vm.Machine, []vm.Value) error {
|
|
return &vm.Fault{Code: vm.CodeNil, Message: "node reference is nil"}
|
|
}})
|
|
if _, fl := m.Call(lm.Func("h"), nil, nil); fl == nil || fl.Code != vm.CodeNil {
|
|
t.Fatalf("fault %v", fl)
|
|
}
|
|
m, lm = load(t, hostModule(), map[string]vm.HostFunc{"t.add": func(*vm.Machine, []vm.Value) error {
|
|
return errors.New("plain")
|
|
}})
|
|
if _, fl := m.Call(lm.Func("h"), nil, nil); fl == nil || fl.Code != vm.CodeHost {
|
|
t.Fatalf("fault %v", fl)
|
|
}
|
|
}
|
|
|
|
func crossStruct(heap []uint16) (*bc.Module, *bc.Module) {
|
|
a := bc.NewModule("game/a")
|
|
a.Structs = []bc.StructDef{{Name: "game/a.P", Slots: 1, Fields: []bc.StructField{{Name: "S", Type: "string", Slot: 0, Slots: 1}}}}
|
|
b := bc.NewModule("game/b")
|
|
b.Scripts = []bc.ScriptDef{{
|
|
FullName: "game.b.S", Slots: 2, HeapSlots: heap,
|
|
Fields: []bc.FieldDef{{Name: "P", Type: "struct:game/a.P", Slot: 1, Slots: 1}},
|
|
Ready: -1, FixedUpdate: -1, Update: -1, Reloaded: -1,
|
|
}}
|
|
return a, b
|
|
}
|
|
|
|
func TestLinkChecksCrossModuleRoots(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
heap []uint16
|
|
ok bool
|
|
}{{nil, false}, {[]uint16{1}, true}} {
|
|
a, b := crossStruct(tc.heap)
|
|
m := vm.New(vm.Options{})
|
|
la, err := m.Load(a, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
lb, err := m.Load(b, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := vm.Link([]*vm.Module{la, lb}); (err == nil) != tc.ok {
|
|
t.Fatalf("heap %v: link err %v", tc.heap, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBadHandle(t *testing.T) {
|
|
f := fn("bad", 0, 1, 2, bc.ABx(bc.LOADK, 0, 0), bc.ABC(bc.LENS, 1, 0, 0), bc.ABC(bc.RET, 1, 1, 0))
|
|
f.Consts = []bc.Const{bc.RawK(uint64(9)<<32|1, 0)}
|
|
_, fl, _ := run(t, module(f), "bad")
|
|
if fl == nil || fl.Code != vm.CodeBadHandle {
|
|
t.Fatalf("fault %v", fl)
|
|
}
|
|
}
|
|
|
|
func TestCallLinkFault(t *testing.T) {
|
|
m, lm := load(t, module(sumFunc()), nil)
|
|
if _, fl := m.Call(lm.Func("missing"), nil, nil); fl == nil || fl.Code != vm.CodeLink {
|
|
t.Fatalf("fault %v", fl)
|
|
}
|
|
}
|
|
|
|
func TestCollectRefusedDuringCall(t *testing.T) {
|
|
collected := true
|
|
m, lm := load(t, hostModule(), map[string]vm.HostFunc{"t.add": func(m *vm.Machine, w []vm.Value) error {
|
|
collected = m.Collect(nil)
|
|
return nil
|
|
}})
|
|
if _, fl := m.Call(lm.Func("h"), nil, nil); fl != nil {
|
|
t.Fatal(fl)
|
|
}
|
|
if collected {
|
|
t.Fatal("Collect ran with an active frame")
|
|
}
|
|
if !m.Collect(nil) {
|
|
t.Fatal("Collect refused at a safe point")
|
|
}
|
|
}
|