f81430caa9
Since its introduction (behind a compile-time flag), conservative stack scanning was disabled by default on tests. This CL inverts this logic, enabling CSS by default for all tests that do not define an explicit scope to disable it. Bug: v8:13257 Change-Id: I5ea4249d02f69b0b1e195415c2562daf5d8c0ea9 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4100912 Reviewed-by: Dominik Inführ <dinfuehr@chromium.org> Commit-Queue: Nikolaos Papaspyrou <nikolaos@chromium.org> Cr-Commit-Position: refs/heads/main@{#84848}
334 lines
13 KiB
C++
334 lines
13 KiB
C++
// Copyright 2019 the V8 project authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "test/wasm-api-tests/wasm-api-test.h"
|
|
|
|
#include "src/execution/isolate.h"
|
|
#include "src/heap/heap.h"
|
|
#include "src/wasm/c-api.h"
|
|
|
|
namespace v8 {
|
|
namespace internal {
|
|
namespace wasm {
|
|
|
|
namespace {
|
|
|
|
own<Trap> Stage2(void* env, const Val args[], Val results[]) {
|
|
printf("Stage2...\n");
|
|
WasmCapiTest* self = reinterpret_cast<WasmCapiTest*>(env);
|
|
Func* stage3 = self->GetExportedFunction(1);
|
|
own<Trap> trap = stage3->call(args, results);
|
|
if (trap) {
|
|
printf("Stage2: got exception: %s\n", trap->message().get());
|
|
} else {
|
|
printf("Stage2: call successful\n");
|
|
}
|
|
return trap;
|
|
}
|
|
|
|
own<Trap> Stage4_GC(void* env, const Val args[], Val results[]) {
|
|
printf("Stage4...\n");
|
|
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(env);
|
|
isolate->heap()->PreciseCollectAllGarbage(Heap::kForcedGC,
|
|
GarbageCollectionReason::kTesting);
|
|
results[0] = Val::i32(args[0].i32() + 1);
|
|
return nullptr;
|
|
}
|
|
|
|
class WasmCapiCallbacksTest : public WasmCapiTest {
|
|
public:
|
|
WasmCapiCallbacksTest() : WasmCapiTest() {
|
|
// Build the following function:
|
|
// int32 stage1(int32 arg0) { return stage2(arg0); }
|
|
uint32_t stage2_index =
|
|
builder()->AddImport(base::CStrVector("stage2"), wasm_i_i_sig());
|
|
byte code[] = {WASM_CALL_FUNCTION(stage2_index, WASM_LOCAL_GET(0))};
|
|
AddExportedFunction(base::CStrVector("stage1"), code, sizeof(code));
|
|
|
|
stage2_ = Func::make(store(), cpp_i_i_sig(), Stage2, this);
|
|
}
|
|
|
|
Func* stage2() { return stage2_.get(); }
|
|
void AddExportedFunction(base::Vector<const char> name, byte code[],
|
|
size_t code_size) {
|
|
WasmCapiTest::AddExportedFunction(name, code, code_size, wasm_i_i_sig());
|
|
}
|
|
|
|
private:
|
|
own<Func> stage2_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST_F(WasmCapiCallbacksTest, Trap) {
|
|
// Build the following function:
|
|
// int32 stage3_trap(int32 arg0) { unreachable(); }
|
|
byte code[] = {WASM_UNREACHABLE};
|
|
AddExportedFunction(base::CStrVector("stage3_trap"), code, sizeof(code));
|
|
|
|
Extern* imports[] = {stage2()};
|
|
Instantiate(imports);
|
|
Val args[] = {Val::i32(42)};
|
|
Val results[1];
|
|
own<Trap> trap = GetExportedFunction(0)->call(args, results);
|
|
EXPECT_NE(trap, nullptr);
|
|
printf("Stage0: Got trap as expected: %s\n", trap->message().get());
|
|
}
|
|
|
|
TEST_F(WasmCapiCallbacksTest, GC) {
|
|
// Build the following function:
|
|
// int32 stage3_to4(int32 arg0) { return stage4(arg0); }
|
|
uint32_t stage4_index =
|
|
builder()->AddImport(base::CStrVector("stage4"), wasm_i_i_sig());
|
|
byte code[] = {WASM_CALL_FUNCTION(stage4_index, WASM_LOCAL_GET(0))};
|
|
AddExportedFunction(base::CStrVector("stage3_to4"), code, sizeof(code));
|
|
|
|
i::Isolate* isolate =
|
|
reinterpret_cast<::wasm::StoreImpl*>(store())->i_isolate();
|
|
own<Func> stage4 = Func::make(store(), cpp_i_i_sig(), Stage4_GC, isolate);
|
|
EXPECT_EQ(cpp_i_i_sig()->params().size(), stage4->type()->params().size());
|
|
EXPECT_EQ(cpp_i_i_sig()->results().size(), stage4->type()->results().size());
|
|
Extern* imports[] = {stage2(), stage4.get()};
|
|
Instantiate(imports);
|
|
Val args[] = {Val::i32(42)};
|
|
Val results[1];
|
|
own<Trap> trap = GetExportedFunction(0)->call(args, results);
|
|
EXPECT_EQ(trap, nullptr);
|
|
EXPECT_EQ(43, results[0].i32());
|
|
}
|
|
|
|
namespace {
|
|
|
|
own<Trap> FibonacciC(void* env, const Val args[], Val results[]) {
|
|
int32_t x = args[0].i32();
|
|
if (x == 0 || x == 1) {
|
|
results[0] = Val::i32(x);
|
|
return nullptr;
|
|
}
|
|
WasmCapiTest* self = reinterpret_cast<WasmCapiTest*>(env);
|
|
Func* fibo_wasm = self->GetExportedFunction(0);
|
|
// Aggressively re-use existing arrays. That's maybe not great coding
|
|
// style, but this test intentionally ensures that it works if someone
|
|
// insists on doing it.
|
|
Val recursive_args[] = {Val::i32(x - 1)};
|
|
own<Trap> trap = fibo_wasm->call(recursive_args, results);
|
|
DCHECK_NULL(trap);
|
|
int32_t x1 = results[0].i32();
|
|
recursive_args[0] = Val::i32(x - 2);
|
|
trap = fibo_wasm->call(recursive_args, results);
|
|
DCHECK_NULL(trap);
|
|
int32_t x2 = results[0].i32();
|
|
results[0] = Val::i32(x1 + x2);
|
|
return nullptr;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_F(WasmCapiTest, Recursion) {
|
|
// Build the following function:
|
|
// int32 fibonacci_wasm(int32 arg0) {
|
|
// if (arg0 == 0) return 0;
|
|
// if (arg0 == 1) return 1;
|
|
// return fibonacci_c(arg0 - 1) + fibonacci_c(arg0 - 2);
|
|
// }
|
|
uint32_t fibo_c_index =
|
|
builder()->AddImport(base::CStrVector("fibonacci_c"), wasm_i_i_sig());
|
|
byte code_fibo[] = {
|
|
WASM_IF(WASM_I32_EQ(WASM_LOCAL_GET(0), WASM_ZERO),
|
|
WASM_RETURN(WASM_ZERO)),
|
|
WASM_IF(WASM_I32_EQ(WASM_LOCAL_GET(0), WASM_ONE), WASM_RETURN(WASM_ONE)),
|
|
// Muck with the parameter to ensure callers don't depend on its value.
|
|
WASM_LOCAL_SET(0, WASM_I32_SUB(WASM_LOCAL_GET(0), WASM_ONE)),
|
|
WASM_RETURN(WASM_I32_ADD(
|
|
WASM_CALL_FUNCTION(fibo_c_index, WASM_LOCAL_GET(0)),
|
|
WASM_CALL_FUNCTION(fibo_c_index,
|
|
WASM_I32_SUB(WASM_LOCAL_GET(0), WASM_ONE))))};
|
|
AddExportedFunction(base::CStrVector("fibonacci_wasm"), code_fibo,
|
|
sizeof(code_fibo), wasm_i_i_sig());
|
|
|
|
own<Func> fibonacci = Func::make(store(), cpp_i_i_sig(), FibonacciC, this);
|
|
Extern* imports[] = {fibonacci.get()};
|
|
Instantiate(imports);
|
|
// Enough iterations to make it interesting, few enough to keep it fast.
|
|
Val args[] = {Val::i32(15)};
|
|
Val results[1];
|
|
own<Trap> result = GetExportedFunction(0)->call(args, results);
|
|
EXPECT_EQ(result, nullptr);
|
|
EXPECT_EQ(610, results[0].i32());
|
|
}
|
|
|
|
namespace {
|
|
|
|
own<Trap> PlusOne(const Val args[], Val results[]) {
|
|
int32_t a0 = args[0].i32();
|
|
results[0] = Val::i32(a0 + 1);
|
|
int64_t a1 = args[1].i64();
|
|
results[1] = Val::i64(a1 + 1);
|
|
float a2 = args[2].f32();
|
|
results[2] = Val::f32(a2 + 1);
|
|
double a3 = args[3].f64();
|
|
results[3] = Val::f64(a3 + 1);
|
|
results[4] = Val::ref(args[4].ref()->copy()); // No +1 for Refs.
|
|
return nullptr;
|
|
}
|
|
|
|
own<Trap> PlusOneWithManyArgs(const Val args[], Val results[]) {
|
|
int32_t a0 = args[0].i32();
|
|
results[0] = Val::i32(a0 + 1);
|
|
int64_t a1 = args[1].i64();
|
|
results[1] = Val::i64(a1 + 1);
|
|
float a2 = args[2].f32();
|
|
results[2] = Val::f32(a2 + 1);
|
|
double a3 = args[3].f64();
|
|
results[3] = Val::f64(a3 + 1);
|
|
results[4] = Val::ref(args[4].ref()->copy()); // No +1 for Refs.
|
|
int32_t a5 = args[5].i32();
|
|
results[5] = Val::i32(a5 + 1);
|
|
int64_t a6 = args[6].i64();
|
|
results[6] = Val::i64(a6 + 1);
|
|
float a7 = args[7].f32();
|
|
results[7] = Val::f32(a7 + 1);
|
|
double a8 = args[8].f64();
|
|
results[8] = Val::f64(a8 + 1);
|
|
int32_t a9 = args[9].i32();
|
|
results[9] = Val::i32(a9 + 1);
|
|
int64_t a10 = args[10].i64();
|
|
results[10] = Val::i64(a10 + 1);
|
|
float a11 = args[11].f32();
|
|
results[11] = Val::f32(a11 + 1);
|
|
double a12 = args[12].f64();
|
|
results[12] = Val::f64(a12 + 1);
|
|
int32_t a13 = args[13].i32();
|
|
results[13] = Val::i32(a13 + 1);
|
|
return nullptr;
|
|
}
|
|
} // namespace
|
|
|
|
TEST_F(WasmCapiTest, DirectCallCapiFunction) {
|
|
own<FuncType> cpp_sig =
|
|
FuncType::make(ownvec<ValType>::make(
|
|
ValType::make(::wasm::I32), ValType::make(::wasm::I64),
|
|
ValType::make(::wasm::F32), ValType::make(::wasm::F64),
|
|
ValType::make(::wasm::ANYREF)),
|
|
ownvec<ValType>::make(
|
|
ValType::make(::wasm::I32), ValType::make(::wasm::I64),
|
|
ValType::make(::wasm::F32), ValType::make(::wasm::F64),
|
|
ValType::make(::wasm::ANYREF)));
|
|
own<Func> func = Func::make(store(), cpp_sig.get(), PlusOne);
|
|
Extern* imports[] = {func.get()};
|
|
ValueType wasm_types[] = {kWasmI32, kWasmI64, kWasmF32, kWasmF64,
|
|
kWasmExternRef, kWasmI32, kWasmI64, kWasmF32,
|
|
kWasmF64, kWasmExternRef};
|
|
FunctionSig wasm_sig(5, 5, wasm_types);
|
|
int func_index = builder()->AddImport(base::CStrVector("func"), &wasm_sig);
|
|
builder()->ExportImportedFunction(base::CStrVector("func"), func_index);
|
|
Instantiate(imports);
|
|
int32_t a0 = 42;
|
|
int64_t a1 = 0x1234c0ffee;
|
|
float a2 = 1234.5;
|
|
double a3 = 123.45;
|
|
Val args[] = {Val::i32(a0), Val::i64(a1), Val::f32(a2), Val::f64(a3),
|
|
Val::ref(func->copy())};
|
|
Val results[5];
|
|
// Test that {func} can be called directly.
|
|
own<Trap> trap = func->call(args, results);
|
|
EXPECT_EQ(nullptr, trap);
|
|
EXPECT_EQ(a0 + 1, results[0].i32());
|
|
EXPECT_EQ(a1 + 1, results[1].i64());
|
|
EXPECT_EQ(a2 + 1, results[2].f32());
|
|
EXPECT_EQ(a3 + 1, results[3].f64());
|
|
EXPECT_TRUE(func->same(results[4].ref()));
|
|
|
|
// Test that {func} can be called after import/export round-tripping.
|
|
trap = GetExportedFunction(0)->call(args, results);
|
|
EXPECT_EQ(nullptr, trap);
|
|
EXPECT_EQ(a0 + 1, results[0].i32());
|
|
EXPECT_EQ(a1 + 1, results[1].i64());
|
|
EXPECT_EQ(a2 + 1, results[2].f32());
|
|
EXPECT_EQ(a3 + 1, results[3].f64());
|
|
EXPECT_TRUE(func->same(results[4].ref()));
|
|
}
|
|
|
|
TEST_F(WasmCapiTest, DirectCallCapiFunctionWithManyArgs) {
|
|
// Test with many arguments to make sure that CWasmArgumentsPacker won't use
|
|
// its buffer-on-stack optimization.
|
|
own<FuncType> cpp_sig = FuncType::make(
|
|
ownvec<ValType>::make(
|
|
ValType::make(::wasm::I32), ValType::make(::wasm::I64),
|
|
ValType::make(::wasm::F32), ValType::make(::wasm::F64),
|
|
ValType::make(::wasm::ANYREF), ValType::make(::wasm::I32),
|
|
ValType::make(::wasm::I64), ValType::make(::wasm::F32),
|
|
ValType::make(::wasm::F64), ValType::make(::wasm::I32),
|
|
ValType::make(::wasm::I64), ValType::make(::wasm::F32),
|
|
ValType::make(::wasm::F64), ValType::make(::wasm::I32)),
|
|
ownvec<ValType>::make(
|
|
ValType::make(::wasm::I32), ValType::make(::wasm::I64),
|
|
ValType::make(::wasm::F32), ValType::make(::wasm::F64),
|
|
ValType::make(::wasm::ANYREF), ValType::make(::wasm::I32),
|
|
ValType::make(::wasm::I64), ValType::make(::wasm::F32),
|
|
ValType::make(::wasm::F64), ValType::make(::wasm::I32),
|
|
ValType::make(::wasm::I64), ValType::make(::wasm::F32),
|
|
ValType::make(::wasm::F64), ValType::make(::wasm::I32)));
|
|
own<Func> func = Func::make(store(), cpp_sig.get(), PlusOneWithManyArgs);
|
|
Extern* imports[] = {func.get()};
|
|
ValueType wasm_types[] = {
|
|
kWasmI32, kWasmI64, kWasmF32, kWasmF64, kWasmExternRef, kWasmI32,
|
|
kWasmI64, kWasmF32, kWasmF64, kWasmI32, kWasmI64, kWasmF32,
|
|
kWasmF64, kWasmI32, kWasmI32, kWasmI64, kWasmF32, kWasmF64,
|
|
kWasmExternRef, kWasmI32, kWasmI64, kWasmF32, kWasmF64, kWasmI32,
|
|
kWasmI64, kWasmF32, kWasmF64, kWasmI32};
|
|
FunctionSig wasm_sig(14, 14, wasm_types);
|
|
int func_index = builder()->AddImport(base::CStrVector("func"), &wasm_sig);
|
|
builder()->ExportImportedFunction(base::CStrVector("func"), func_index);
|
|
Instantiate(imports);
|
|
int32_t a0 = 42;
|
|
int64_t a1 = 0x1234c0ffee;
|
|
float a2 = 1234.5;
|
|
double a3 = 123.45;
|
|
Val args[] = {
|
|
Val::i32(a0), Val::i64(a1), Val::f32(a2), Val::f64(a3),
|
|
Val::ref(func->copy()), Val::i32(a0), Val::i64(a1), Val::f32(a2),
|
|
Val::f64(a3), Val::i32(a0), Val::i64(a1), Val::f32(a2),
|
|
Val::f64(a3), Val::i32(a0)};
|
|
Val results[14];
|
|
// Test that {func} can be called directly.
|
|
own<Trap> trap = func->call(args, results);
|
|
EXPECT_EQ(nullptr, trap);
|
|
EXPECT_EQ(a0 + 1, results[0].i32());
|
|
EXPECT_EQ(a1 + 1, results[1].i64());
|
|
EXPECT_EQ(a2 + 1, results[2].f32());
|
|
EXPECT_EQ(a3 + 1, results[3].f64());
|
|
EXPECT_TRUE(func->same(results[4].ref()));
|
|
EXPECT_EQ(a0 + 1, results[5].i32());
|
|
EXPECT_EQ(a1 + 1, results[6].i64());
|
|
EXPECT_EQ(a2 + 1, results[7].f32());
|
|
EXPECT_EQ(a3 + 1, results[8].f64());
|
|
EXPECT_EQ(a0 + 1, results[9].i32());
|
|
EXPECT_EQ(a1 + 1, results[10].i64());
|
|
EXPECT_EQ(a2 + 1, results[11].f32());
|
|
EXPECT_EQ(a3 + 1, results[12].f64());
|
|
EXPECT_EQ(a0 + 1, results[13].i32());
|
|
|
|
// Test that {func} can be called after import/export round-tripping.
|
|
trap = GetExportedFunction(0)->call(args, results);
|
|
EXPECT_EQ(nullptr, trap);
|
|
EXPECT_EQ(a0 + 1, results[0].i32());
|
|
EXPECT_EQ(a1 + 1, results[1].i64());
|
|
EXPECT_EQ(a2 + 1, results[2].f32());
|
|
EXPECT_EQ(a3 + 1, results[3].f64());
|
|
EXPECT_TRUE(func->same(results[4].ref()));
|
|
EXPECT_EQ(a0 + 1, results[5].i32());
|
|
EXPECT_EQ(a1 + 1, results[6].i64());
|
|
EXPECT_EQ(a2 + 1, results[7].f32());
|
|
EXPECT_EQ(a3 + 1, results[8].f64());
|
|
EXPECT_EQ(a0 + 1, results[9].i32());
|
|
EXPECT_EQ(a1 + 1, results[10].i64());
|
|
EXPECT_EQ(a2 + 1, results[11].f32());
|
|
EXPECT_EQ(a3 + 1, results[12].f64());
|
|
EXPECT_EQ(a0 + 1, results[13].i32());
|
|
}
|
|
} // namespace wasm
|
|
} // namespace internal
|
|
} // namespace v8
|