/*
* 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<u8>& data) {
Errors errors;
ReadBinaryOptions options;
Result result = ReadBinaryInterp(
"<internal>" , 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> 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<DefinedFunc>(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({
0 x00,
0 x61,
0 x73,
0 x6d,
0 x01,
0 x00,
0 x00,
0 x00});
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({
0 x00,
0 x61,
0 x73,
0 x6d,
0 x01,
0 x00,
0 x00,
0 x00,
0 x01,
0 x0e,
0 x03,
0 x60,
0 x01,
0 x7f,
0 x01,
0 x7f,
0 x60,
0 x01,
0 x7d,
0 x01,
0 x7d,
0 x60,
0 x00,
0 x00,
0 x02,
0 x0b,
0 x01,
0 x03,
0 x66,
0 x6f,
0 x6f,
0 x03,
0 x62,
0 x61,
0 x72,
0 x00,
0 x00,
0 x03,
0 x03,
0 x02,
0 x01,
0 x02,
0 x04,
0 x05,
0 x01,
0 x70,
0 x01,
0 x01,
0 x02,
0 x05,
0 x03,
0 x01,
0 x00,
0 x01,
0 x06,
0 x06,
0 x01,
0 x7f,
0 x00,
0 x41,
0 x01,
0 x0b,
0 x07,
0 x08,
0 x01,
0 x04,
0 x71,
0 x75,
0 x75,
0 x78,
0 x00,
0 x01,
0 x08,
0 x01,
0 x02,
0 x09,
0 x08,
0 x01,
0 x00,
0 x41,
0 x00,
0 x0b,
0 x02,
0 x00,
0 x01,
0 x0a,
0 x0c,
0 x02,
0 x07,
0 x00,
0 x43,
0 x00,
0 x00,
0 x28,
0 x42,
0 x0b,
0 x02,
0 x00,
0 x0b,
0 x0b,
0 x0b,
0 x01,
0 x00,
0 x41,
0 x02,
0 x0b,
0 x05,
0 x68,
0 x65,
0 x6c,
0 x6c,
0 x6f,
});
EXPECT_EQ(
3 u, module_desc_.func_types.size());
EXPECT_EQ(
1 u, module_desc_.imports.size());
EXPECT_EQ(
2 u, module_desc_.funcs.size());
EXPECT_EQ(
1 u, module_desc_.tables.size());
EXPECT_EQ(
1 u, module_desc_.memories.size());
EXPECT_EQ(
1 u, module_desc_.globals.size());
EXPECT_EQ(
0 u, module_desc_.tags.size());
EXPECT_EQ(
1 u, module_desc_.exports.size());
EXPECT_EQ(
1 u, module_desc_.starts.size());
EXPECT_EQ(
1 u, module_desc_.elems.size());
EXPECT_EQ(
1 u, 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<u8> s_fac_module = {
0 x00,
0 x61,
0 x73,
0 x6d,
0 x01,
0 x00,
0 x00,
0 x00,
0 x01,
0 x06,
0 x01,
0 x60,
0 x01,
0 x7f,
0 x01,
0 x7f,
0 x03,
0 x02,
0 x01,
0 x00,
0 x07,
0 x07,
0 x01,
0 x03,
0 x66,
0 x61,
0 x63,
0 x00,
0 x00,
0 x0a,
0 x22,
0 x01,
0 x20,
0 x01,
0 x01,
0 x7f,
0 x41,
0 x01,
0 x21,
0 x01,
0 x03,
0 x7f,
0 x20,
0 x01,
0 x20,
0 x00,
0 x45,
0 x0d,
0 x01,
0 x20,
0 x00,
0 x6c,
0 x21,
0 x01,
0 x20,
0 x00,
0 x41,
0 x01,
0 x6b,
0 x21,
0 x00,
0 x0c,
0 x00,
0 x0b,
0 x0b,
};
}
// 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(
1 u, results.size());
EXPECT_EQ(
120 u, results[
0 ].Get<u32>());
}
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({
0 x00,
0 x61,
0 x73,
0 x6d,
0 x01,
0 x00,
0 x00,
0 x00,
0 x01,
0 x04,
0 x01,
0 x60,
0 x00,
0 x00,
0 x03,
0 x02,
0 x01,
0 x00,
0 x07,
0 x05,
0 x01,
0 x01,
0 x61,
0 x00,
0 x00,
0 x0a,
0 x26,
0 x01,
0 x24,
0 x04,
0 x01,
0 x7f,
0 x01,
0 x7e,
0 x01,
0 x7d,
0 x01,
0 x7c,
0 x41,
0 x00,
0 x21,
0 x00,
0 x42,
0 x01,
0 x21,
0 x01,
0 x43,
0 x00,
0 x00,
0 x00,
0 x40,
0 x21,
0 x02,
0 x44,
0 x00,
0 x00,
0 x00,
0 x00,
0 x00,
0 x00,
0 x08,
0 x40,
0 x21,
0 x03,
0 x0b,
});
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({
0 x00,
0 x61,
0 x73,
0 x6d,
0 x01,
0 x00,
0 x00,
0 x00,
0 x01,
0 x0a,
0 x02,
0 x60,
0 x01,
0 x7f,
0 x01,
0 x7f,
0 x60,
0 x00,
0 x01,
0 x7f,
0 x02,
0 x06,
0 x01,
0 x00,
0 x01,
0 x66,
0 x00,
0 x00,
0 x03,
0 x02,
0 x01,
0 x01,
0 x07,
0 x05,
0 x01,
0 x01,
0 x67,
0 x00,
0 x01,
0 x0a,
0 x08,
0 x01,
0 x06,
0 x00,
0 x41,
0 x01,
0 x10,
0 x00,
0 x0b,
});
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<u32>() +
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(
1 u, results.size());
EXPECT_EQ(
2 u, results[
0 ].Get<u32>());
}
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({
0 x00,
0 x61,
0 x73,
0 x6d,
0 x01,
0 x00,
0 x00,
0 x00,
0 x01,
0 x06,
0 x01,
0 x60,
0 x01,
0 x7f,
0 x01,
0 x7f,
0 x02,
0 x06,
0 x01,
0 x00,
0 x01,
0 x66,
0 x00,
0 x00,
0 x03,
0 x02,
0 x01,
0 x00,
0 x07,
0 x05,
0 x01,
0 x01,
0 x67,
0 x00,
0 x01,
0 x0a,
0 x0b,
0 x01,
0 x09,
0 x00,
0 x20,
0 x00,
0 x41,
0 x01,
0 x6a,
0 x10,
0 x00,
0 x0b,
});
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<u32>();
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(
1 u, results.size());
EXPECT_EQ(
11 u, results[
0 ].Get<u32>());
}
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({
0 x00,
0 x61,
0 x73,
0 x6d,
0 x01,
0 x00,
0 x00,
0 x00,
0 x01,
0 x06,
0 x01,
0 x60,
0 x01,
0 x7f,
0 x01,
0 x7f,
0 x02,
0 x06,
0 x01,
0 x00,
0 x01,
0 x66,
0 x00,
0 x00,
0 x03,
0 x02,
0 x01,
0 x00,
0 x07,
0 x05,
0 x01,
0 x01,
0 x67,
0 x00,
0 x01,
0 x0a,
0 x0b,
0 x01,
0 x09,
0 x00,
0 x20,
0 x00,
0 x41,
0 x01,
0 x6a,
0 x10,
0 x00,
0 x0b,
});
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<u32>();
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(
1 u, results.size());
EXPECT_EQ(
11 u, results[
0 ].Get<u32>());
}
TEST_F(InterpTest, HostTrap) {
// (import "host" "a" (func $0))
// (func $1 call $0)
// (start $1)
ReadModule({
0 x00,
0 x61,
0 x73,
0 x6d,
0 x01,
0 x00,
0 x00,
0 x00,
0 x01,
0 x04,
0 x01,
0 x60,
0 x00,
0 x00,
0 x02,
0 x0a,
0 x01,
0 x04,
0 x68,
0 x6f,
0 x73,
0 x74,
0 x01,
0 x61,
0 x00,
0 x00,
0 x03,
0 x02,
0 x01,
0 x00,
0 x08,
0 x01,
0 x01,
0 x0a,
0 x06,
0 x01,
0 x04,
0 x00,
0 x10,
0 x00,
0 x0b,
});
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> 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({
0 x00,
0 x61,
0 x73,
0 x6d,
0 x01,
0 x00,
0 x00,
0 x00,
0 x01,
0 x14,
0 x04,
0 x60,
0 x02,
0 x7f,
0 x7f,
0 x01,
0 x7f,
0 x60,
0 x02,
0 x7f,
0 x7f,
0 x00,
0 x60,
0 x01,
0 x7f,
0 x01,
0 x7f,
0 x60,
0 x00,
0 x00,
0 x02,
0 x2d,
0 x03,
0 x04,
0 x68,
0 x6f,
0 x73,
0 x74,
0 x03,
0 x6d,
0 x65,
0 x6d,
0 x02,
0 x00,
0 x01,
0 x04,
0 x68,
0 x6f,
0 x73,
0 x74,
0 x08,
0 x66,
0 x69,
0 x6c,
0 x6c,
0 x5f,
0 x62,
0 x75,
0 x66,
0 x00,
0 x00,
0 x04,
0 x68,
0 x6f,
0 x73,
0 x74,
0 x08,
0 x62,
0 x75,
0 x66,
0 x5f,
0 x64,
0 x6f,
0 x6e,
0 x65,
0 x00,
0 x01,
0 x03,
0 x03,
0 x02,
0 x02,
0 x03,
0 x07,
0 x09,
0 x01,
0 x05,
0 x72,
0 x6f,
0 x74,
0 x31,
0 x33,
0 x00,
0 x03,
0 x0a,
0 x74,
0 x02,
0 x39,
0 x01,
0 x01,
0 x7f,
0 x20,
0 x00,
0 x41,
0 xc1,
0 x00,
0 x49,
0 x04,
0 x40,
0 x20,
0 x00,
0 x0f,
0 x0b,
0 x20,
0 x00,
0 x41,
0 xdf,
0 x01,
0 x71,
0 x21,
0 x01,
0 x20,
0 x01,
0 x41,
0 xcd,
0 x00,
0 x4d,
0 x04,
0 x40,
0 x20,
0 x00,
0 x41,
0 x0d,
0 x6a,
0 x0f,
0 x0b,
0 x20,
0 x01,
0 x41,
0 xda,
0 x00,
0 x4d,
0 x04,
0 x40,
0 x20,
0 x00,
0 x41,
0 x0d,
0 x6b,
0 x0f,
0 x0b,
0 x20,
0 x00,
0 x0f,
0 x0b,
0 x38,
0 x01,
0 x02,
0 x7f,
0 x41,
0 x00,
0 x41,
0 x80,
0 x08,
0 x10,
0 x00,
0 x21,
0 x00,
0 x02,
0 x40,
0 x03,
0 x40,
0 x20,
0 x01,
0 x20,
0 x00,
0 x4f,
0 x04,
0 x40,
0 x0c,
0 x02,
0 x0b,
0 x20,
0 x01,
0 x20,
0 x01,
0 x2d,
0 x00,
0 x00,
0 x10,
0 x02,
0 x3a,
0 x00,
0 x00,
0 x20,
0 x01,
0 x41,
0 x01,
0 x6a,
0 x21,
0 x01,
0 x0c,
0 x00,
0 x0b,
0 x0b,
0 x41,
0 x00,
0 x20,
0 x00,
0 x10,
0 x01,
0 x0b,
});
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<u32>() +
1 );
return Result::Ok;
});
std::string string_data =
"Hello, WebAssembly!" ;
auto memory =
Memory::
New (store_, MemoryType{Limits{
1 }, WABT_DEFAULT_PAGE_SIZE});
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(
2 u, params.size());
EXPECT_EQ(
1 u, results.size());
u32 ptr = params[
0 ].Get<u32>();
u32 max_size = params[
1 ].Get<u32>();
u32 size = std::min(max_size, u32(string_data.size()));
EXPECT_LT(ptr + size, memory->ByteSize());
#if WABT_BIG_ENDIAN
std::copy(string_data.rbegin(), string_data.rbegin() + size,
memory->UnsafeData() + memory->ByteSize() - ptr - size);
#else
std::copy(string_data.begin(), string_data.begin() + size,
memory->UnsafeData() + ptr);
#endif
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(
2 u, params.size());
EXPECT_EQ(
0 u, results.size());
u32 ptr = params[
0 ].Get<u32>();
u32 size = params[
1 ].Get<u32>();
EXPECT_LT(ptr + size, memory->ByteSize());
string_data.resize(size);
#if WABT_BIG_ENDIAN
std::copy(memory->UnsafeData() + memory->ByteSize() - ptr - size,
memory->UnsafeData() + memory->ByteSize() - ptr,
string_data.rbegin());
#else
std::copy(memory->UnsafeData() + ptr, memory->UnsafeData() + ptr + size,
string_data.begin());
#endif
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({
0 x00,
0 x61,
0 x73,
0 x6d,
0 x01,
0 x00,
0 x00,
0 x00,
0 x01,
0 x04,
0 x01,
0 x60,
0 x00,
0 x00,
0 x02,
0 x19,
0 x04,
0 x00,
0 x01,
0 x66,
0 x00,
0 x00,
0 x00,
0 x01,
0 x74,
0 x01,
0 x70,
0 x00,
0 x00,
0 x00,
0 x01,
0 x6d,
0 x02,
0 x00,
0 x00,
0 x00,
0 x01,
0 x67,
0 x03,
0 x7f,
0 x00,
});
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 }, WABT_DEFAULT_PAGE_SIZE});
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({
0 x00,
0 x61,
0 x73,
0 x6d,
0 x01,
0 x00,
0 x00,
0 x00,
0 x01,
0 x04,
0 x01,
0 x60,
0 x00,
0 x00,
0 x03,
0 x02,
0 x01,
0 x00,
0 x04,
0 x04,
0 x01,
0 x70,
0 x00,
0 x00,
0 x05,
0 x03,
0 x01,
0 x00,
0 x00,
0 x06,
0 x06,
0 x01,
0 x7f,
0 x00,
0 x41,
0 x00,
0 x0b,
0 x07,
0 x11,
0 x04,
0 x01,
0 x66,
0 x00,
0 x00,
0 x01,
0 x67,
0 x03,
0 x00,
0 x01,
0 x74,
0 x01,
0 x00,
0 x01,
0 x6d,
0 x02,
0 x00,
0 x0a,
0 x04,
0 x01,
0 x02,
0 x00,
0 x0b,
});
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(
1 u, 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)
Messung V0.5 in Prozent C=96 H=95 G=95
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-06-04)
¤
*© Formatika GbR, Deutschland