From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- third_party/wasm2c/src/test-interp.cc | 723 ++++++++++++++++++++++++++++++++++ 1 file changed, 723 insertions(+) create mode 100644 third_party/wasm2c/src/test-interp.cc (limited to 'third_party/wasm2c/src/test-interp.cc') diff --git a/third_party/wasm2c/src/test-interp.cc b/third_party/wasm2c/src/test-interp.cc new file mode 100644 index 0000000000..11e4770fca --- /dev/null +++ b/third_party/wasm2c/src/test-interp.cc @@ -0,0 +1,723 @@ +/* + * Copyright 2020 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gtest/gtest.h" + +#include "wabt/binary-reader.h" +#include "wabt/error-formatter.h" + +#include "wabt/interp/binary-reader-interp.h" +#include "wabt/interp/interp.h" + +using namespace wabt; +using namespace wabt::interp; + +class InterpTest : public ::testing::Test { + public: + void ReadModule(const std::vector& data) { + Errors errors; + ReadBinaryOptions options; + Result result = ReadBinaryInterp("", data.data(), data.size(), + options, &errors, &module_desc_); + ASSERT_EQ(Result::Ok, result) + << FormatErrorsToString(errors, Location::Type::Binary); + } + + void Instantiate(const RefVec& imports = RefVec{}) { + mod_ = Module::New(store_, module_desc_); + RefPtr trap; + inst_ = Instance::Instantiate(store_, mod_.ref(), imports, &trap); + ASSERT_TRUE(inst_) << trap->message(); + } + + DefinedFunc::Ptr GetFuncExport(interp::Index index) { + EXPECT_LT(index, inst_->exports().size()); + return store_.UnsafeGet(inst_->exports()[index]); + } + + void ExpectBufferStrEq(OutputBuffer& buf, const char* str) { + std::string buf_str(buf.data.begin(), buf.data.end()); + EXPECT_STREQ(buf_str.c_str(), str); + } + + Store store_; + ModuleDesc module_desc_; + Module::Ptr mod_; + Instance::Ptr inst_; +}; + +TEST_F(InterpTest, Empty) { + ASSERT_TRUE(mod_.empty()); + ReadModule({0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00}); + Instantiate(); + ASSERT_FALSE(mod_.empty()); +} + +TEST_F(InterpTest, MVP) { + // (module + // (type (;0;) (func (param i32) (result i32))) + // (type (;1;) (func (param f32) (result f32))) + // (type (;2;) (func)) + // (import "foo" "bar" (func (;0;) (type 0))) + // (func (;1;) (type 1) (param f32) (result f32) + // (f32.const 0x1.5p+5 (;=42;))) + // (func (;2;) (type 2)) + // (table (;0;) 1 2 funcref) + // (memory (;0;) 1) + // (global (;0;) i32 (i32.const 1)) + // (export "quux" (func 1)) + // (start 2) + // (elem (;0;) (i32.const 0) 0 1) + // (data (;0;) (i32.const 2) "hello")) + ReadModule({ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0e, 0x03, 0x60, + 0x01, 0x7f, 0x01, 0x7f, 0x60, 0x01, 0x7d, 0x01, 0x7d, 0x60, 0x00, 0x00, + 0x02, 0x0b, 0x01, 0x03, 0x66, 0x6f, 0x6f, 0x03, 0x62, 0x61, 0x72, 0x00, + 0x00, 0x03, 0x03, 0x02, 0x01, 0x02, 0x04, 0x05, 0x01, 0x70, 0x01, 0x01, + 0x02, 0x05, 0x03, 0x01, 0x00, 0x01, 0x06, 0x06, 0x01, 0x7f, 0x00, 0x41, + 0x01, 0x0b, 0x07, 0x08, 0x01, 0x04, 0x71, 0x75, 0x75, 0x78, 0x00, 0x01, + 0x08, 0x01, 0x02, 0x09, 0x08, 0x01, 0x00, 0x41, 0x00, 0x0b, 0x02, 0x00, + 0x01, 0x0a, 0x0c, 0x02, 0x07, 0x00, 0x43, 0x00, 0x00, 0x28, 0x42, 0x0b, + 0x02, 0x00, 0x0b, 0x0b, 0x0b, 0x01, 0x00, 0x41, 0x02, 0x0b, 0x05, 0x68, + 0x65, 0x6c, 0x6c, 0x6f, + }); + + EXPECT_EQ(3u, module_desc_.func_types.size()); + EXPECT_EQ(1u, module_desc_.imports.size()); + EXPECT_EQ(2u, module_desc_.funcs.size()); + EXPECT_EQ(1u, module_desc_.tables.size()); + EXPECT_EQ(1u, module_desc_.memories.size()); + EXPECT_EQ(1u, module_desc_.globals.size()); + EXPECT_EQ(0u, module_desc_.tags.size()); + EXPECT_EQ(1u, module_desc_.exports.size()); + EXPECT_EQ(1u, module_desc_.starts.size()); + EXPECT_EQ(1u, module_desc_.elems.size()); + EXPECT_EQ(1u, module_desc_.datas.size()); +} + +namespace { + +// (func (export "fac") (param $n i32) (result i32) +// (local $result i32) +// (local.set $result (i32.const 1)) +// (loop (result i32) +// (local.set $result +// (i32.mul +// (br_if 1 (local.get $result) (i32.eqz (local.get $n))) +// (local.get $n))) +// (local.set $n (i32.sub (local.get $n) (i32.const 1))) +// (br 0))) +const std::vector s_fac_module = { + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x06, 0x01, + 0x60, 0x01, 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07, 0x07, + 0x01, 0x03, 0x66, 0x61, 0x63, 0x00, 0x00, 0x0a, 0x22, 0x01, 0x20, + 0x01, 0x01, 0x7f, 0x41, 0x01, 0x21, 0x01, 0x03, 0x7f, 0x20, 0x01, + 0x20, 0x00, 0x45, 0x0d, 0x01, 0x20, 0x00, 0x6c, 0x21, 0x01, 0x20, + 0x00, 0x41, 0x01, 0x6b, 0x21, 0x00, 0x0c, 0x00, 0x0b, 0x0b, +}; + +} // namespace + +TEST_F(InterpTest, Disassemble) { + ReadModule(s_fac_module); + + MemoryStream stream; + module_desc_.istream.Disassemble(&stream); + auto buf = stream.ReleaseOutputBuffer(); + + ExpectBufferStrEq(*buf, +R"( 0| alloca 1 + 8| i32.const 1 + 16| local.set $2, %[-1] + 24| local.get $1 + 32| local.get $3 + 40| i32.eqz %[-1] + 44| br_unless @60, %[-1] + 52| br @116 + 60| local.get $3 + 68| i32.mul %[-2], %[-1] + 72| local.set $2, %[-1] + 80| local.get $2 + 88| i32.const 1 + 96| i32.sub %[-2], %[-1] + 100| local.set $3, %[-1] + 108| br @24 + 116| drop_keep $2 $1 + 128| return +)"); +} + +TEST_F(InterpTest, Fac) { + ReadModule(s_fac_module); + Instantiate(); + auto func = GetFuncExport(0); + + Values results; + Trap::Ptr trap; + Result result = func->Call(store_, {Value::Make(5)}, results, &trap); + + ASSERT_EQ(Result::Ok, result); + EXPECT_EQ(1u, results.size()); + EXPECT_EQ(120u, results[0].Get()); +} + +TEST_F(InterpTest, Fac_Trace) { + ReadModule(s_fac_module); + Instantiate(); + auto func = GetFuncExport(0); + + Values results; + Trap::Ptr trap; + MemoryStream stream; + Result result = func->Call(store_, {Value::Make(2)}, results, &trap, &stream); + ASSERT_EQ(Result::Ok, result); + + auto buf = stream.ReleaseOutputBuffer(); + ExpectBufferStrEq(*buf, +R"(#0. 0: V:1 | alloca 1 +#0. 8: V:2 | i32.const 1 +#0. 16: V:3 | local.set $2, 1 +#0. 24: V:2 | local.get $1 +#0. 32: V:3 | local.get $3 +#0. 40: V:4 | i32.eqz 2 +#0. 44: V:4 | br_unless @60, 0 +#0. 60: V:3 | local.get $3 +#0. 68: V:4 | i32.mul 1, 2 +#0. 72: V:3 | local.set $2, 2 +#0. 80: V:2 | local.get $2 +#0. 88: V:3 | i32.const 1 +#0. 96: V:4 | i32.sub 2, 1 +#0. 100: V:3 | local.set $3, 1 +#0. 108: V:2 | br @24 +#0. 24: V:2 | local.get $1 +#0. 32: V:3 | local.get $3 +#0. 40: V:4 | i32.eqz 1 +#0. 44: V:4 | br_unless @60, 0 +#0. 60: V:3 | local.get $3 +#0. 68: V:4 | i32.mul 2, 1 +#0. 72: V:3 | local.set $2, 2 +#0. 80: V:2 | local.get $2 +#0. 88: V:3 | i32.const 1 +#0. 96: V:4 | i32.sub 1, 1 +#0. 100: V:3 | local.set $3, 0 +#0. 108: V:2 | br @24 +#0. 24: V:2 | local.get $1 +#0. 32: V:3 | local.get $3 +#0. 40: V:4 | i32.eqz 0 +#0. 44: V:4 | br_unless @60, 1 +#0. 52: V:3 | br @116 +#0. 116: V:3 | drop_keep $2 $1 +#0. 128: V:1 | return +)"); +} + +TEST_F(InterpTest, Local_Trace) { + // (func (export "a") + // (local i32 i64 f32 f64) + // (local.set 0 (i32.const 0)) + // (local.set 1 (i64.const 1)) + // (local.set 2 (f32.const 2)) + // (local.set 3 (f64.const 3))) + ReadModule({ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x04, 0x01, + 0x60, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00, 0x07, 0x05, 0x01, 0x01, + 0x61, 0x00, 0x00, 0x0a, 0x26, 0x01, 0x24, 0x04, 0x01, 0x7f, 0x01, + 0x7e, 0x01, 0x7d, 0x01, 0x7c, 0x41, 0x00, 0x21, 0x00, 0x42, 0x01, + 0x21, 0x01, 0x43, 0x00, 0x00, 0x00, 0x40, 0x21, 0x02, 0x44, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x40, 0x21, 0x03, 0x0b, + }); + + Instantiate(); + auto func = GetFuncExport(0); + + Values results; + Trap::Ptr trap; + MemoryStream stream; + Result result = func->Call(store_, {}, results, &trap, &stream); + ASSERT_EQ(Result::Ok, result); + + auto buf = stream.ReleaseOutputBuffer(); + ExpectBufferStrEq(*buf, +R"(#0. 0: V:0 | alloca 4 +#0. 8: V:4 | i32.const 0 +#0. 16: V:5 | local.set $5, 0 +#0. 24: V:4 | i64.const 1 +#0. 36: V:5 | local.set $4, 1 +#0. 44: V:4 | f32.const 2 +#0. 52: V:5 | local.set $3, 2 +#0. 60: V:4 | f64.const 3 +#0. 72: V:5 | local.set $2, 3 +#0. 80: V:4 | drop_keep $4 $0 +#0. 92: V:0 | return +)"); +} + +TEST_F(InterpTest, HostFunc) { + // (import "" "f" (func $f (param i32) (result i32))) + // (func (export "g") (result i32) + // (call $f (i32.const 1))) + ReadModule({ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0a, + 0x02, 0x60, 0x01, 0x7f, 0x01, 0x7f, 0x60, 0x00, 0x01, 0x7f, + 0x02, 0x06, 0x01, 0x00, 0x01, 0x66, 0x00, 0x00, 0x03, 0x02, + 0x01, 0x01, 0x07, 0x05, 0x01, 0x01, 0x67, 0x00, 0x01, 0x0a, + 0x08, 0x01, 0x06, 0x00, 0x41, 0x01, 0x10, 0x00, 0x0b, + }); + + auto host_func = + HostFunc::New(store_, FuncType{{ValueType::I32}, {ValueType::I32}}, + [](Thread& thread, const Values& params, Values& results, + Trap::Ptr* out_trap) -> Result { + results[0] = Value::Make(params[0].Get() + 1); + return Result::Ok; + }); + + Instantiate({host_func->self()}); + + Values results; + Trap::Ptr trap; + Result result = GetFuncExport(0)->Call(store_, {}, results, &trap); + + ASSERT_EQ(Result::Ok, result); + EXPECT_EQ(1u, results.size()); + EXPECT_EQ(2u, results[0].Get()); +} + +TEST_F(InterpTest, HostFunc_PingPong) { + // (import "" "f" (func $f (param i32) (result i32))) + // (func (export "g") (param i32) (result i32) + // (call $f (i32.add (local.get 0) (i32.const 1)))) + ReadModule({ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x06, 0x01, 0x60, + 0x01, 0x7f, 0x01, 0x7f, 0x02, 0x06, 0x01, 0x00, 0x01, 0x66, 0x00, 0x00, + 0x03, 0x02, 0x01, 0x00, 0x07, 0x05, 0x01, 0x01, 0x67, 0x00, 0x01, 0x0a, + 0x0b, 0x01, 0x09, 0x00, 0x20, 0x00, 0x41, 0x01, 0x6a, 0x10, 0x00, 0x0b, + }); + + auto host_func = + HostFunc::New(store_, FuncType{{ValueType::I32}, {ValueType::I32}}, + [&](Thread& thread, const Values& params, Values& results, + Trap::Ptr* out_trap) -> Result { + auto val = params[0].Get(); + if (val < 10) { + return GetFuncExport(0)->Call( + store_, {Value::Make(val * 2)}, results, out_trap); + } + results[0] = Value::Make(val); + return Result::Ok; + }); + + Instantiate({host_func->self()}); + + // Should produce the following calls: + // g(1) -> f(2) -> g(4) -> f(5) -> g(10) -> f(11) -> return 11 + + Values results; + Trap::Ptr trap; + Result result = + GetFuncExport(0)->Call(store_, {Value::Make(1)}, results, &trap); + + ASSERT_EQ(Result::Ok, result); + EXPECT_EQ(1u, results.size()); + EXPECT_EQ(11u, results[0].Get()); +} + +TEST_F(InterpTest, HostFunc_PingPong_SameThread) { + // (import "" "f" (func $f (param i32) (result i32))) + // (func (export "g") (param i32) (result i32) + // (call $f (i32.add (local.get 0) (i32.const 1)))) + ReadModule({ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x06, 0x01, 0x60, + 0x01, 0x7f, 0x01, 0x7f, 0x02, 0x06, 0x01, 0x00, 0x01, 0x66, 0x00, 0x00, + 0x03, 0x02, 0x01, 0x00, 0x07, 0x05, 0x01, 0x01, 0x67, 0x00, 0x01, 0x0a, + 0x0b, 0x01, 0x09, 0x00, 0x20, 0x00, 0x41, 0x01, 0x6a, 0x10, 0x00, 0x0b, + }); + + Thread thread(store_); + + auto host_func = + HostFunc::New(store_, FuncType{{ValueType::I32}, {ValueType::I32}}, + [&](Thread& t, const Values& params, Values& results, + Trap::Ptr* out_trap) -> Result { + auto val = params[0].Get(); + if (val < 10) { + return GetFuncExport(0)->Call(t, {Value::Make(val * 2)}, + results, out_trap); + } + results[0] = Value::Make(val); + return Result::Ok; + }); + + Instantiate({host_func->self()}); + + // Should produce the following calls: + // g(1) -> f(2) -> g(4) -> f(5) -> g(10) -> f(11) -> return 11 + + Values results; + Trap::Ptr trap; + Result result = + GetFuncExport(0)->Call(thread, {Value::Make(1)}, results, &trap); + + ASSERT_EQ(Result::Ok, result); + EXPECT_EQ(1u, results.size()); + EXPECT_EQ(11u, results[0].Get()); +} + +TEST_F(InterpTest, HostTrap) { + // (import "host" "a" (func $0)) + // (func $1 call $0) + // (start $1) + ReadModule({ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x04, 0x01, + 0x60, 0x00, 0x00, 0x02, 0x0a, 0x01, 0x04, 0x68, 0x6f, 0x73, 0x74, + 0x01, 0x61, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00, 0x08, 0x01, 0x01, + 0x0a, 0x06, 0x01, 0x04, 0x00, 0x10, 0x00, 0x0b, + }); + + auto host_func = + HostFunc::New(store_, FuncType{{}, {}}, + [&](Thread& thread, const Values& params, Values& results, + Trap::Ptr* out_trap) -> Result { + *out_trap = Trap::New(store_, "boom"); + return Result::Error; + }); + + mod_ = Module::New(store_, module_desc_); + RefPtr trap; + Instance::Instantiate(store_, mod_.ref(), {host_func->self()}, &trap); + + ASSERT_TRUE(trap); + ASSERT_EQ("boom", trap->message()); +} + +TEST_F(InterpTest, Rot13) { + // (import "host" "mem" (memory $mem 1)) + // (import "host" "fill_buf" (func $fill_buf (param i32 i32) (result i32))) + // (import "host" "buf_done" (func $buf_done (param i32 i32))) + // + // (func $rot13c (param $c i32) (result i32) + // (local $uc i32) + // + // ;; No change if < 'A'. + // (if (i32.lt_u (local.get $c) (i32.const 65)) + // (return (local.get $c))) + // + // ;; Clear 5th bit of c, to force uppercase. 0xdf = 0b11011111 + // (local.set $uc (i32.and (local.get $c) (i32.const 0xdf))) + // + // ;; In range ['A', 'M'] return |c| + 13. + // (if (i32.le_u (local.get $uc) (i32.const 77)) + // (return (i32.add (local.get $c) (i32.const 13)))) + // + // ;; In range ['N', 'Z'] return |c| - 13. + // (if (i32.le_u (local.get $uc) (i32.const 90)) + // (return (i32.sub (local.get $c) (i32.const 13)))) + // + // ;; No change for everything else. + // (return (local.get $c)) + // ) + // + // (func (export "rot13") + // (local $size i32) + // (local $i i32) + // + // ;; Ask host to fill memory [0, 1024) with data. + // (call $fill_buf (i32.const 0) (i32.const 1024)) + // + // ;; The host returns the size filled. + // (local.set $size) + // + // ;; Loop over all bytes and rot13 them. + // (block $exit + // (loop $top + // ;; if (i >= size) break + // (if (i32.ge_u (local.get $i) (local.get $size)) (br $exit)) + // + // ;; mem[i] = rot13c(mem[i]) + // (i32.store8 + // (local.get $i) + // (call $rot13c + // (i32.load8_u (local.get $i)))) + // + // ;; i++ + // (local.set $i (i32.add (local.get $i) (i32.const 1))) + // (br $top) + // ) + // ) + // + // (call $buf_done (i32.const 0) (local.get $size)) + // ) + ReadModule({ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x14, 0x04, 0x60, + 0x02, 0x7f, 0x7f, 0x01, 0x7f, 0x60, 0x02, 0x7f, 0x7f, 0x00, 0x60, 0x01, + 0x7f, 0x01, 0x7f, 0x60, 0x00, 0x00, 0x02, 0x2d, 0x03, 0x04, 0x68, 0x6f, + 0x73, 0x74, 0x03, 0x6d, 0x65, 0x6d, 0x02, 0x00, 0x01, 0x04, 0x68, 0x6f, + 0x73, 0x74, 0x08, 0x66, 0x69, 0x6c, 0x6c, 0x5f, 0x62, 0x75, 0x66, 0x00, + 0x00, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x08, 0x62, 0x75, 0x66, 0x5f, 0x64, + 0x6f, 0x6e, 0x65, 0x00, 0x01, 0x03, 0x03, 0x02, 0x02, 0x03, 0x07, 0x09, + 0x01, 0x05, 0x72, 0x6f, 0x74, 0x31, 0x33, 0x00, 0x03, 0x0a, 0x74, 0x02, + 0x39, 0x01, 0x01, 0x7f, 0x20, 0x00, 0x41, 0xc1, 0x00, 0x49, 0x04, 0x40, + 0x20, 0x00, 0x0f, 0x0b, 0x20, 0x00, 0x41, 0xdf, 0x01, 0x71, 0x21, 0x01, + 0x20, 0x01, 0x41, 0xcd, 0x00, 0x4d, 0x04, 0x40, 0x20, 0x00, 0x41, 0x0d, + 0x6a, 0x0f, 0x0b, 0x20, 0x01, 0x41, 0xda, 0x00, 0x4d, 0x04, 0x40, 0x20, + 0x00, 0x41, 0x0d, 0x6b, 0x0f, 0x0b, 0x20, 0x00, 0x0f, 0x0b, 0x38, 0x01, + 0x02, 0x7f, 0x41, 0x00, 0x41, 0x80, 0x08, 0x10, 0x00, 0x21, 0x00, 0x02, + 0x40, 0x03, 0x40, 0x20, 0x01, 0x20, 0x00, 0x4f, 0x04, 0x40, 0x0c, 0x02, + 0x0b, 0x20, 0x01, 0x20, 0x01, 0x2d, 0x00, 0x00, 0x10, 0x02, 0x3a, 0x00, + 0x00, 0x20, 0x01, 0x41, 0x01, 0x6a, 0x21, 0x01, 0x0c, 0x00, 0x0b, 0x0b, + 0x41, 0x00, 0x20, 0x00, 0x10, 0x01, 0x0b, + }); + + auto host_func = + HostFunc::New(store_, FuncType{{ValueType::I32}, {ValueType::I32}}, + [](Thread& thread, const Values& params, Values& results, + Trap::Ptr* out_trap) -> Result { + results[0] = Value::Make(params[0].Get() + 1); + return Result::Ok; + }); + + std::string string_data = "Hello, WebAssembly!"; + + auto memory = Memory::New(store_, MemoryType{Limits{1}}); + + auto fill_buf = [&](Thread& thread, const Values& params, Values& results, + Trap::Ptr* out_trap) -> Result { + // (param $ptr i32) (param $max_size i32) (result $size i32) + EXPECT_EQ(2u, params.size()); + EXPECT_EQ(1u, results.size()); + + u32 ptr = params[0].Get(); + u32 max_size = params[1].Get(); + u32 size = std::min(max_size, u32(string_data.size())); + + EXPECT_LT(ptr + size, memory->ByteSize()); + + std::copy(string_data.begin(), string_data.begin() + size, + memory->UnsafeData() + ptr); + + results[0].Set(size); + return Result::Ok; + }; + auto fill_buf_func = HostFunc::New( + store_, FuncType{{ValueType::I32, ValueType::I32}, {ValueType::I32}}, + fill_buf); + + auto buf_done = [&](Thread& thread, const Values& params, Values& results, + Trap::Ptr* out_trap) -> Result { + // (param $ptr i32) (param $size i32) + EXPECT_EQ(2u, params.size()); + EXPECT_EQ(0u, results.size()); + + u32 ptr = params[0].Get(); + u32 size = params[1].Get(); + + EXPECT_LT(ptr + size, memory->ByteSize()); + + string_data.resize(size); + std::copy(memory->UnsafeData() + ptr, memory->UnsafeData() + ptr + size, + string_data.begin()); + + return Result::Ok; + }; + auto buf_done_func = HostFunc::New( + store_, FuncType{{ValueType::I32, ValueType::I32}, {}}, buf_done); + + Instantiate({memory->self(), fill_buf_func->self(), buf_done_func->self()}); + + auto rot13 = GetFuncExport(0); + + Values results; + Trap::Ptr trap; + ASSERT_EQ(Result::Ok, rot13->Call(store_, {}, results, &trap)); + + ASSERT_EQ("Uryyb, JroNffrzoyl!", string_data); + + ASSERT_EQ(Result::Ok, rot13->Call(store_, {}, results, &trap)); + + ASSERT_EQ("Hello, WebAssembly!", string_data); +} + +class InterpGCTest : public InterpTest { + public: + void SetUp() override { before_new = store_.object_count(); } + + void TearDown() override { + // Reset instance and module, in case they were allocated. + inst_.reset(); + mod_.reset(); + + store_.Collect(); + EXPECT_EQ(before_new, store_.object_count()); + } + + size_t before_new; +}; + +TEST_F(InterpGCTest, Collect_Basic) { + auto foreign = Foreign::New(store_, nullptr); + auto after_new = store_.object_count(); + EXPECT_EQ(before_new + 1, after_new); + + // Remove root, but object is not destroyed until collect. + foreign.reset(); + EXPECT_EQ(after_new, store_.object_count()); +} + +TEST_F(InterpGCTest, Collect_GlobalCycle) { + auto gt = GlobalType{ValueType::ExternRef, Mutability::Var}; + auto g1 = Global::New(store_, gt, Value::Make(Ref::Null)); + auto g2 = Global::New(store_, gt, Value::Make(g1->self())); + g1->Set(store_, g2->self()); + + auto after_new = store_.object_count(); + EXPECT_EQ(before_new + 2, after_new); + + // Remove g1 root, but it's kept alive by g2. + g1.reset(); + store_.Collect(); + EXPECT_EQ(after_new, store_.object_count()); + + // Remove g2 root, now both should be removed. + g2.reset(); +} + +TEST_F(InterpGCTest, Collect_TableCycle) { + auto tt = TableType{ValueType::ExternRef, Limits{2}}; + auto t1 = Table::New(store_, tt); + auto t2 = Table::New(store_, tt); + auto t3 = Table::New(store_, tt); + + t1->Set(store_, 0, t1->self()); // t1 references itself. + t2->Set(store_, 0, t3->self()); + t3->Set(store_, 0, t2->self()); // t2 and t3 reference each other. + t3->Set(store_, 1, t1->self()); // t3 also references t1. + + auto after_new = store_.object_count(); + EXPECT_EQ(before_new + 3, after_new); + + // Remove t1 and t2 roots, but their kept alive by t3. + t1.reset(); + t2.reset(); + store_.Collect(); + EXPECT_EQ(after_new, store_.object_count()); + + // Remove t3 root, now all should be removed. + t3.reset(); +} + +TEST_F(InterpGCTest, Collect_Func) { + ReadModule(s_fac_module); + Instantiate(); + auto func = GetFuncExport(0); + + auto after_new = store_.object_count(); + EXPECT_EQ(before_new + 3, after_new); // module, instance, func. + + // Reset module and instance roots, but they'll be kept alive by the func. + mod_.reset(); + inst_.reset(); + store_.Collect(); + EXPECT_EQ(after_new, store_.object_count()); +} + +TEST_F(InterpGCTest, Collect_InstanceImport) { + // (import "" "f" (func)) + // (import "" "t" (table 0 funcref)) + // (import "" "m" (memory 0)) + // (import "" "g" (global i32)) + ReadModule({ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x04, 0x01, + 0x60, 0x00, 0x00, 0x02, 0x19, 0x04, 0x00, 0x01, 0x66, 0x00, 0x00, + 0x00, 0x01, 0x74, 0x01, 0x70, 0x00, 0x00, 0x00, 0x01, 0x6d, 0x02, + 0x00, 0x00, 0x00, 0x01, 0x67, 0x03, 0x7f, 0x00, + }); + auto f = HostFunc::New(store_, FuncType{{}, {}}, + [](Thread& thread, const Values&, Values&, + Trap::Ptr*) -> Result { return Result::Ok; }); + auto t = Table::New(store_, TableType{ValueType::FuncRef, Limits{0}}); + auto m = Memory::New(store_, MemoryType{Limits{0}}); + auto g = Global::New(store_, GlobalType{ValueType::I32, Mutability::Const}, + Value::Make(5)); + + Instantiate({f->self(), t->self(), m->self(), g->self()}); + auto after_new = store_.object_count(); + EXPECT_EQ(before_new + 6, after_new); // module, instance, f, t, m, g + + // Instance keeps all imports alive. + f.reset(); + t.reset(); + m.reset(); + g.reset(); + store_.Collect(); + EXPECT_EQ(after_new, store_.object_count()); +} + +TEST_F(InterpGCTest, Collect_InstanceExport) { + // (func (export "f")) + // (global (export "g") i32 (i32.const 0)) + // (table (export "t") 0 funcref) + // (memory (export "m") 0) + ReadModule({ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x04, 0x01, + 0x60, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00, 0x04, 0x04, 0x01, 0x70, + 0x00, 0x00, 0x05, 0x03, 0x01, 0x00, 0x00, 0x06, 0x06, 0x01, 0x7f, + 0x00, 0x41, 0x00, 0x0b, 0x07, 0x11, 0x04, 0x01, 0x66, 0x00, 0x00, + 0x01, 0x67, 0x03, 0x00, 0x01, 0x74, 0x01, 0x00, 0x01, 0x6d, 0x02, + 0x00, 0x0a, 0x04, 0x01, 0x02, 0x00, 0x0b, + }); + Instantiate(); + auto after_new = store_.object_count(); + EXPECT_EQ(before_new + 7, + after_new); // module, instance, f, t, m, g, g-init-func + + // Instance keeps all exports alive, except the init func which can be + // collected once its has been run + store_.Collect(); + EXPECT_EQ(after_new - 1, store_.object_count()); +} + +TEST_F(InterpGCTest, Collect_DeepRecursion) { + const size_t table_count = 65; + + TableType tt = TableType{ValueType::ExternRef, Limits{1}}; + + // Create a chain of tables, where each contains + // a single reference to the next table. + + Table::Ptr prev_table = Table::New(store_, tt); + + for (size_t i = 1; i < table_count; i++) { + Table::Ptr new_table = Table::New(store_, tt); + + new_table->Set(store_, 0, prev_table->self()); + + prev_table.reset(); + prev_table = std::move(new_table); + } + + store_.Collect(); + EXPECT_EQ(table_count + 1, store_.object_count()); + + // Remove the last root, now all should be removed. + prev_table.reset(); + + store_.Collect(); + EXPECT_EQ(1u, store_.object_count()); +} + +// TODO: Test for Thread keeping references alive as locals/params/stack values. +// This requires better tracking of references than currently exists in the +// interpreter. (see TODOs in Select/LocalGet/GlobalGet) -- cgit v1.2.3