diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 177b0f9dd66..bf0892be681 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -31,6 +31,7 @@ import json import math import os +import pathlib import random import re import shutil @@ -2049,8 +2050,9 @@ def compare_to_merged_output(self, output, merged_output): compare(output, merged_output, 'Two-Merged') -# Test --fuzz-preserve-imports-exports, which never modifies imports or exports. -class PreserveImportsExports(TestCaseHandler): +# Test --fuzz-preserve-imports-exports on random inputs. This should never +# modify imports or exports. +class PreserveImportsExportsRandom(TestCaseHandler): frequency = 0.1 def handle(self, wasm): @@ -2095,6 +2097,147 @@ def get_relevant_lines(wat): compare(get_relevant_lines(original), get_relevant_lines(processed), 'Preserve') +# Test --fuzz-preserve-imports-exports on a realistic js+wasm input. Unlike +# PreserveImportsExportsRandom which starts with a random file and modifies it, +# this starts with a fixed js+wasm testcase, known to work and to have +# interesting operations on the js/wasm boundary, and then randomly modifies +# the wasm. This simulates how an external fuzzer could use binaryen to modify +# its known-working testcases (parallel to how we test ClusterFuzz here). +# +# This reads wasm+js combinations from the test/js_wasm directory, so as new +# testcases are added there, this will fuzz them. +# +# Note that bugs found by this fuzzer require BINARYEN_TRUST_GIVEN_WASM=1 in the +# env for reduction. TODO: simplify this +class PreserveImportsExportsJS(TestCaseHandler): + frequency = 1 + + def handle_pair(self, input, before_wasm, after_wasm, opts): + try: + self.do_handle_pair(input, before_wasm, after_wasm, opts) + except Exception as e: + if not os.environ.get('BINARYEN_TRUST_GIVEN_WASM'): + # We errored, and we were not given a wasm file to trust as we + # reduce, so this is the first time we hit an error. Save the + # pre wasm file, the one we began with, as `before_wasm`, so + # that the reducer will make us proceed exactly from there. + shutil.copyfile(self.pre_wasm, before_wasm) + raise e + + def do_handle_pair(self, input, before_wasm, after_wasm, opts): + # Some of the time use a custom input. The normal inputs the fuzzer + # generates are in range INPUT_SIZE_MIN-INPUT_SIZE_MAX, which is good + # for new testcases, but the more changes we make to js+wasm testcases, + # the more chance we have to break things entirely (the js/wasm boundary + # is fragile). It is useful to also fuzz smaller sizes. + if random.random() < 0.25: + size = random.randint(0, INPUT_SIZE_MIN * 2) + make_random_input(size, input) + + # Pick a js+wasm pair. + js_files = list(pathlib.Path(in_binaryen('test', 'js_wasm')).glob('*.mjs')) + js_file = str(random.choice(js_files)) + print(f'js file: {js_file}') + wat_file = str(pathlib.Path(js_file).with_suffix('.wat')) + + # Verify the wat works with our features + try: + run([in_bin('wasm-opt'), wat_file] + FEATURE_OPTS, + stderr=subprocess.PIPE, + silent=True) + except Exception: + note_ignored_vm_run('PreserveImportsExportsJS: features not compatible with js+wasm') + return + + # Make sure the testcase runs by itself - there should be no invalid + # testcases. + original_wasm = 'orig.wasm' + run([in_bin('wasm-opt'), wat_file, '-o', original_wasm] + FEATURE_OPTS) + D8().run_js(js_file, original_wasm) + + # Modify the initial wat to get the pre-optimizations wasm. + pre_wasm = abspath('pre.wasm') + run([in_bin('wasm-opt'), input] + FEATURE_OPTS + [ + '-ttf', + '--fuzz-preserve-imports-exports', + '--initial-fuzz=' + wat_file, + '-o', pre_wasm, + '-g', + ]) + + # We successfully generated pre_wasm; stash it for possible reduction + # purposes later. + self.pre_wasm = pre_wasm + + # If we were given a wasm file, use that instead of all the above. We + # do this now, after creating pre_wasm, because we still need to consume + # all the randomness normally. + if os.environ.get('BINARYEN_TRUST_GIVEN_WASM'): + print('using given wasm', before_wasm) + pre_wasm = before_wasm + + # Pick a vm and run before we optimize the wasm. + vms = [ + D8(), + D8Liftoff(), + D8Turboshaft(), + ] + pre_vm = random.choice(vms) + pre = self.do_run(pre_vm, js_file, pre_wasm) + + # Optimize. + post_wasm = abspath('post.wasm') + cmd = [in_bin('wasm-opt'), pre_wasm, '-o', post_wasm] + opts + FEATURE_OPTS + print(' '.join(cmd)) + proc = subprocess.run(cmd, capture_output=True, text=True) + if proc.returncode: + if 'Invalid configureAll' in proc.stderr: + # We have a hard error on unfamiliar configureAll patterns atm. + # Mutation of configureAll will easily break that pattern, so we + # must ignore such cases. + note_ignored_vm_run('PreserveImportsExportsJS: bad configureAll') + return + + # Anything else is a problem. + print(proc.stderr) + raise Exception('opts failed') + + # Run after opts, in a random vm. + post_vm = random.choice(vms) + post = self.do_run(post_vm, js_file, post_wasm) + + # Compare + compare(pre, post, 'PreserveImportsExportsJS') + + def do_run(self, vm, js, wasm): + out = vm.run_js(js, wasm, checked=False) + + cleaned = [] + for line in out.splitlines(): + if 'RuntimeError:' in line or 'TypeError:' in line: + # This is part of an error like + # + # wasm-function[2]:0x273: RuntimeError: unreachable + # + # We must ignore the binary location, which opts can change. We + # must also remove the specific trap, as Binaryen can change + # that. + line = 'TRAP' + elif 'wasm://' in line or '()' in line: + # This is part of a stack trace like + # + # at wasm://wasm/12345678:wasm-function[42]:0x123 + # at () + # + # Ignore it, as traces differ based on optimizations. + continue + cleaned.append(line) + return '\n'.join(cleaned) + + def can_run_on_wasm(self, wasm): + return all_disallowed(DISALLOWED_FEATURES_IN_V8) + + # Test that we preserve branch hints properly. The invariant that we test here # is that, given correct branch hints (that is, the input wasm's branch hints # are always correct: a branch is taken iff the hint is that it is taken), then @@ -2322,7 +2465,8 @@ def handle(self, wasm): RoundtripText(), ClusterFuzz(), Two(), - PreserveImportsExports(), + PreserveImportsExportsRandom(), + PreserveImportsExportsJS(), BranchHintPreservation(), ] diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 29e4cf0e4ac..8a23262280d 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -117,6 +117,10 @@ 'waitqueue.wast', # TODO: fix handling of the non-utf8 names here 'name-high-bytes.wast', + # JS interop testcases have complex js-wasm interactions + 'js_interop_counter.wat', + 'js_interop_cases.wat', + 'js_interop_corners.wat', ] diff --git a/test/js_wasm/js_interop_cases.mjs b/test/js_wasm/js_interop_cases.mjs new file mode 100644 index 00000000000..c54758b2b98 --- /dev/null +++ b/test/js_wasm/js_interop_cases.mjs @@ -0,0 +1,56 @@ +let protoFactory = new Proxy({}, { + get(target, prop, receiver) { + // Always return a fresh, empty object. + return {}; + } +}); + +let constructors = {}; + +let imports = { + "protos": protoFactory, + "env": { constructors }, +}; + +let compileOptions = { builtins: ["js-prototypes"] }; + +let buffer = readbuffer(arguments[0]); + +let { module, instance } = + await WebAssembly.instantiate(buffer, imports, compileOptions); + +let Base = constructors.Base; +let Derived = constructors.Derived; + +// Test Base +console.log("Testing Base..."); +let b = new Base(10); +console.log("b.getValue():", b.getValue()); // 10 +console.log("b.value getter:", b.value); // 10 +b.value = 20; +console.log("b.value after setter:", b.getValue()); // 20 +console.log("b instanceof Base:", b instanceof Base); // true +console.log("b instanceof Derived:", b instanceof Derived); // false + +// Test Derived +console.log("\nTesting Derived..."); +let d = new Derived(100, 500); +console.log("d.getValue() (inherited):", d.getValue()); // 100 +console.log("d.getExtra():", d.getExtra()); // 500 +console.log("d.value getter (inherited):", d.value); // 100 +d.value = 150; +console.log("d.value after setter (inherited):", d.getValue()); // 150 +console.log("d instanceof Derived:", d instanceof Derived); // true +console.log("d instanceof Base (inheritance):", d instanceof Base); // true +console.log("Derived.staticMethod():", Derived.staticMethod()); // 42 + +// Test Wasm-side descriptor checks +console.log("\nTesting Wasm-side descriptor checks..."); +console.log("checkDesc(b):", instance.exports.checkDesc(b)); // 1 +console.log("checkDesc(d):", instance.exports.checkDesc(d)); // 2 +console.log("isDerived(b):", instance.exports.isDerived(b)); // 0 +console.log("isDerived(d):", instance.exports.isDerived(d)); // 1 + +// Test cross-checks +console.log("\nTesting cross-checks..."); +console.log("get_base_val(d):", instance.exports.get_base_val(d)); // 150 diff --git a/test/js_wasm/js_interop_cases.wat b/test/js_wasm/js_interop_cases.wat new file mode 100644 index 00000000000..68861143ce8 --- /dev/null +++ b/test/js_wasm/js_interop_cases.wat @@ -0,0 +1,163 @@ +(module + (rec + (type $Base (sub (descriptor $Base.vtable) (struct (field $val (mut i32))))) + (type $Base.vtable (sub (describes $Base) (struct + (field $proto (ref extern)) + (field $getValue (ref $getValue_t)) + (field $setValue (ref $setValue_t)) + ))) + (type $getValue_t (func (param (ref null $Base)) (result i32))) + (type $setValue_t (func (param (ref null $Base)) (param i32))) + + (type $Derived (sub $Base (descriptor $Derived.vtable) (struct (field $val (mut i32)) (field $extra i32)))) + (type $Derived.vtable (sub $Base.vtable (describes $Derived) (struct + (field $proto (ref extern)) + (field $getValue (ref $getValue_t)) + (field $setValue (ref $setValue_t)) + (field $getExtra (ref $getExtra_t)) + ))) + (type $getExtra_t (func (param (ref null $Derived)) (result i32))) + (type $staticMethod_t (func (result i32))) + ) + + (type $newBase_t (func (param i32) (result (ref $Base)))) + (type $newDerived_t (func (param i32 i32) (result (ref $Derived)))) + + ;; Types for prototype configuration + (type $prototypes (array (mut externref))) + (type $functions (array (mut funcref))) + (type $data (array (mut i8))) + (type $configureAll (func (param (ref null $prototypes)) + (param (ref null $functions)) + (param (ref null $data)) + (param externref))) + + (import "protos" "Base.proto" (global $Base.proto (ref extern))) + (import "protos" "Derived.proto" (global $Derived.proto (ref extern))) + + (import "env" "constructors" (global $constructors externref)) + + (import "wasm:js-prototypes" "configureAll" + (func $configureAll (type $configureAll))) + + (elem $prototypes externref + (global.get $Base.proto) + (global.get $Derived.proto) + ) + + (elem $functions funcref + (ref.func $Base.new) + (ref.func $Base.getValue) + (ref.func $Base.getValue) + (ref.func $Base.setValue) + (ref.func $Derived.new) + (ref.func $Derived.staticMethod) + (ref.func $Derived.getExtra) + ) + + ;; \02 - 2 protoconfigs + ;; Base: + ;; \01 - 1 constructorconfig + ;; \04Base - "Base" + ;; \00 - 0 static methods + ;; \03 - 3 methodconfigs + ;; \00\08getValue - method "getValue" + ;; \01\05value - getter "value" + ;; \02\05value - setter "value" + ;; \7f - parentidx -1 + ;; Derived: + ;; \01 - 1 constructorconfig + ;; \07Derived - "Derived" + ;; \01 - 1 static method + ;; \00\0cstaticMethod + ;; \01 - 1 methodconfig + ;; \00\08getExtra + ;; \00 - parentidx 0 (Base) + (data $data "\02\01\04Base\00\03\00\08getValue\01\05value\02\05value\7f\01\07Derived\01\00\0cstaticMethod\01\00\08getExtra\00") + + (global $Base.vtable (export "Base.vtable") (ref (exact $Base.vtable)) + (struct.new $Base.vtable + (global.get $Base.proto) + (ref.func $Base.getValue) + (ref.func $Base.setValue) + ) + ) + + (global $Derived.vtable (export "Derived.vtable") (ref (exact $Derived.vtable)) + (struct.new $Derived.vtable + (global.get $Derived.proto) + (ref.func $Base.getValue) + (ref.func $Base.setValue) + (ref.func $Derived.getExtra) + ) + ) + + (func $Base.new (type $newBase_t) (param $val i32) (result (ref $Base)) + (struct.new_desc $Base + (local.get $val) + (global.get $Base.vtable) + ) + ) + + (func $Base.getValue (type $getValue_t) (param $this (ref null $Base)) (result i32) + (struct.get $Base $val (local.get $this)) + ) + + (func $Base.setValue (type $setValue_t) (param $this (ref null $Base)) (param $val i32) + (struct.set $Base $val (local.get $this) (local.get $val)) + ) + + (func $Derived.new (type $newDerived_t) (param $val i32) (param $extra i32) (result (ref $Derived)) + (struct.new_desc $Derived + (local.get $val) + (local.get $extra) + (global.get $Derived.vtable) + ) + ) + + (func $Derived.getExtra (type $getExtra_t) (param $this (ref null $Derived)) (result i32) + (struct.get $Derived $extra (local.get $this)) + ) + + (func $Derived.staticMethod (type $staticMethod_t) (result i32) + (i32.const 42) + ) + + (func $start + (call $configureAll + (array.new_elem $prototypes $prototypes (i32.const 0) (i32.const 2)) + (array.new_elem $functions $functions (i32.const 0) (i32.const 7)) + (array.new_data $data $data (i32.const 0) (i32.const 70)) + (global.get $constructors) + ) + ) + + (start $start) + + ;; Additional tests for descriptor instructions + (func (export "get_base_val") (param $b (ref $Base)) (result i32) + (call $Base.getValue (local.get $b)) + ) + + (func (export "checkDesc") (param $b (ref $Base)) (result i32) + (block $derived (result (ref $Derived)) + (block $base (result (ref $Base)) + (br_on_cast_desc_eq $base (ref $Base) (ref $Base) (local.get $b) (global.get $Base.vtable)) + (br_on_cast_desc_eq $derived (ref $Base) (ref $Derived) (local.get $b) (global.get $Derived.vtable)) + (return (i32.const 0)) + ) + (drop) + (return (i32.const 1)) + ) + (drop) + (return (i32.const 2)) + ) + + (func (export "isDerived") (param $b (ref $Base)) (result i32) + (if (result i32) + (ref.test (ref $Derived) (local.get $b)) + (then (i32.const 1)) + (else (i32.const 0)) + ) + ) +) diff --git a/test/js_wasm/js_interop_corners.mjs b/test/js_wasm/js_interop_corners.mjs new file mode 100644 index 00000000000..75ed2feeb27 --- /dev/null +++ b/test/js_wasm/js_interop_corners.mjs @@ -0,0 +1,92 @@ +let protoFactory = new Proxy({}, { + get(target, prop, receiver) { + // Always return a fresh, empty object. + return {}; + } +}); + +let constructors = {}; + +let imports = { + "protos": protoFactory, + "env": { + constructors, + exact_func: (x) => x + 100, + }, +}; + +let compileOptions = { builtins: ["js-prototypes"] }; + +let buffer = readbuffer(arguments[0]); + +let { module, instance } = + await WebAssembly.instantiate(buffer, imports, compileOptions); + +// Test exact function import +console.log("call_exact(5):", instance.exports.call_exact(5)); + +// Test A (no constructor, just methods on prototype) +let a = instance.exports.newA(10); +console.log("a.getA():", a.getA()); +console.log("Object.getPrototypeOf(a) exists:", !!Object.getPrototypeOf(a)); + +// Test B (inherits from A) +let B = constructors.B; +let b = new B(20, 30); +console.log("b.getA():", b.getA()); +console.log("b.getB():", b.getB()); +console.log("b instanceof B:", b instanceof B); +console.log("b instanceof constructors.B:", b instanceof constructors.B); + +// Test C (inherits from B) +let C = constructors.C; +let c = new C(40, 50, 60); +console.log("c.getA():", c.getA()); +console.log("c.getB():", c.getB()); +console.log("c.getC():", c.getC()); +console.log("c instanceof C:", c instanceof C); +console.log("c instanceof B:", c instanceof B); +console.log("C.s1():", C.s1()); +console.log("C.s2():", C.s2()); + +// Test Meta-descriptor +let Meta = constructors.Meta; +let m = new Meta(70); +console.log("m.getM():", m.getM()); + +let mDesc = instance.exports.get_meta_desc(m); +console.log("mDesc.getVal():", mDesc.getVal()); +console.log("mDesc instanceof Object:", mDesc instanceof Object); +// The descriptor itself has a prototype configured! +console.log("mDesc.getVal inherited:", !!mDesc.getVal); + +// Test NoProto (invalid prototype source in descriptor) +let noProto = instance.exports.newNoProto(80); +try { + console.log("Object.getPrototypeOf(noProto):", Object.getPrototypeOf(noProto)); +} catch (e) { + console.log("Object.getPrototypeOf(noProto) threw:", e.name); +} + +// Test cast instructions +let bVtable = instance.exports.get_B_vtable(); +try { + let castedB = instance.exports.test_cast_desc_eq(b, bVtable); + console.log("test_cast_desc_eq(b, bVtable) succeeded:", !!castedB); +} catch (e) { + console.log("test_cast_desc_eq(b, bVtable) failed:", e.name); +} + +try { + instance.exports.test_cast_desc_eq(a, bVtable); + console.log("test_cast_desc_eq(a, bVtable) succeeded unexpectedly"); +} catch (e) { + console.log("test_cast_desc_eq(a, bVtable) failed as expected:", e.name); +} + +console.log("test_br_on_cast_desc_eq_fail(b, bVtable):", instance.exports.test_br_on_cast_desc_eq_fail(b, bVtable)); +console.log("test_br_on_cast_desc_eq_fail(a, bVtable):", instance.exports.test_br_on_cast_desc_eq_fail(a, bVtable)); + +// Test newDefault +let def = instance.exports.newDefault(); +console.log("newDefault exists:", !!def); diff --git a/test/js_wasm/js_interop_corners.wat b/test/js_wasm/js_interop_corners.wat new file mode 100644 index 00000000000..330100dfc0e --- /dev/null +++ b/test/js_wasm/js_interop_corners.wat @@ -0,0 +1,169 @@ +(module + (rec + ;; A -> B -> C inheritance chain + (type $A (sub (descriptor $A.desc) (struct (field $a (mut i32))))) + (type $A.desc (sub (describes $A) (struct (field $proto (ref extern)) (field $getA (ref $getA_t))))) + (type $getA_t (func (param (ref null $A)) (result i32))) + + (type $B (sub $A (descriptor $B.desc) (struct (field $a (mut i32)) (field $b (mut i32))))) + (type $B.desc (sub $A.desc (describes $B) (struct (field $proto (ref extern)) (field $getA (ref $getA_t)) (field $getB (ref $getB_t))))) + (type $getB_t (func (param (ref null $B)) (result i32))) + + (type $C (sub $B (descriptor $C.desc) (struct (field $a (mut i32)) (field $b (mut i32)) (field $c (mut i32))))) + (type $C.desc (sub $B.desc (describes $C) (struct (field $proto (ref extern)) (field $getA (ref $getA_t)) (field $getB (ref $getB_t)) (field $getC (ref $getC_t))))) + (type $getC_t (func (param (ref null $C)) (result i32))) + + ;; Type with meta-descriptor (descriptor for the descriptor) + (type $Meta (sub (descriptor $Meta.desc) (struct (field $m (mut i32))))) + (type $Meta.desc (sub (describes $Meta) (descriptor $Meta.meta-desc) (struct (field $proto (ref extern)) (field $val i32) (field $getM (ref $getM_t))))) + (type $getM_t (func (param (ref null $Meta)) (result i32))) + (type $Meta.meta-desc (sub (describes $Meta.desc) (struct (field $proto (ref extern)) (field $metaVal i32) (field $getVal (ref $getVal_t))))) + (type $getVal_t (func (param (ref null $Meta.desc)) (result i32))) + + ;; Type with invalid prototype source (first field is i32, not externref) + (type $NoProto (sub (descriptor $NoProto.desc) (struct (field $x i32)))) + (type $NoProto.desc (sub (describes $NoProto) (struct (field $val i32)))) + + ;; Test struct.new_default_desc + (type $Default (sub (descriptor $Default.desc) (struct (field i32)))) + (type $Default.desc (sub (describes $Default) (struct (field (ref extern))))) + ) + + (type $exact_f_t (func (param i32) (result i32))) + (type $newB_t (func (param i32 i32) (result (ref $B)))) + (type $newC_t (func (param i32 i32 i32) (result (ref $C)))) + (type $newMeta_t (func (param i32) (result (ref $Meta)))) + + ;; Types for configureAll + (type $prototypes (array (mut externref))) + (type $functions (array (mut funcref))) + (type $data (array (mut i8))) + (type $configureAll (func (param (ref null $prototypes)) + (param (ref null $functions)) + (param (ref null $data)) + (param externref))) + + (import "wasm:js-prototypes" "configureAll" (func $configureAll (type $configureAll))) + (import "env" "constructors" (global $constructors externref)) + (import "protos" "A.proto" (global $A.proto (ref extern))) + (import "protos" "B.proto" (global $B.proto (ref extern))) + (import "protos" "C.proto" (global $C.proto (ref extern))) + (import "protos" "Meta.proto" (global $Meta.proto (ref extern))) + (import "protos" "MetaDesc.proto" (global $MetaDesc.proto (ref extern))) + + ;; Exact function import test + (import "env" "exact_func" (func $exact_func (exact (type $exact_f_t)))) + + (elem $prototypes externref + (global.get $A.proto) + (global.get $B.proto) + (global.get $C.proto) + (global.get $Meta.proto) + (global.get $MetaDesc.proto) + ) + + (elem $functions funcref + (ref.func $A.getA) ;; 0: method for A + (ref.func $B.new) ;; 1: constructor for B + (ref.func $B.getB) ;; 2: method for B + (ref.func $C.new) ;; 3: constructor for C + (ref.func $static1) ;; 4: static 1 for C + (ref.func $static2) ;; 5: static 2 for C + (ref.func $C.getC) ;; 6: method for C + (ref.func $Meta.new) ;; 7: constructor for Meta + (ref.func $Meta.getM) ;; 8: method for Meta + (ref.func $MetaDesc.getVal) ;; 9: method for MetaDesc + ) + + ;; \05 (5 protoconfigs) + ;; 1. A: \00 (0 constructors) \01 (1 method) \00\04getA \7f (parent -1) + ;; 2. B: \01 (1 constructor) \01B \00 (0 static) \01 (1 method) \00\04getB \00 (parent 0) + ;; 3. C: \01 (1 constructor) \01C \02 (2 static) \00\02s1 \00\02s2 \01 (1 method) \00\04getC \01 (parent 1) + ;; 4. Meta: \01 (1 constructor) \04Meta \00 (0 static) \01 (1 method) \00\04getM \7f (parent -1) + ;; 5. MetaDesc: \00 (0 constructors) \01 (1 method) \00\06getVal \7f (parent -1) + (data $data "\05\00\01\00\04getA\7f\01\01B\00\01\00\04getB\00\01\01C\02\00\02s1\00\02s2\01\00\04getC\01\01\04Meta\00\01\00\04getM\7f\00\01\00\06getVal\7f") + + (global $A.vtable (ref (exact $A.desc)) + (struct.new $A.desc (global.get $A.proto) (ref.func $A.getA)) + ) + (global $B.vtable (ref (exact $B.desc)) + (struct.new $B.desc (global.get $B.proto) (ref.func $A.getA) (ref.func $B.getB)) + ) + (global $C.vtable (ref (exact $C.desc)) + (struct.new $C.desc (global.get $C.proto) (ref.func $A.getA) (ref.func $B.getB) (ref.func $C.getC)) + ) + + (global $Meta.meta-vtable (ref (exact $Meta.meta-desc)) + (struct.new $Meta.meta-desc (global.get $MetaDesc.proto) (i32.const 42) (ref.func $MetaDesc.getVal)) + ) + (global $Meta.vtable (ref (exact $Meta.desc)) + (struct.new_desc $Meta.desc (global.get $Meta.proto) (i32.const 100) (ref.func $Meta.getM) (global.get $Meta.meta-vtable)) + ) + + (global $NoProto.vtable (ref (exact $NoProto.desc)) + (struct.new $NoProto.desc (i32.const 123)) + ) + + (func $A.getA (type $getA_t) (param $this (ref null $A)) (result i32) (struct.get $A $a (local.get $this))) + (func $B.getB (type $getB_t) (param $this (ref null $B)) (result i32) (struct.get $B $b (local.get $this))) + (func $C.getC (type $getC_t) (param $this (ref null $C)) (result i32) (struct.get $C $c (local.get $this))) + (func $Meta.getM (type $getM_t) (param $this (ref null $Meta)) (result i32) (struct.get $Meta $m (local.get $this))) + (func $MetaDesc.getVal (type $getVal_t) (param $this (ref null $Meta.desc)) (result i32) (struct.get $Meta.desc $val (local.get $this))) + (func $static1 (result i32) (i32.const 1)) + (func $static2 (result i32) (i32.const 2)) + + (func $A.new (export "newA") (param $a i32) (result (ref $A)) + (struct.new_desc $A (local.get $a) (global.get $A.vtable)) + ) + (func $B.new (type $newB_t) (param $a i32) (param $b i32) (result (ref $B)) + (struct.new_desc $B (local.get $a) (local.get $b) (global.get $B.vtable)) + ) + (func $C.new (type $newC_t) (param $a i32) (param $b i32) (param $c i32) (result (ref $C)) + (struct.new_desc $C (local.get $a) (local.get $b) (local.get $c) (global.get $C.vtable)) + ) + (func $Meta.new (type $newMeta_t) (param $m i32) (result (ref $Meta)) + (struct.new_desc $Meta (local.get $m) (global.get $Meta.vtable)) + ) + (func $NoProto.new (export "newNoProto") (param $x i32) (result (ref $NoProto)) + (struct.new_desc $NoProto (local.get $x) (global.get $NoProto.vtable)) + ) + + (func $start + (call $configureAll + (array.new_elem $prototypes $prototypes (i32.const 0) (i32.const 5)) + (array.new_elem $functions $functions (i32.const 0) (i32.const 10)) + (array.new_data $data $data (i32.const 0) (i32.const 68)) + (global.get $constructors) + ) + ) + (start $start) + + (func (export "get_meta_desc") (param $m (ref $Meta)) (result (ref $Meta.desc)) + (ref.get_desc $Meta (local.get $m)) + ) + + (func (export "test_cast_desc_eq") (param $a (ref $A)) (param $desc (ref (exact $B.desc))) (result (ref null $B)) + (ref.cast_desc_eq (ref null $B) (local.get $a) (local.get $desc)) + ) + + (func (export "test_br_on_cast_desc_eq_fail") (param $a (ref $A)) (param $desc (ref (exact $B.desc))) (result i32) + (block $fail (result (ref $A)) + (drop (br_on_cast_desc_eq_fail $fail (ref $A) (ref $B) (local.get $a) (local.get $desc))) + (return (i32.const 1)) + ) + (drop) + (return (i32.const 0)) + ) + + (func (export "get_B_vtable") (result (ref (exact $B.desc))) (global.get $B.vtable)) + + (global $Default.vtable (ref (exact $Default.desc)) (struct.new $Default.desc (global.get $A.proto))) + + (func (export "newDefault") (result (ref $Default)) + (struct.new_default_desc $Default (global.get $Default.vtable)) + ) + + (func (export "call_exact") (param i32) (result i32) + (call $exact_func (local.get 0)) + ) +) diff --git a/test/js_wasm/js_interop_counter.mjs b/test/js_wasm/js_interop_counter.mjs new file mode 100644 index 00000000000..3a646aa8f0a --- /dev/null +++ b/test/js_wasm/js_interop_counter.mjs @@ -0,0 +1,32 @@ +// https://github.com/WebAssembly/custom-descriptors/blob/main/proposals/custom-descriptors/Overview.md + +let protoFactory = new Proxy({}, { + get(target, prop, receiver) { + // Always return a fresh, empty object. + return {}; + } +}); + +let constructors = {}; + +let imports = { + "protos": protoFactory, + "env": { constructors }, +}; + +let compileOptions = { builtins: ["js-prototypes"] }; + +let buffer = readbuffer(arguments[0]); // XXX modified to read the wasm filename + +let { module, instance } = + await WebAssembly.instantiate(buffer, imports, compileOptions); + +let Counter = constructors.Counter; + +let count = new Counter(0); + +console.log(count.get()); +count.inc(); +console.log(count.get()); + +console.log(count instanceof Counter); diff --git a/test/js_wasm/js_interop_counter.wat b/test/js_wasm/js_interop_counter.wat new file mode 100644 index 00000000000..781816829ef --- /dev/null +++ b/test/js_wasm/js_interop_counter.wat @@ -0,0 +1,105 @@ +;; https://github.com/WebAssembly/custom-descriptors/blob/main/proposals/custom-descriptors/Overview.md + +(module + (rec + (type $counter (descriptor $counter.vtable) (struct (field $val (mut i32)))) + (type $counter.vtable (describes $counter) (struct + (field $proto (ref extern)) + (field $get (ref $get_t)) + (field $inc (ref $inc_t)) + )) + (type $get_t (func (param (ref null $counter)) (result i32))) + (type $inc_t (func (param (ref null $counter)))) + ) + (type $new_t (func (param i32) (result (ref $counter)))) + + ;; Types for prototype configuration + (type $prototypes (array (mut externref))) + (type $functions (array (mut funcref))) + (type $data (array (mut i8))) + (type $configureAll (func (param (ref null $prototypes)) + (param (ref null $functions)) + (param (ref null $data)) + (param externref))) + + (import "protos" "counter.proto" (global $counter.proto (ref extern))) + + ;; The object where configured constructors will be installed. + (import "env" "constructors" (global $constructors externref)) + + (import "wasm:js-prototypes" "configureAll" + (func $configureAll (type $configureAll))) + + ;; Segments used to create arrays passed to $configureAll + (elem $prototypes externref + (global.get $counter.proto) + ) + (elem $functions funcref + (ref.func $counter.new) + (ref.func $counter.get) + (ref.func $counter.inc) + ) + ;; \01 one protoconfig + ;; \01 one constructorconfig + ;; \07 length of name "Counter" + ;; Counter constructor name + ;; \00 no static methods + ;; \02 two methodconfigs + ;; \00 method (not getter or setter) + ;; \03 length of name "get" + ;; get method name + ;; \00 method (not getter or setter) + ;; \03 length of name "inc" + ;; inc method name + ;; \7f no parent prototype (-1 s32) + (data $data "\01\01\07Counter\00\02\00\03get\00\03inc\7f") + + (global $counter.vtable (ref (exact $counter.vtable)) + (struct.new $counter.vtable + (global.get $counter.proto) + (ref.func $counter.get) + (ref.func $counter.inc) + ) + ) + + (func $counter.get (type $get_t) (param (ref null $counter)) (result i32) + (struct.get $counter $val (local.get 0)) + ) + + (func $counter.inc (type $inc_t) (param (ref null $counter)) + (struct.set $counter $val + (local.get 0) + (i32.add + (struct.get $counter $val (local.get 0)) + (i32.const 1) + ) + ) + ) + + (func $counter.new (type $new_t) (param i32) (result (ref $counter)) + (struct.new_desc $counter + (local.get 0) + (global.get $counter.vtable) + ) + ) + + (func $start + (call $configureAll + (array.new_elem $prototypes $prototypes + (i32.const 0) + (i32.const 1) + ) + (array.new_elem $functions $functions + (i32.const 0) + (i32.const 3) + ) + (array.new_data $data $data + (i32.const 0) + (i32.const 23) + ) + (global.get $constructors) + ) + ) + + (start $start) +)