Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

5
deps/v8/test/cctest/wasm/DEPS vendored Normal file
View File

@ -0,0 +1,5 @@
specific_include_rules = {
"test-wasm-strings.cc": [
"+third_party/utf8-decoder",
],
}

14
deps/v8/test/cctest/wasm/DIR_METADATA vendored Normal file
View File

@ -0,0 +1,14 @@
# Metadata information for this directory.
#
# For more information on DIR_METADATA files, see:
# https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
#
# For the schema of this file, see Metadata message:
# https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
monorail {
component: "Blink>JavaScript>WebAssembly"
}
buganizer_public: {
component_id: 1456332
}

1
deps/v8/test/cctest/wasm/OWNERS vendored Normal file
View File

@ -0,0 +1 @@
file:../../../src/wasm/OWNERS

View File

@ -0,0 +1,83 @@
// 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 "src/api/api-inl.h"
#include "src/objects/backing-store.h"
#include "src/wasm/wasm-objects.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-utils.h"
#include "test/cctest/manually-externalized-buffer.h"
namespace v8::internal::wasm {
using testing::ManuallyExternalizedBuffer;
TEST(Run_WasmModule_Buffer_Externalized_Detach) {
{
// Regression test for
// https://bugs.chromium.org/p/chromium/issues/detail?id=731046
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
MaybeHandle<JSArrayBuffer> result =
isolate->factory()->NewJSArrayBufferAndBackingStore(
kWasmPageSize, InitializedFlag::kZeroInitialized);
Handle<JSArrayBuffer> buffer = result.ToHandleChecked();
// Embedder requests contents.
ManuallyExternalizedBuffer external(buffer);
JSArrayBuffer::Detach(buffer).Check();
CHECK(buffer->was_detached());
// Make sure we can write to the buffer without crashing
uint32_t* int_buffer =
reinterpret_cast<uint32_t*>(external.backing_store());
int_buffer[0] = 0;
// Embedder frees contents.
}
heap::InvokeMemoryReducingMajorGCs(CcTest::heap());
}
TEST(Run_WasmModule_Buffer_Externalized_Regression_UseAfterFree) {
{
// Regression test for https://crbug.com/813876
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
MaybeDirectHandle<WasmMemoryObject> result = WasmMemoryObject::New(
isolate, 1, 1, SharedFlag::kNotShared, wasm::AddressType::kI32);
DirectHandle<WasmMemoryObject> memory_object = result.ToHandleChecked();
Handle<JSArrayBuffer> buffer(memory_object->array_buffer(), isolate);
{
// Embedder requests contents.
ManuallyExternalizedBuffer external(buffer);
// Growing (even by 0) detaches the old buffer.
WasmMemoryObject::Grow(isolate, memory_object, 0);
CHECK(buffer->was_detached());
// Embedder frees contents.
}
// Make sure the memory object has a new buffer that can be written to.
uint32_t* int_buffer = reinterpret_cast<uint32_t*>(
memory_object->array_buffer()->backing_store());
int_buffer[0] = 0;
}
heap::InvokeMemoryReducingMajorGCs(CcTest::heap());
}
#if V8_TARGET_ARCH_64_BIT
TEST(BackingStore_Reclaim) {
// Make sure we can allocate memories without running out of address space.
Isolate* isolate = CcTest::InitIsolateOnce();
for (int i = 0; i < 256; ++i) {
auto backing_store = BackingStore::AllocateWasmMemory(
isolate, 1, 1, WasmMemoryFlag::kWasmMemory32, SharedFlag::kNotShared);
CHECK(backing_store);
}
}
#endif
} // namespace v8::internal::wasm

View File

@ -0,0 +1,194 @@
// Copyright 2017 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 <cstdint>
#include "src/base/overflowing-math.h"
#include "src/base/safe_conversions.h"
#include "src/codegen/assembler-inl.h"
#include "src/objects/objects-inl.h"
#include "src/wasm/wasm-arguments.h"
#include "src/wasm/wasm-code-pointer-table-inl.h"
#include "src/wasm/wasm-objects.h"
#include "test/cctest/cctest.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/common/value-helper.h"
#include "test/common/wasm/wasm-macro-gen.h"
namespace v8 {
namespace internal {
namespace wasm {
/**
* We test the interface from C to compiled wasm code by generating a wasm
* function, creating a corresponding signature, compiling the c wasm entry for
* that signature, and then calling that entry using different test values.
* The result is compared against the expected result, computed from a lambda
* passed to the CWasmEntryArgTester.
*/
namespace {
template <typename ReturnType, typename... Args>
class CWasmEntryArgTester {
public:
CWasmEntryArgTester(std::initializer_list<uint8_t> wasm_function_bytes,
std::function<ReturnType(Args...)> expected_fn)
: runner_(TestExecutionTier::kTurbofan),
isolate_(runner_.main_isolate()),
expected_fn_(expected_fn),
sig_(WasmRunnerBase::CanonicalizeSig(
runner_.template CreateSig<ReturnType, Args...>())) {
std::vector<uint8_t> code{wasm_function_bytes};
runner_.Build(code.data(), code.data() + code.size());
wasm_code_ = runner_.builder().GetFunctionCode(0);
c_wasm_entry_ = compiler::CompileCWasmEntry(isolate_, sig_);
}
template <typename... Rest>
void WriteToBuffer(CWasmArgumentsPacker* packer, Rest... rest) {
static_assert(sizeof...(rest) == 0, "this is the base case");
}
template <typename First, typename... Rest>
void WriteToBuffer(CWasmArgumentsPacker* packer, First first, Rest... rest) {
packer->Push(first);
WriteToBuffer(packer, rest...);
}
void CheckCall(Args... args) {
CWasmArgumentsPacker packer(CWasmArgumentsPacker::TotalSize(sig_));
WriteToBuffer(&packer, args...);
WasmCodePointer wasm_call_target =
GetProcessWideWasmCodePointerTable()->AllocateAndInitializeEntry(
wasm_code_->instruction_start(), wasm_code_->signature_hash());
DirectHandle<Object> object_ref = runner_.builder().instance_object();
Execution::CallWasm(isolate_, c_wasm_entry_, wasm_call_target, object_ref,
packer.argv());
GetProcessWideWasmCodePointerTable()->FreeEntry(wasm_call_target);
CHECK(!isolate_->has_exception());
packer.Reset();
// Check the result.
ReturnType result = packer.Pop<ReturnType>();
ReturnType expected = expected_fn_(args...);
if (std::is_floating_point<ReturnType>::value) {
CHECK_DOUBLE_EQ(expected, result);
} else {
CHECK_EQ(expected, result);
}
}
private:
WasmRunner<ReturnType, Args...> runner_;
Isolate* isolate_;
std::function<ReturnType(Args...)> expected_fn_;
const CanonicalSig* sig_;
Handle<Code> c_wasm_entry_;
WasmCode* wasm_code_;
};
} // namespace
// Pass int32_t, return int32_t.
TEST(TestCWasmEntryArgPassing_int32) {
CWasmEntryArgTester<int32_t, int32_t> tester(
{// Return 2*<0> + 1.
WASM_I32_ADD(WASM_I32_MUL(WASM_I32V_1(2), WASM_LOCAL_GET(0)), WASM_ONE)},
[](int32_t a) {
return base::AddWithWraparound(base::MulWithWraparound(2, a), 1);
});
FOR_INT32_INPUTS(v) { tester.CheckCall(v); }
}
// Pass int64_t, return double.
TEST(TestCWasmEntryArgPassing_double_int64) {
CWasmEntryArgTester<double, int64_t> tester(
{// Return (double)<0>.
WASM_F64_SCONVERT_I64(WASM_LOCAL_GET(0))},
[](int64_t a) { return static_cast<double>(a); });
FOR_INT64_INPUTS(v) { tester.CheckCall(v); }
}
// Pass double, return int64_t.
TEST(TestCWasmEntryArgPassing_int64_double) {
CWasmEntryArgTester<int64_t, double> tester(
{// Return (int64_t)<0>.
WASM_I64_SCONVERT_F64(WASM_LOCAL_GET(0))},
[](double d) { return static_cast<int64_t>(d); });
FOR_FLOAT64_INPUTS(d) {
if (base::IsValueInRangeForNumericType<int64_t>(d)) {
tester.CheckCall(d);
}
}
}
// Pass float, return double.
TEST(TestCWasmEntryArgPassing_float_double) {
CWasmEntryArgTester<double, float> tester(
{// Return 2*(double)<0> + 1.
WASM_F64_ADD(
WASM_F64_MUL(WASM_F64(2), WASM_F64_CONVERT_F32(WASM_LOCAL_GET(0))),
WASM_F64(1))},
[](float f) { return 2. * static_cast<double>(f) + 1.; });
FOR_FLOAT32_INPUTS(f) { tester.CheckCall(f); }
}
// Pass two doubles, return double.
TEST(TestCWasmEntryArgPassing_double_double) {
CWasmEntryArgTester<double, double, double> tester(
{// Return <0> + <1>.
WASM_F64_ADD(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1))},
[](double a, double b) { return a + b; });
FOR_FLOAT64_INPUTS(d1) {
FOR_FLOAT64_INPUTS(d2) { tester.CheckCall(d1, d2); }
}
}
// Pass int32_t, int64_t, float and double, return double.
TEST(TestCWasmEntryArgPassing_AllTypes) {
CWasmEntryArgTester<double, int32_t, int64_t, float, double> tester(
{
// Convert all arguments to double, add them and return the sum.
WASM_F64_ADD( // <0+1+2> + <3>
WASM_F64_ADD( // <0+1> + <2>
WASM_F64_ADD( // <0> + <1>
WASM_F64_SCONVERT_I32(
WASM_LOCAL_GET(0)), // <0> to double
WASM_F64_SCONVERT_I64(
WASM_LOCAL_GET(1))), // <1> to double
WASM_F64_CONVERT_F32(WASM_LOCAL_GET(2))), // <2> to double
WASM_LOCAL_GET(3)) // <3>
},
[](int32_t a, int64_t b, float c, double d) {
return 0. + a + b + c + d;
});
base::Vector<const int32_t> test_values_i32 =
compiler::ValueHelper::int32_vector();
base::Vector<const int64_t> test_values_i64 =
compiler::ValueHelper::int64_vector();
base::Vector<const float> test_values_f32 =
compiler::ValueHelper::float32_vector();
base::Vector<const double> test_values_f64 =
compiler::ValueHelper::float64_vector();
size_t max_len =
std::max(std::max(test_values_i32.size(), test_values_i64.size()),
std::max(test_values_f32.size(), test_values_f64.size()));
for (size_t i = 0; i < max_len; ++i) {
int32_t i32 = test_values_i32[i % test_values_i32.size()];
int64_t i64 = test_values_i64[i % test_values_i64.size()];
float f32 = test_values_f32[i % test_values_f32.size()];
double f64 = test_values_f64[i % test_values_f64.size()];
tester.CheckCall(i32, i64, f32, f64);
}
}
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,312 @@
// Copyright 2020 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 "src/api/api-inl.h"
#include "src/init/v8.h"
#include "src/wasm/streaming-decoder.h"
#include "src/wasm/wasm-code-manager.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-module-builder.h"
#include "test/cctest/cctest.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace {
class TestResolver : public CompilationResultResolver {
public:
explicit TestResolver(std::atomic<int>* pending)
: native_module_(nullptr), pending_(pending) {}
void OnCompilationSucceeded(
i::DirectHandle<i::WasmModuleObject> module) override {
if (!module.is_null()) {
native_module_ = module->shared_native_module();
pending_->fetch_sub(1);
}
}
void OnCompilationFailed(i::DirectHandle<i::JSAny> error_reason) override {
CHECK(false);
}
std::shared_ptr<NativeModule> native_module() { return native_module_; }
private:
std::shared_ptr<NativeModule> native_module_;
std::atomic<int>* pending_;
};
class StreamTester {
public:
explicit StreamTester(std::shared_ptr<TestResolver> test_resolver)
: internal_scope_(CcTest::i_isolate()), test_resolver_(test_resolver) {
i::Isolate* i_isolate = CcTest::i_isolate();
DirectHandle<Context> context = i_isolate->native_context();
stream_ = GetWasmEngine()->StartStreamingCompilation(
i_isolate, WasmEnabledFeatures::All(), CompileTimeImports{}, context,
"WebAssembly.compileStreaming()", test_resolver_);
}
void OnBytesReceived(const uint8_t* start, size_t length) {
stream_->OnBytesReceived(base::Vector<const uint8_t>(start, length));
}
void FinishStream() { stream_->Finish(); }
private:
i::HandleScope internal_scope_;
std::shared_ptr<StreamingDecoder> stream_;
std::shared_ptr<TestResolver> test_resolver_;
};
// Create a valid module such that the bytes depend on {n}.
ZoneBuffer GetValidModuleBytes(Zone* zone, uint8_t n) {
ZoneBuffer buffer(zone);
TestSignatures sigs;
WasmModuleBuilder builder(zone);
{
WasmFunctionBuilder* f = builder.AddFunction(sigs.v_v());
f->EmitCode({kExprI32Const, n, kExprDrop, kExprEnd});
}
builder.WriteTo(&buffer);
return buffer;
}
std::shared_ptr<NativeModule> SyncCompile(base::Vector<const uint8_t> bytes) {
ErrorThrower thrower(CcTest::i_isolate(), "Test");
auto enabled_features = WasmEnabledFeatures::FromIsolate(CcTest::i_isolate());
DirectHandle<WasmModuleObject> module =
GetWasmEngine()
->SyncCompile(CcTest::i_isolate(), enabled_features,
CompileTimeImports{}, &thrower,
base::OwnedCopyOf(bytes))
.ToHandleChecked();
return module->shared_native_module();
}
// Shared prefix.
constexpr uint8_t kPrefix[] = {
WASM_MODULE_HEADER, // module header
kTypeSectionCode, // section code
U32V_1(1 + SIZEOF_SIG_ENTRY_v_v), // section size
U32V_1(1), // type count
SIG_ENTRY_v_v, // signature entry
kFunctionSectionCode, // section code
U32V_1(2), // section size
U32V_1(1), // functions count
0, // signature index
kCodeSectionCode, // section code
U32V_1(7), // section size
U32V_1(1), // functions count
5, // body size
};
constexpr uint8_t kFunctionA[] = {
U32V_1(0), kExprI32Const, U32V_1(0), kExprDrop, kExprEnd,
};
constexpr uint8_t kFunctionB[] = {
U32V_1(0), kExprI32Const, U32V_1(1), kExprDrop, kExprEnd,
};
constexpr size_t kPrefixSize = arraysize(kPrefix);
constexpr size_t kFunctionSize = arraysize(kFunctionA);
} // namespace
TEST(TestAsyncCache) {
CcTest::InitializeVM();
i::HandleScope internal_scope(CcTest::i_isolate());
AccountingAllocator allocator;
Zone zone(&allocator, "CompilationCacheTester");
auto bufferA = GetValidModuleBytes(&zone, 0);
auto bufferB = GetValidModuleBytes(&zone, 1);
std::atomic<int> pending(3);
auto resolverA1 = std::make_shared<TestResolver>(&pending);
auto resolverA2 = std::make_shared<TestResolver>(&pending);
auto resolverB = std::make_shared<TestResolver>(&pending);
GetWasmEngine()->AsyncCompile(
CcTest::i_isolate(), WasmEnabledFeatures::All(), CompileTimeImports{},
resolverA1, base::OwnedCopyOf(bufferA), "WebAssembly.compile");
GetWasmEngine()->AsyncCompile(
CcTest::i_isolate(), WasmEnabledFeatures::All(), CompileTimeImports{},
resolverA2, base::OwnedCopyOf(bufferA), "WebAssembly.compile");
GetWasmEngine()->AsyncCompile(
CcTest::i_isolate(), WasmEnabledFeatures::All(), CompileTimeImports{},
resolverB, base::OwnedCopyOf(bufferB), "WebAssembly.compile");
while (pending > 0) {
v8::platform::PumpMessageLoop(i::V8::GetCurrentPlatform(),
CcTest::isolate());
}
CHECK_EQ(resolverA1->native_module(), resolverA2->native_module());
CHECK_NE(resolverA1->native_module(), resolverB->native_module());
}
TEST(TestStreamingCache) {
CcTest::InitializeVM();
std::atomic<int> pending(3);
auto resolverA1 = std::make_shared<TestResolver>(&pending);
auto resolverA2 = std::make_shared<TestResolver>(&pending);
auto resolverB = std::make_shared<TestResolver>(&pending);
StreamTester testerA1(resolverA1);
StreamTester testerA2(resolverA2);
StreamTester testerB(resolverB);
// Start receiving kPrefix bytes.
testerA1.OnBytesReceived(kPrefix, kPrefixSize);
testerA2.OnBytesReceived(kPrefix, kPrefixSize);
testerB.OnBytesReceived(kPrefix, kPrefixSize);
// Receive function bytes and start streaming compilation.
testerA1.OnBytesReceived(kFunctionA, kFunctionSize);
testerA1.FinishStream();
testerA2.OnBytesReceived(kFunctionA, kFunctionSize);
testerA2.FinishStream();
testerB.OnBytesReceived(kFunctionB, kFunctionSize);
testerB.FinishStream();
while (pending > 0) {
v8::platform::PumpMessageLoop(i::V8::GetCurrentPlatform(),
CcTest::isolate());
}
std::shared_ptr<NativeModule> native_module_A1 = resolverA1->native_module();
std::shared_ptr<NativeModule> native_module_A2 = resolverA2->native_module();
std::shared_ptr<NativeModule> native_module_B = resolverB->native_module();
CHECK_EQ(native_module_A1, native_module_A2);
CHECK_NE(native_module_A1, native_module_B);
}
TEST(TestStreamingAndSyncCache) {
CcTest::InitializeVM();
std::atomic<int> pending(1);
auto resolver = std::make_shared<TestResolver>(&pending);
StreamTester tester(resolver);
tester.OnBytesReceived(kPrefix, kPrefixSize);
// Compile the same module synchronously to make sure we don't deadlock
// waiting for streaming compilation to finish.
auto full_bytes =
base::OwnedVector<uint8_t>::New(kPrefixSize + kFunctionSize);
memcpy(full_bytes.begin(), kPrefix, kPrefixSize);
memcpy(full_bytes.begin() + kPrefixSize, kFunctionA, kFunctionSize);
auto native_module_sync = SyncCompile(full_bytes.as_vector());
// Streaming compilation should just discard its native module now and use the
// one inserted in the cache by sync compilation.
tester.OnBytesReceived(kFunctionA, kFunctionSize);
tester.FinishStream();
while (pending > 0) {
v8::platform::PumpMessageLoop(i::V8::GetCurrentPlatform(),
CcTest::isolate());
}
std::shared_ptr<NativeModule> native_module_streaming =
resolver->native_module();
CHECK_EQ(native_module_streaming, native_module_sync);
}
void TestModuleSharingBetweenIsolates() {
class ShareModuleThread : public base::Thread {
public:
ShareModuleThread(
const char* name,
std::function<void(std::shared_ptr<NativeModule>)> register_module)
: base::Thread(base::Thread::Options{name}),
register_module_(std::move(register_module)) {}
void Run() override {
v8::Isolate::CreateParams isolate_create_params;
auto* ab_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
isolate_create_params.array_buffer_allocator = ab_allocator;
v8::Isolate* isolate = v8::Isolate::New(isolate_create_params);
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
isolate->Enter();
{
i::HandleScope handle_scope(i_isolate);
v8::Context::New(isolate)->Enter();
auto full_bytes =
base::OwnedVector<uint8_t>::New(kPrefixSize + kFunctionSize);
memcpy(full_bytes.begin(), kPrefix, kPrefixSize);
memcpy(full_bytes.begin() + kPrefixSize, kFunctionA, kFunctionSize);
ErrorThrower thrower(i_isolate, "Test");
std::shared_ptr<NativeModule> native_module =
GetWasmEngine()
->SyncCompile(i_isolate, WasmEnabledFeatures::All(),
CompileTimeImports{}, &thrower,
std::move(full_bytes))
.ToHandleChecked()
->shared_native_module();
register_module_(native_module);
// Check that we can access the code (see https://crbug.com/1280451).
WasmCodeRefScope code_ref_scope;
uint8_t* code_start = native_module->GetCode(0)->instructions().begin();
// Use the loaded value in a CHECK to prevent the compiler from just
// optimizing it away. Even {volatile} would require that.
CHECK_NE(0, *code_start);
}
isolate->Exit();
isolate->Dispose();
delete ab_allocator;
}
private:
const std::function<void(std::shared_ptr<NativeModule>)> register_module_;
};
std::vector<std::shared_ptr<NativeModule>> modules;
base::Mutex mutex;
auto register_module = [&](std::shared_ptr<NativeModule> module) {
base::MutexGuard guard(&mutex);
modules.emplace_back(std::move(module));
};
ShareModuleThread thread1("ShareModuleThread1", register_module);
CHECK(thread1.Start());
thread1.Join();
// Start a second thread which should get the cached module.
ShareModuleThread thread2("ShareModuleThread2", register_module);
CHECK(thread2.Start());
thread2.Join();
CHECK_EQ(2, modules.size());
CHECK_EQ(modules[0].get(), modules[1].get());
}
UNINITIALIZED_TEST(TwoIsolatesShareNativeModule) {
v8_flags.wasm_lazy_compilation = false;
TestModuleSharingBetweenIsolates();
}
UNINITIALIZED_TEST(TwoIsolatesShareNativeModuleWithPku) {
v8_flags.wasm_lazy_compilation = false;
v8_flags.memory_protection_keys = true;
TestModuleSharingBetweenIsolates();
}
} // namespace wasm
} // namespace internal
} // namespace v8

2155
deps/v8/test/cctest/wasm/test-gc.cc vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,127 @@
// 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 "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-opcodes.h"
#include "src/wasm/wasm-module-builder.h"
#include "test/cctest/cctest.h"
#include "test/cctest/manually-externalized-buffer.h"
#include "test/common/wasm/flag-utils.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
#include "test/common/wasm/wasm-module-runner.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace test_grow_memory {
using testing::CompileAndInstantiateForTesting;
using v8::internal::testing::ManuallyExternalizedBuffer;
namespace {
void ExportAsMain(WasmFunctionBuilder* f) {
f->builder()->AddExport(base::CStrVector("main"), f);
}
void Cleanup(Isolate* isolate = CcTest::InitIsolateOnce()) {
// By sending a low memory notifications, we will try hard to collect all
// garbage and will therefore also invoke all weak callbacks of actually
// unreachable persistent handles.
reinterpret_cast<v8::Isolate*>(isolate)->LowMemoryNotification();
}
} // namespace
TEST(GrowMemDetaches) {
{
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
DirectHandle<WasmMemoryObject> memory_object =
WasmMemoryObject::New(isolate, 16, 100, SharedFlag::kNotShared,
wasm::AddressType::kI32)
.ToHandleChecked();
DirectHandle<JSArrayBuffer> buffer(memory_object->array_buffer(), isolate);
int32_t result = WasmMemoryObject::Grow(isolate, memory_object, 0);
CHECK_EQ(16, result);
CHECK_NE(*buffer, memory_object->array_buffer());
CHECK(buffer->was_detached());
}
Cleanup();
}
TEST(Externalized_GrowMemMemSize) {
{
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
DirectHandle<WasmMemoryObject> memory_object =
WasmMemoryObject::New(isolate, 16, 100, SharedFlag::kNotShared,
wasm::AddressType::kI32)
.ToHandleChecked();
ManuallyExternalizedBuffer external(
handle(memory_object->array_buffer(), isolate));
int32_t result = WasmMemoryObject::Grow(isolate, memory_object, 0);
CHECK_EQ(16, result);
CHECK_NE(*external.buffer_, memory_object->array_buffer());
CHECK(external.buffer_->was_detached());
}
Cleanup();
}
TEST(Run_WasmModule_Buffer_Externalized_GrowMem) {
{
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
TestSignatures sigs;
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
builder->AddMemory(16);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
ExportAsMain(f);
f->EmitCode({WASM_MEMORY_GROW(WASM_I32V_1(6)), WASM_DROP, WASM_MEMORY_SIZE,
WASM_END});
ZoneBuffer buffer(&zone);
builder->WriteTo(&buffer);
testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "Test");
const DirectHandle<WasmInstanceObject> instance =
CompileAndInstantiateForTesting(isolate, &thrower,
base::VectorOf(buffer))
.ToHandleChecked();
DirectHandle<WasmMemoryObject> memory_object{
instance->trusted_data(isolate)->memory_object(0), isolate};
// Fake the Embedder flow by externalizing the array buffer.
ManuallyExternalizedBuffer external1(
handle(memory_object->array_buffer(), isolate));
// Grow using the API.
uint32_t result = WasmMemoryObject::Grow(isolate, memory_object, 4);
CHECK_EQ(16, result);
CHECK(external1.buffer_->was_detached()); // growing always detaches
CHECK_EQ(0, external1.buffer_->byte_length());
CHECK_NE(*external1.buffer_, memory_object->array_buffer());
// Fake the Embedder flow by externalizing the array buffer.
ManuallyExternalizedBuffer external2(
handle(memory_object->array_buffer(), isolate));
// Grow using an internal Wasm bytecode.
result = testing::CallWasmFunctionForTesting(isolate, instance, "main", {});
CHECK_EQ(26, result);
CHECK(external2.buffer_->was_detached()); // growing always detaches
CHECK_EQ(0, external2.buffer_->byte_length());
CHECK_NE(*external2.buffer_, memory_object->array_buffer());
}
Cleanup();
}
} // namespace test_grow_memory
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,335 @@
// Copyright 2018 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 <bitset>
#include "src/base/utils/random-number-generator.h"
#include "src/codegen/assembler-inl.h"
#include "src/codegen/macro-assembler-inl.h"
#include "src/execution/simulator.h"
#include "src/utils/utils.h"
#include "src/wasm/code-space-access.h"
#include "src/wasm/jump-table-assembler.h"
#include "test/cctest/cctest.h"
#include "test/common/assembler-tester.h"
namespace v8 {
namespace internal {
namespace wasm {
#if 0
#define TRACE(...) PrintF(__VA_ARGS__)
#else
#define TRACE(...)
#endif
#define __ masm.
namespace {
static volatile int global_stop_bit = 0;
constexpr int kJumpTableSlotCount = 128;
constexpr uint32_t kJumpTableSize =
JumpTableAssembler::SizeForNumberOfSlots(kJumpTableSlotCount);
// This must be a safe commit page size so we pick the largest OS page size that
// V8 is known to support. Arm64 linux can support up to 64k at runtime.
constexpr size_t kThunkBufferSize = 64 * KB;
#if V8_TARGET_ARCH_ARM64 || V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_LOONG64 || \
V8_TARGET_ARCH_RISCV64
// We need the branches (from CompileJumpTableThunk) to be within near-call
// range of the jump table slots. The address hint to AllocateAssemblerBuffer
// is not reliable enough to guarantee that we can always achieve this with
// separate allocations, so we generate all code in a single
// kMaxCodeMemory-sized chunk.
constexpr size_t kAssemblerBufferSize =
size_t{kDefaultMaxWasmCodeSpaceSizeMb} * MB;
constexpr uint32_t kAvailableBufferSlots =
(kAssemblerBufferSize - kJumpTableSize) / kThunkBufferSize;
constexpr uint32_t kBufferSlotStartOffset =
RoundUp<kThunkBufferSize>(kJumpTableSize);
#else
constexpr size_t kAssemblerBufferSize = kJumpTableSize;
constexpr uint32_t kAvailableBufferSlots = 0;
constexpr uint32_t kBufferSlotStartOffset = 0;
#endif
Address AllocateJumpTableThunk(
Address jump_target, uint8_t* thunk_slot_buffer,
std::bitset<kAvailableBufferSlots>* used_slots,
std::vector<std::unique_ptr<TestingAssemblerBuffer>>* thunk_buffers) {
#if V8_TARGET_ARCH_ARM64 || V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_LOONG64 || \
V8_TARGET_ARCH_RISCV64
// To guarantee that the branch range lies within the near-call range,
// generate the thunk in the same (kMaxWasmCodeSpaceSize-sized) buffer as the
// jump_target itself.
//
// Allocate a slot that we haven't already used. This is necessary because
// each test iteration expects to generate two unique addresses and we leave
// each slot executable (and not writable).
base::RandomNumberGenerator* rng =
CcTest::i_isolate()->random_number_generator();
// Ensure a chance of completion without too much thrashing.
DCHECK(used_slots->count() < (used_slots->size() / 2));
int buffer_index;
do {
buffer_index = rng->NextInt(kAvailableBufferSlots);
} while (used_slots->test(buffer_index));
used_slots->set(buffer_index);
return reinterpret_cast<Address>(thunk_slot_buffer +
buffer_index * kThunkBufferSize);
#else
USE(thunk_slot_buffer);
USE(used_slots);
thunk_buffers->emplace_back(
AllocateAssemblerBuffer(kThunkBufferSize, GetRandomMmapAddr()));
return reinterpret_cast<Address>(thunk_buffers->back()->start());
#endif
}
void CompileJumpTableThunk(Address thunk, Address jump_target) {
RwxMemoryWriteScopeForTesting write_scope;
MacroAssembler masm(CcTest::i_isolate()->allocator(), AssemblerOptions{},
CodeObjectRequired::kNo,
ExternalAssemblerBuffer(reinterpret_cast<void*>(thunk),
kThunkBufferSize));
Label exit;
Register scratch = kReturnRegister0;
Address stop_bit_address = reinterpret_cast<Address>(&global_stop_bit);
#if V8_TARGET_ARCH_X64
__ Move(scratch, stop_bit_address, RelocInfo::NO_INFO);
__ testl(MemOperand(scratch, 0), Immediate(1));
__ j(not_zero, &exit);
__ Jump(jump_target, RelocInfo::NO_INFO);
#elif V8_TARGET_ARCH_IA32
__ Move(scratch, Immediate(stop_bit_address, RelocInfo::NO_INFO));
__ test(MemOperand(scratch, 0), Immediate(1));
__ j(not_zero, &exit);
__ jmp(jump_target, RelocInfo::NO_INFO);
#elif V8_TARGET_ARCH_ARM
__ mov(scratch, Operand(stop_bit_address, RelocInfo::NO_INFO));
__ ldr(scratch, MemOperand(scratch, 0));
__ tst(scratch, Operand(1));
__ b(ne, &exit);
__ Jump(jump_target, RelocInfo::NO_INFO);
#elif V8_TARGET_ARCH_ARM64
UseScratchRegisterScope temps(&masm);
temps.Exclude(x16);
scratch = x16;
__ Mov(scratch, Operand(stop_bit_address, RelocInfo::NO_INFO));
__ Ldr(scratch, MemOperand(scratch, 0));
__ Tbnz(scratch, 0, &exit);
__ Mov(scratch, Immediate(jump_target, RelocInfo::NO_INFO));
__ Br(scratch);
#elif V8_TARGET_ARCH_PPC64
__ mov(scratch, Operand(stop_bit_address, RelocInfo::NO_INFO));
__ LoadU64(scratch, MemOperand(scratch));
__ cmpi(scratch, Operand::Zero());
__ bne(&exit);
__ mov(scratch, Operand(jump_target, RelocInfo::NO_INFO));
__ Jump(scratch);
#elif V8_TARGET_ARCH_S390X
__ mov(scratch, Operand(stop_bit_address, RelocInfo::NO_INFO));
__ LoadU64(scratch, MemOperand(scratch));
__ CmpP(scratch, Operand(0));
__ bne(&exit);
__ mov(scratch, Operand(jump_target, RelocInfo::NO_INFO));
__ Jump(scratch);
#elif V8_TARGET_ARCH_MIPS64
__ li(scratch, Operand(stop_bit_address, RelocInfo::NO_INFO));
__ Lw(scratch, MemOperand(scratch, 0));
__ Branch(&exit, ne, scratch, Operand(zero_reg));
__ Jump(jump_target, RelocInfo::NO_INFO);
#elif V8_TARGET_ARCH_LOONG64
__ li(scratch, Operand(stop_bit_address, RelocInfo::NO_INFO));
__ Ld_w(scratch, MemOperand(scratch, 0));
__ Branch(&exit, ne, scratch, Operand(zero_reg));
__ Jump(jump_target, RelocInfo::NO_INFO);
#elif V8_TARGET_ARCH_MIPS
__ li(scratch, Operand(stop_bit_address, RelocInfo::NO_INFO));
__ lw(scratch, MemOperand(scratch, 0));
__ Branch(&exit, ne, scratch, Operand(zero_reg));
__ Jump(jump_target, RelocInfo::NO_INFO);
#elif V8_TARGET_ARCH_RISCV64 || V8_TARGET_ARCH_RISCV32
__ li(scratch, Operand(stop_bit_address, RelocInfo::NO_INFO));
__ Lw(scratch, MemOperand(scratch, 0));
__ Branch(&exit, ne, scratch, Operand(zero_reg));
__ Jump(jump_target, RelocInfo::NO_INFO);
#else
#error Unsupported architecture
#endif
__ bind(&exit);
__ Ret();
FlushInstructionCache(thunk, kThunkBufferSize);
#if defined(V8_OS_DARWIN) && defined(V8_HOST_ARCH_ARM64)
// MacOS on arm64 refuses {mprotect} calls to toggle permissions of RWX
// memory. Simply do nothing here, as the space will by default be executable
// and non-writable for the JumpTableRunner.
#else
CHECK(SetPermissions(GetPlatformPageAllocator(), thunk, kThunkBufferSize,
v8::PageAllocator::kReadExecute));
#endif
}
class JumpTableRunner : public v8::base::Thread {
public:
JumpTableRunner(Address slot_address, int runner_id)
: Thread(Options("JumpTableRunner")),
slot_address_(slot_address),
runner_id_(runner_id) {}
void Run() override {
TRACE("Runner #%d is starting ...\n", runner_id_);
GeneratedCode<void>::FromAddress(CcTest::i_isolate(), slot_address_).Call();
TRACE("Runner #%d is stopping ...\n", runner_id_);
USE(runner_id_);
}
private:
Address slot_address_;
int runner_id_;
};
class JumpTablePatcher : public v8::base::Thread {
public:
JumpTablePatcher(Address slot_start, uint32_t slot_index, Address thunk1,
Address thunk2, base::Mutex* jump_table_mutex)
: Thread(Options("JumpTablePatcher")),
slot_start_(slot_start),
slot_index_(slot_index),
thunks_{thunk1, thunk2},
jump_table_mutex_(jump_table_mutex) {}
void Run() override {
TRACE("Patcher %p is starting ...\n", this);
Address slot_address =
slot_start_ + JumpTableAssembler::JumpSlotIndexToOffset(slot_index_);
// First, emit code to the two thunks.
for (Address thunk : thunks_) {
CompileJumpTableThunk(thunk, slot_address);
}
// Then, repeatedly patch the jump table to jump to one of the two thunks.
WritableJumpTablePair jump_table_pair = WritableJumpTablePair::ForTesting(
slot_start_, JumpTableAssembler::JumpSlotIndexToOffset(slot_index_ + 1),
slot_start_,
JumpTableAssembler::JumpSlotIndexToOffset(slot_index_ + 1));
constexpr int kNumberOfPatchIterations = 64;
for (int i = 0; i < kNumberOfPatchIterations; ++i) {
TRACE(" patcher %p patch slot " V8PRIxPTR_FMT
" to thunk #%d (" V8PRIxPTR_FMT ")\n",
this, slot_address, i % 2, thunks_[i % 2]);
base::MutexGuard jump_table_guard(jump_table_mutex_);
Address slot_addr =
slot_start_ + JumpTableAssembler::JumpSlotIndexToOffset(slot_index_);
JumpTableAssembler::PatchJumpTableSlot(jump_table_pair, slot_addr,
kNullAddress, thunks_[i % 2]);
}
TRACE("Patcher %p is stopping ...\n", this);
}
private:
Address slot_start_;
uint32_t slot_index_;
Address thunks_[2];
base::Mutex* jump_table_mutex_;
};
} // namespace
// This test is intended to stress concurrent patching of jump-table slots. It
// uses the following setup:
// 1) Picks a particular slot of the jump-table. Slots are iterated over to
// ensure multiple entries (at different offset alignments) are tested.
// 2) Starts multiple runners that spin through the above slot. The runners
// use thunk code that will jump to the same jump-table slot repeatedly
// until the {global_stop_bit} indicates a test-end condition.
// 3) Start a patcher that repeatedly patches the jump-table slot back and
// forth between two thunk. If there is a race then chances are high that
// one of the runners is currently executing the jump-table slot.
TEST(JumpTablePatchingStress) {
constexpr int kNumberOfRunnerThreads = 5;
constexpr int kNumberOfPatcherThreads = 3;
static_assert(kAssemblerBufferSize >= kJumpTableSize);
auto buffer = AllocateAssemblerBuffer(kAssemblerBufferSize, nullptr,
JitPermission::kMapAsJittable);
uint8_t* thunk_slot_buffer = buffer->start() + kBufferSlotStartOffset;
std::bitset<kAvailableBufferSlots> used_thunk_slots;
buffer->MakeWritableAndExecutable();
// Iterate through jump-table slots to hammer at different alignments within
// the jump-table, thereby increasing stress for variable-length ISAs.
Address slot_start = reinterpret_cast<Address>(buffer->start());
for (int slot = 0; slot < kJumpTableSlotCount; ++slot) {
TRACE("Hammering on jump table slot #%d ...\n", slot);
uint32_t slot_offset = JumpTableAssembler::JumpSlotIndexToOffset(slot);
std::vector<std::unique_ptr<TestingAssemblerBuffer>> thunk_buffers;
std::vector<Address> patcher_thunks;
{
Address jump_table_address = reinterpret_cast<Address>(buffer->start());
WritableJumpTablePair jump_table_pair =
WritableJumpTablePair::ForTesting(jump_table_address, buffer->size(),
jump_table_address, buffer->size());
// Patch the jump table slot to jump to itself. This will later be patched
// by the patchers.
Address slot_addr =
slot_start + JumpTableAssembler::JumpSlotIndexToOffset(slot);
JumpTableAssembler::PatchJumpTableSlot(jump_table_pair, slot_addr,
kNullAddress, slot_addr);
}
// For each patcher, generate two thunks where this patcher can emit code
// which finally jumps back to {slot} in the jump table.
for (int i = 0; i < 2 * kNumberOfPatcherThreads; ++i) {
Address thunk =
AllocateJumpTableThunk(slot_start + slot_offset, thunk_slot_buffer,
&used_thunk_slots, &thunk_buffers);
{
RwxMemoryWriteScopeForTesting write_scope;
ZapCode(thunk, kThunkBufferSize);
}
patcher_thunks.push_back(thunk);
TRACE(" generated jump thunk: " V8PRIxPTR_FMT "\n",
patcher_thunks.back());
}
// Start multiple runner threads that execute the jump table slot
// concurrently.
std::list<JumpTableRunner> runners;
for (int runner = 0; runner < kNumberOfRunnerThreads; ++runner) {
runners.emplace_back(slot_start + slot_offset, runner);
}
// Start multiple patcher thread that concurrently generate code and insert
// jumps to that into the jump table slot.
std::list<JumpTablePatcher> patchers;
// Only one patcher should modify the jump table at a time.
base::Mutex jump_table_mutex;
for (int i = 0; i < kNumberOfPatcherThreads; ++i) {
patchers.emplace_back(slot_start, slot, patcher_thunks[2 * i],
patcher_thunks[2 * i + 1], &jump_table_mutex);
}
global_stop_bit = 0; // Signal runners to keep going.
for (auto& runner : runners) CHECK(runner.Start());
for (auto& patcher : patchers) CHECK(patcher.Start());
for (auto& patcher : patchers) patcher.Join();
global_stop_bit = -1; // Signal runners to stop.
for (auto& runner : runners) runner.Join();
}
}
#undef __
#undef TRACE
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,104 @@
// Copyright 2021 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 "src/wasm/wasm-engine.h"
#include "test/cctest/cctest.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
namespace v8::internal::wasm {
TEST(MaxSteps) {
WasmRunner<uint32_t> r(TestExecutionTier::kLiftoffForFuzzing);
r.Build({WASM_LOOP(WASM_BR(0)), WASM_I32V(23)});
r.SetMaxSteps(10);
r.CheckCallViaJSTraps();
}
TEST(NondeterminismUnopF32) {
WasmRunner<float> r(TestExecutionTier::kLiftoffForFuzzing);
r.Build({WASM_F32_ABS(WASM_F32(std::nanf("")))});
CHECK(!WasmEngine::had_nondeterminism());
r.CheckCallViaJS(std::nanf(""));
CHECK(WasmEngine::had_nondeterminism());
}
TEST(NondeterminismUnopF64) {
WasmRunner<double> r(TestExecutionTier::kLiftoffForFuzzing);
r.Build({WASM_F64_ABS(WASM_F64(std::nan("")))});
CHECK(!WasmEngine::had_nondeterminism());
r.CheckCallViaJS(std::nan(""));
CHECK(WasmEngine::had_nondeterminism());
}
TEST(NondeterminismUnopF32x4AllNaN) {
WasmRunner<int32_t, float> r(TestExecutionTier::kLiftoffForFuzzing);
uint8_t value = 0;
r.Build({WASM_SIMD_UNOP(kExprF32x4Ceil,
WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(value))),
kExprDrop, WASM_ONE});
CHECK(!WasmEngine::had_nondeterminism());
r.CheckCallViaJS(1, 0.0);
CHECK(!WasmEngine::had_nondeterminism());
r.CheckCallViaJS(1, std::nanf(""));
CHECK(WasmEngine::had_nondeterminism());
}
TEST(NondeterminismUnopF32x4OneNaN) {
for (uint8_t lane = 0; lane < 4; ++lane) {
WasmRunner<int32_t, float> r(TestExecutionTier::kLiftoffForFuzzing);
r.Build({WASM_SIMD_F32x4_SPLAT(WASM_F32(0)), WASM_LOCAL_GET(0),
WASM_SIMD_OP(kExprF32x4ReplaceLane), lane,
WASM_SIMD_OP(kExprF32x4Ceil), kExprDrop, WASM_ONE});
CHECK(!WasmEngine::had_nondeterminism());
r.CheckCallViaJS(1, 0.0);
CHECK(!WasmEngine::had_nondeterminism());
r.CheckCallViaJS(1, std::nanf(""));
CHECK(WasmEngine::clear_nondeterminism());
}
}
TEST(NondeterminismUnopF64x2AllNaN) {
WasmRunner<int32_t, double> r(TestExecutionTier::kLiftoffForFuzzing);
uint8_t value = 0;
r.Build({WASM_SIMD_UNOP(kExprF64x2Ceil,
WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(value))),
kExprDrop, WASM_ONE});
CHECK(!WasmEngine::had_nondeterminism());
r.CheckCallViaJS(1, 0.0);
CHECK(!WasmEngine::had_nondeterminism());
r.CheckCallViaJS(1, std::nan(""));
CHECK(WasmEngine::clear_nondeterminism());
}
TEST(NondeterminismUnopF64x2OneNaN) {
for (uint8_t lane = 0; lane < 2; ++lane) {
WasmRunner<int32_t, double> r(TestExecutionTier::kLiftoffForFuzzing);
r.Build({WASM_SIMD_F64x2_SPLAT(WASM_F64(0)), WASM_LOCAL_GET(0),
WASM_SIMD_OP(kExprF64x2ReplaceLane), lane,
WASM_SIMD_OP(kExprF64x2Ceil), kExprDrop, WASM_ONE});
CHECK(!WasmEngine::had_nondeterminism());
r.CheckCallViaJS(1, 0.0);
CHECK(!WasmEngine::had_nondeterminism());
r.CheckCallViaJS(1, std::nan(""));
CHECK(WasmEngine::clear_nondeterminism());
}
}
TEST(NondeterminismBinop) {
WasmRunner<float> r(TestExecutionTier::kLiftoffForFuzzing);
r.Build({WASM_F32_ADD(WASM_F32(std::nanf("")), WASM_F32(0))});
CHECK(!WasmEngine::had_nondeterminism());
r.CheckCallViaJS(std::nanf(""));
CHECK(WasmEngine::clear_nondeterminism());
}
} // namespace v8::internal::wasm

View File

@ -0,0 +1,491 @@
// 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 "src/wasm/baseline/liftoff-compiler.h"
#include "src/wasm/compilation-environment-inl.h"
#include "src/wasm/wasm-debug.h"
#include "test/cctest/cctest.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace {
class LiftoffCompileEnvironment {
public:
LiftoffCompileEnvironment()
: isolate_(CcTest::InitIsolateOnce()),
handle_scope_(isolate_),
zone_(isolate_->allocator(), ZONE_NAME),
wasm_runner_(nullptr, kWasmOrigin, TestExecutionTier::kLiftoff, 0) {
// Add a table of length 1, for indirect calls.
wasm_runner_.builder().AddIndirectFunctionTable(nullptr, 1);
// Set tiered down such that we generate debugging code.
wasm_runner_.builder().SetDebugState();
}
struct TestFunction {
WasmCode* code;
FunctionBody body;
};
void CheckDeterministicCompilation(
std::initializer_list<ValueType> return_types,
std::initializer_list<ValueType> param_types,
std::initializer_list<uint8_t> raw_function_bytes) {
auto test_func = AddFunction(return_types, param_types, raw_function_bytes);
// Now compile the function with Liftoff two times.
CompilationEnv env = CompilationEnv::ForModule(
wasm_runner_.builder().trusted_instance_data()->native_module());
WasmDetectedFeatures detected1;
WasmDetectedFeatures detected2;
WasmCompilationResult result1 =
ExecuteLiftoffCompilation(&env, test_func.body,
LiftoffOptions{}
.set_func_index(test_func.code->index())
.set_detected_features(&detected1));
WasmCompilationResult result2 =
ExecuteLiftoffCompilation(&env, test_func.body,
LiftoffOptions{}
.set_func_index(test_func.code->index())
.set_detected_features(&detected2));
CHECK(result1.succeeded());
CHECK(result2.succeeded());
// Check that the generated code matches.
auto code1 =
base::VectorOf(result1.code_desc.buffer, result1.code_desc.instr_size);
auto code2 =
base::VectorOf(result2.code_desc.buffer, result2.code_desc.instr_size);
CHECK_EQ(code1, code2);
CHECK_EQ(detected1, detected2);
}
std::unique_ptr<DebugSideTable> GenerateDebugSideTable(
std::initializer_list<ValueType> return_types,
std::initializer_list<ValueType> param_types,
std::initializer_list<uint8_t> raw_function_bytes,
std::vector<int> breakpoints = {}) {
auto test_func = AddFunction(return_types, param_types, raw_function_bytes);
CompilationEnv env = CompilationEnv::ForModule(
wasm_runner_.builder().trusted_instance_data()->native_module());
std::unique_ptr<DebugSideTable> debug_side_table_via_compilation;
auto result = ExecuteLiftoffCompilation(
&env, test_func.body,
LiftoffOptions{}
.set_func_index(0)
.set_for_debugging(kForDebugging)
.set_breakpoints(base::VectorOf(breakpoints))
.set_debug_sidetable(&debug_side_table_via_compilation));
CHECK(result.succeeded());
// If there are no breakpoint, then {ExecuteLiftoffCompilation} should
// provide the same debug side table.
if (breakpoints.empty()) {
std::unique_ptr<DebugSideTable> debug_side_table =
GenerateLiftoffDebugSideTable(test_func.code);
CheckTableEquals(*debug_side_table, *debug_side_table_via_compilation);
}
return debug_side_table_via_compilation;
}
TestingModuleBuilder* builder() { return &wasm_runner_.builder(); }
private:
static void CheckTableEquals(const DebugSideTable& a,
const DebugSideTable& b) {
CHECK_EQ(a.num_locals(), b.num_locals());
CHECK_EQ(a.entries().size(), b.entries().size());
CHECK(std::equal(a.entries().begin(), a.entries().end(),
b.entries().begin(), b.entries().end(),
&CheckEntryEquals));
}
static bool CheckEntryEquals(const DebugSideTable::Entry& a,
const DebugSideTable::Entry& b) {
CHECK_EQ(a.pc_offset(), b.pc_offset());
CHECK_EQ(a.stack_height(), b.stack_height());
CHECK_EQ(a.changed_values(), b.changed_values());
return true;
}
FunctionSig* AddSig(std::initializer_list<ValueType> return_types,
std::initializer_list<ValueType> param_types) {
ValueType* storage = zone_.AllocateArray<ValueType>(return_types.size() +
param_types.size());
std::copy(return_types.begin(), return_types.end(), storage);
std::copy(param_types.begin(), param_types.end(),
storage + return_types.size());
FunctionSig* sig = zone_.New<FunctionSig>(return_types.size(),
param_types.size(), storage);
return sig;
}
TestFunction AddFunction(std::initializer_list<ValueType> return_types,
std::initializer_list<ValueType> param_types,
std::initializer_list<uint8_t> function_bytes) {
FunctionSig* sig = AddSig(return_types, param_types);
// Compile the function so we can get the WasmCode* which is later used to
// generate the debug side table lazily.
auto& func_compiler = wasm_runner_.NewFunction(sig, "f");
func_compiler.Build(base::VectorOf(function_bytes));
WasmCode* code =
wasm_runner_.builder().GetFunctionCode(func_compiler.function_index());
// Get the wire bytes created by the function compiler (including locals
// declaration and the trailing "end" opcode).
NativeModule* native_module = code->native_module();
auto* function = &native_module->module()->functions[code->index()];
base::Vector<const uint8_t> function_wire_bytes =
native_module->wire_bytes().SubVector(function->code.offset(),
function->code.end_offset());
bool is_shared =
native_module->module()->type(function->sig_index).is_shared;
FunctionBody body{sig, 0, function_wire_bytes.begin(),
function_wire_bytes.end(), is_shared};
return {code, body};
}
Isolate* isolate_;
HandleScope handle_scope_;
Zone zone_;
// wasm_runner_ is used to build actual code objects needed to request lazy
// generation of debug side tables.
WasmRunnerBase wasm_runner_;
WasmCodeRefScope code_ref_scope_;
};
struct DebugSideTableEntry {
int stack_height;
std::vector<DebugSideTable::Entry::Value> changed_values;
// Construct via vector or implicitly via initializer list.
DebugSideTableEntry(int stack_height,
std::vector<DebugSideTable::Entry::Value> changed_values)
: stack_height(stack_height), changed_values(std::move(changed_values)) {}
DebugSideTableEntry(
int stack_height,
std::initializer_list<DebugSideTable::Entry::Value> changed_values)
: stack_height(stack_height), changed_values(changed_values) {}
bool operator==(const DebugSideTableEntry& other) const {
return stack_height == other.stack_height &&
std::equal(changed_values.begin(), changed_values.end(),
other.changed_values.begin(), other.changed_values.end(),
CheckValueEquals);
}
// Check for equality, but ignore exact register and stack offset.
static bool CheckValueEquals(const DebugSideTable::Entry::Value& a,
const DebugSideTable::Entry::Value& b) {
return a.index == b.index && a.type == b.type && a.storage == b.storage &&
(a.storage != DebugSideTable::Entry::kConstant ||
a.i32_const == b.i32_const);
}
};
// Debug builds will print the vector of DebugSideTableEntry.
#ifdef DEBUG
std::ostream& operator<<(std::ostream& out, const DebugSideTableEntry& entry) {
out << "stack height " << entry.stack_height << ", changed: {";
const char* comma = "";
for (auto& v : entry.changed_values) {
out << comma << v.index << ":" << v.type.name() << " ";
switch (v.storage) {
case DebugSideTable::Entry::kConstant:
out << "const:" << v.i32_const;
break;
case DebugSideTable::Entry::kRegister:
out << "reg";
break;
case DebugSideTable::Entry::kStack:
out << "stack";
break;
}
comma = ", ";
}
return out << "}";
}
std::ostream& operator<<(std::ostream& out,
const std::vector<DebugSideTableEntry>& entries) {
return out << PrintCollection(entries);
}
#endif // DEBUG
// Named constructors to make the tests more readable.
DebugSideTable::Entry::Value Constant(int index, ValueType type,
int32_t constant) {
DebugSideTable::Entry::Value value;
value.index = index;
value.type = type;
value.storage = DebugSideTable::Entry::kConstant;
value.i32_const = constant;
return value;
}
DebugSideTable::Entry::Value Register(int index, ValueType type) {
DebugSideTable::Entry::Value value;
value.index = index;
value.type = type;
value.storage = DebugSideTable::Entry::kRegister;
return value;
}
DebugSideTable::Entry::Value Stack(int index, ValueType type) {
DebugSideTable::Entry::Value value;
value.index = index;
value.type = type;
value.storage = DebugSideTable::Entry::kStack;
return value;
}
void CheckDebugSideTable(std::vector<DebugSideTableEntry> expected_entries,
const wasm::DebugSideTable* debug_side_table) {
std::vector<DebugSideTableEntry> entries;
for (auto& entry : debug_side_table->entries()) {
entries.emplace_back(
entry.stack_height(),
std::vector<DebugSideTable::Entry::Value>{
entry.changed_values().begin(), entry.changed_values().end()});
}
CHECK_EQ(expected_entries, entries);
}
} // namespace
TEST(Liftoff_deterministic_simple) {
LiftoffCompileEnvironment env;
env.CheckDeterministicCompilation(
{kWasmI32}, {kWasmI32, kWasmI32},
{WASM_I32_ADD(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1))});
}
TEST(Liftoff_deterministic_call) {
LiftoffCompileEnvironment env;
env.CheckDeterministicCompilation(
{kWasmI32}, {kWasmI32},
{WASM_I32_ADD(WASM_CALL_FUNCTION(0, WASM_LOCAL_GET(0)),
WASM_LOCAL_GET(0))});
}
TEST(Liftoff_deterministic_indirect_call) {
LiftoffCompileEnvironment env;
env.CheckDeterministicCompilation(
{kWasmI32}, {kWasmI32},
{WASM_I32_ADD(WASM_CALL_INDIRECT(0, WASM_LOCAL_GET(0), WASM_I32V_1(47)),
WASM_LOCAL_GET(0))});
}
TEST(Liftoff_deterministic_loop) {
LiftoffCompileEnvironment env;
env.CheckDeterministicCompilation(
{kWasmI32}, {kWasmI32},
{WASM_LOOP(WASM_BR_IF(0, WASM_LOCAL_GET(0))), WASM_LOCAL_GET(0)});
}
TEST(Liftoff_deterministic_trap) {
LiftoffCompileEnvironment env;
env.CheckDeterministicCompilation(
{kWasmI32}, {kWasmI32, kWasmI32},
{WASM_I32_DIVS(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1))});
}
TEST(Liftoff_debug_side_table_simple) {
LiftoffCompileEnvironment env;
auto debug_side_table = env.GenerateDebugSideTable(
{kWasmI32}, {kWasmI32, kWasmI32},
{WASM_I32_ADD(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1))});
CheckDebugSideTable(
{
// function entry, locals in registers.
{2, {Register(0, kWasmI32), Register(1, kWasmI32)}},
// OOL stack check, locals spilled, stack still empty.
{2, {Stack(0, kWasmI32), Stack(1, kWasmI32)}},
},
debug_side_table.get());
}
TEST(Liftoff_debug_side_table_call) {
LiftoffCompileEnvironment env;
auto debug_side_table = env.GenerateDebugSideTable(
{kWasmI32}, {kWasmI32},
{WASM_I32_ADD(WASM_CALL_FUNCTION(0, WASM_LOCAL_GET(0)),
WASM_LOCAL_GET(0))});
CheckDebugSideTable(
{
// function entry, local in register.
{1, {Register(0, kWasmI32)}},
// call, local spilled, stack empty.
{1, {Stack(0, kWasmI32)}},
// OOL stack check, local spilled as before, stack empty.
{1, {}},
},
debug_side_table.get());
}
TEST(Liftoff_debug_side_table_call_const) {
LiftoffCompileEnvironment env;
constexpr int kConst = 13;
auto debug_side_table = env.GenerateDebugSideTable(
{kWasmI32}, {kWasmI32},
{WASM_LOCAL_SET(0, WASM_I32V_1(kConst)),
WASM_I32_ADD(WASM_CALL_FUNCTION(0, WASM_LOCAL_GET(0)),
WASM_LOCAL_GET(0))});
CheckDebugSideTable(
{
// function entry, local in register.
{1, {Register(0, kWasmI32)}},
// call, local is kConst.
{1, {Constant(0, kWasmI32, kConst)}},
// OOL stack check, local spilled.
{1, {Stack(0, kWasmI32)}},
},
debug_side_table.get());
}
TEST(Liftoff_debug_side_table_indirect_call) {
LiftoffCompileEnvironment env;
constexpr int kConst = 47;
auto debug_side_table = env.GenerateDebugSideTable(
{kWasmI32}, {kWasmI32},
{WASM_I32_ADD(
WASM_CALL_INDIRECT(0, WASM_I32V_1(kConst), WASM_LOCAL_GET(0)),
WASM_LOCAL_GET(0))});
CheckDebugSideTable(
{
// function entry, local in register.
{1, {Register(0, kWasmI32)}},
// indirect call, local spilled, stack empty.
{1, {Stack(0, kWasmI32)}},
// OOL stack check, local still spilled.
{1, {}},
// OOL trap (invalid index), local still spilled, stack has {kConst,
// kStack}.
{3, {Constant(1, kWasmI32, kConst), Stack(2, kWasmI32)}},
// OOL trap (sig mismatch), stack unmodified.
{3, {}},
},
debug_side_table.get());
}
TEST(Liftoff_debug_side_table_loop) {
LiftoffCompileEnvironment env;
constexpr int kConst = 42;
auto debug_side_table = env.GenerateDebugSideTable(
{kWasmI32}, {kWasmI32},
{WASM_I32V_1(kConst), WASM_LOOP(WASM_BR_IF(0, WASM_LOCAL_GET(0)))});
CheckDebugSideTable(
{
// function entry, local in register.
{1, {Register(0, kWasmI32)}},
// OOL stack check, local spilled, stack empty.
{1, {Stack(0, kWasmI32)}},
// OOL loop stack check, local still spilled, stack has {kConst}.
{2, {Constant(1, kWasmI32, kConst)}},
},
debug_side_table.get());
}
TEST(Liftoff_debug_side_table_trap) {
LiftoffCompileEnvironment env;
auto debug_side_table = env.GenerateDebugSideTable(
{kWasmI32}, {kWasmI32, kWasmI32},
{WASM_I32_DIVS(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1))});
CheckDebugSideTable(
{
// function entry, locals in registers.
{2, {Register(0, kWasmI32), Register(1, kWasmI32)}},
// OOL stack check, local spilled, stack empty.
{2, {Stack(0, kWasmI32), Stack(1, kWasmI32)}},
// OOL trap (div by zero), stack as before.
{2, {}},
// OOL trap (unrepresentable), stack as before.
{2, {}},
},
debug_side_table.get());
}
TEST(Liftoff_breakpoint_simple) {
LiftoffCompileEnvironment env;
// Set two breakpoints. At both locations, values are live in registers.
auto debug_side_table = env.GenerateDebugSideTable(
{kWasmI32}, {kWasmI32, kWasmI32},
{WASM_I32_ADD(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1))},
{
1, // break at beginning of function (first local.get)
5 // break at i32.add
});
CheckDebugSideTable(
{
// First break point, locals in registers.
{2, {Register(0, kWasmI32), Register(1, kWasmI32)}},
// Second break point, locals unchanged, two register stack values.
{4, {Register(2, kWasmI32), Register(3, kWasmI32)}},
// OOL stack check, locals spilled, stack empty.
{2, {Stack(0, kWasmI32), Stack(1, kWasmI32)}},
},
debug_side_table.get());
}
TEST(Liftoff_debug_side_table_catch_all) {
LiftoffCompileEnvironment env;
TestSignatures sigs;
int ex = env.builder()->AddException(sigs.v_v());
ValueType exception_type = kWasmAnyRef.AsNonNull();
auto debug_side_table = env.GenerateDebugSideTable(
{}, {kWasmI32},
{WASM_TRY_CATCH_ALL_T(kWasmI32, WASM_STMTS(WASM_I32V(0), WASM_THROW(ex)),
WASM_I32V(1)),
WASM_DROP},
{
18 // Break at the end of the try block.
});
CheckDebugSideTable(
{
// function entry.
{1, {Register(0, kWasmI32)}},
// throw.
{2, {Stack(0, kWasmI32), Constant(1, kWasmI32, 0)}},
// breakpoint.
{3, {Register(1, exception_type), Constant(2, kWasmI32, 1)}},
{1, {}},
},
debug_side_table.get());
}
TEST(Regress1199526) {
LiftoffCompileEnvironment env;
ValueType exception_type = kWasmAnyRef.AsNonNull();
auto debug_side_table = env.GenerateDebugSideTable(
{}, {},
{kExprTry, kVoidCode, kExprCallFunction, 0, kExprCatchAll, kExprLoop,
kVoidCode, kExprEnd, kExprEnd},
{});
CheckDebugSideTable(
{
// function entry.
{0, {}},
// break on entry.
{0, {}},
// function call.
{0, {}},
// loop stack check.
{1, {Stack(0, exception_type)}},
},
debug_side_table.get());
}
} // namespace wasm
} // namespace internal
} // namespace v8

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,232 @@
// Copyright 2016 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 <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "src/base/platform/elapsed-timer.h"
#include "src/codegen/assembler-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/common/value-helper.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
namespace v8 {
namespace internal {
namespace wasm {
TEST(RunAsmJs_Int32AsmjsDivS) {
WasmRunner<int32_t, int32_t, int32_t> r(TestExecutionTier::kTurbofan,
kAsmJsSloppyOrigin);
r.Build({WASM_I32_ASMJS_DIVS(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1))});
const int32_t kMin = std::numeric_limits<int32_t>::min();
CHECK_EQ(0, r.Call(0, 100));
CHECK_EQ(0, r.Call(100, 0));
CHECK_EQ(0, r.Call(-1001, 0));
CHECK_EQ(kMin, r.Call(kMin, -1));
CHECK_EQ(0, r.Call(kMin, 0));
}
TEST(RunAsmJs_Int32AsmjsRemS) {
WasmRunner<int32_t, int32_t, int32_t> r(TestExecutionTier::kTurbofan,
kAsmJsSloppyOrigin);
r.Build({WASM_I32_ASMJS_REMS(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1))});
const int32_t kMin = std::numeric_limits<int32_t>::min();
CHECK_EQ(33, r.Call(133, 100));
CHECK_EQ(0, r.Call(kMin, -1));
CHECK_EQ(0, r.Call(100, 0));
CHECK_EQ(0, r.Call(-1001, 0));
CHECK_EQ(0, r.Call(kMin, 0));
}
TEST(RunAsmJs_Int32AsmjsDivU) {
WasmRunner<int32_t, int32_t, int32_t> r(TestExecutionTier::kTurbofan,
kAsmJsSloppyOrigin);
r.Build({WASM_I32_ASMJS_DIVU(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1))});
const int32_t kMin = std::numeric_limits<int32_t>::min();
CHECK_EQ(0, r.Call(0, 100));
CHECK_EQ(0, r.Call(kMin, -1));
CHECK_EQ(0, r.Call(100, 0));
CHECK_EQ(0, r.Call(-1001, 0));
CHECK_EQ(0, r.Call(kMin, 0));
}
TEST(RunAsmJs_Int32AsmjsRemU) {
WasmRunner<int32_t, int32_t, int32_t> r(TestExecutionTier::kTurbofan,
kAsmJsSloppyOrigin);
r.Build({WASM_I32_ASMJS_REMU(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1))});
const int32_t kMin = std::numeric_limits<int32_t>::min();
CHECK_EQ(17, r.Call(217, 100));
CHECK_EQ(0, r.Call(100, 0));
CHECK_EQ(0, r.Call(-1001, 0));
CHECK_EQ(0, r.Call(kMin, 0));
CHECK_EQ(kMin, r.Call(kMin, -1));
}
TEST(RunAsmJs_I32AsmjsSConvertF32) {
WasmRunner<int32_t, float> r(TestExecutionTier::kTurbofan,
kAsmJsSloppyOrigin);
r.Build({WASM_I32_ASMJS_SCONVERTF32(WASM_LOCAL_GET(0))});
FOR_FLOAT32_INPUTS(i) {
int32_t expected = DoubleToInt32(i);
CHECK_EQ(expected, r.Call(i));
}
}
TEST(RunAsmJs_I32AsmjsSConvertF64) {
WasmRunner<int32_t, double> r(TestExecutionTier::kTurbofan,
kAsmJsSloppyOrigin);
r.Build({WASM_I32_ASMJS_SCONVERTF64(WASM_LOCAL_GET(0))});
FOR_FLOAT64_INPUTS(i) {
int32_t expected = DoubleToInt32(i);
CHECK_EQ(expected, r.Call(i));
}
}
TEST(RunAsmJs_I32AsmjsUConvertF32) {
WasmRunner<uint32_t, float> r(TestExecutionTier::kTurbofan,
kAsmJsSloppyOrigin);
r.Build({WASM_I32_ASMJS_UCONVERTF32(WASM_LOCAL_GET(0))});
FOR_FLOAT32_INPUTS(i) {
uint32_t expected = DoubleToUint32(i);
CHECK_EQ(expected, r.Call(i));
}
}
TEST(RunAsmJs_I32AsmjsUConvertF64) {
WasmRunner<uint32_t, double> r(TestExecutionTier::kTurbofan,
kAsmJsSloppyOrigin);
r.Build({WASM_I32_ASMJS_UCONVERTF64(WASM_LOCAL_GET(0))});
FOR_FLOAT64_INPUTS(i) {
uint32_t expected = DoubleToUint32(i);
CHECK_EQ(expected, r.Call(i));
}
}
TEST(RunAsmJs_LoadMemI32_oob_asm) {
WasmRunner<int32_t, uint32_t> r(TestExecutionTier::kTurbofan,
kAsmJsSloppyOrigin);
int32_t* memory = r.builder().AddMemoryElems<int32_t>(8);
r.builder().RandomizeMemory(1112);
r.Build({WASM_I32_ASMJS_LOADMEM(WASM_LOCAL_GET(0))});
memory[0] = 999999;
CHECK_EQ(999999, r.Call(0u));
// TODO(titzer): offset 29-31 should also be OOB.
for (uint32_t offset = 32; offset < 40; offset++) {
CHECK_EQ(0, r.Call(offset));
}
for (uint32_t offset = 0x80000000; offset < 0x80000010; offset++) {
CHECK_EQ(0, r.Call(offset));
}
}
TEST(RunAsmJs_LoadMemF32_oob_asm) {
WasmRunner<float, uint32_t> r(TestExecutionTier::kTurbofan,
kAsmJsSloppyOrigin);
float* memory = r.builder().AddMemoryElems<float>(8);
r.builder().RandomizeMemory(1112);
r.Build({WASM_F32_ASMJS_LOADMEM(WASM_LOCAL_GET(0))});
memory[0] = 9999.5f;
CHECK_EQ(9999.5f, r.Call(0u));
// TODO(titzer): offset 29-31 should also be OOB.
for (uint32_t offset = 32; offset < 40; offset++) {
CHECK(std::isnan(r.Call(offset)));
}
for (uint32_t offset = 0x80000000; offset < 0x80000010; offset++) {
CHECK(std::isnan(r.Call(offset)));
}
}
TEST(RunAsmJs_LoadMemF64_oob_asm) {
WasmRunner<double, uint32_t> r(TestExecutionTier::kTurbofan,
kAsmJsSloppyOrigin);
double* memory = r.builder().AddMemoryElems<double>(8);
r.builder().RandomizeMemory(1112);
r.Build({WASM_F64_ASMJS_LOADMEM(WASM_LOCAL_GET(0))});
memory[0] = 9799.5;
CHECK_EQ(9799.5, r.Call(0u));
memory[1] = 11799.25;
CHECK_EQ(11799.25, r.Call(8u));
// TODO(titzer): offset 57-63 should also be OOB.
for (uint32_t offset = 64; offset < 80; offset++) {
CHECK(std::isnan(r.Call(offset)));
}
for (uint32_t offset = 0x80000000; offset < 0x80000010; offset++) {
CHECK(std::isnan(r.Call(offset)));
}
}
TEST(RunAsmJs_StoreMemI32_oob_asm) {
WasmRunner<int32_t, uint32_t, uint32_t> r(TestExecutionTier::kTurbofan,
kAsmJsSloppyOrigin);
int32_t* memory = r.builder().AddMemoryElems<int32_t>(8);
r.builder().RandomizeMemory(1112);
r.Build({WASM_I32_ASMJS_STOREMEM(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1))});
memory[0] = 7777;
CHECK_EQ(999999, r.Call(0u, 999999));
CHECK_EQ(999999, memory[0]);
// TODO(titzer): offset 29-31 should also be OOB.
for (uint32_t offset = 32; offset < 40; offset++) {
CHECK_EQ(8888, r.Call(offset, 8888));
}
for (uint32_t offset = 0x10000000; offset < 0xF0000000; offset += 0x1000000) {
CHECK_EQ(7777, r.Call(offset, 7777));
}
}
TEST(RunAsmJs_Int32AsmjsDivS_byzero_const) {
for (int8_t denom = -2; denom < 8; ++denom) {
WasmRunner<int32_t, int32_t> r(TestExecutionTier::kTurbofan,
kAsmJsSloppyOrigin);
r.Build({WASM_I32_ASMJS_DIVS(WASM_LOCAL_GET(0), WASM_I32V_1(denom))});
FOR_INT32_INPUTS(i) {
if (denom == 0) {
CHECK_EQ(0, r.Call(i));
} else if (denom == -1 && i == std::numeric_limits<int32_t>::min()) {
CHECK_EQ(std::numeric_limits<int32_t>::min(), r.Call(i));
} else {
CHECK_EQ(i / denom, r.Call(i));
}
}
}
}
TEST(RunAsmJs_Int32AsmjsRemS_byzero_const) {
for (int8_t denom = -2; denom < 8; ++denom) {
WasmRunner<int32_t, int32_t> r(TestExecutionTier::kTurbofan,
kAsmJsSloppyOrigin);
r.Build({WASM_I32_ASMJS_REMS(WASM_LOCAL_GET(0), WASM_I32V_1(denom))});
FOR_INT32_INPUTS(i) {
if (denom == 0) {
CHECK_EQ(0, r.Call(i));
} else if (denom == -1 && i == std::numeric_limits<int32_t>::min()) {
CHECK_EQ(0, r.Call(i));
} else {
CHECK_EQ(i % denom, r.Call(i));
}
}
}
}
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,580 @@
// Copyright 2017 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/cctest/wasm/wasm-atomics-utils.h"
#include "test/common/wasm/wasm-macro-gen.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace test_run_wasm_atomics {
void RunU32BinOp(TestExecutionTier execution_tier, WasmOpcode wasm_op,
Uint32BinOp expected_op) {
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint32_t* memory =
r.builder().AddMemoryElems<uint32_t>(kWasmPageSize / sizeof(uint32_t));
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_BINOP(wasm_op, WASM_ZERO, WASM_LOCAL_GET(0),
MachineRepresentation::kWord32)});
FOR_UINT32_INPUTS(i) {
uint32_t initial = i;
FOR_UINT32_INPUTS(j) {
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call(j));
uint32_t expected = expected_op(i, j);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
}
void RunU32BinOp_Const(TestExecutionTier execution_tier, WasmOpcode wasm_op,
Uint32BinOp expected_op) {
FOR_UINT32_INPUTS(i) {
WasmRunner<uint32_t> r(execution_tier);
uint32_t* memory =
r.builder().AddMemoryElems<uint32_t>(kWasmPageSize / sizeof(uint32_t));
r.Build({WASM_ATOMICS_BINOP(wasm_op, WASM_ZERO, WASM_I32V(i),
MachineRepresentation::kWord32)});
FOR_UINT32_INPUTS(j) {
uint32_t initial = j;
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call());
uint32_t expected = expected_op(j, i);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
}
#define TEST_OPERATION(Name) \
WASM_EXEC_TEST(I32Atomic##Name) { \
RunU32BinOp(execution_tier, kExprI32Atomic##Name, Name); \
RunU32BinOp_Const(execution_tier, kExprI32Atomic##Name, Name); \
}
WASM_ATOMIC_OPERATION_LIST(TEST_OPERATION)
#undef TEST_OPERATION
void RunU16BinOp(TestExecutionTier tier, WasmOpcode wasm_op,
Uint16BinOp expected_op) {
WasmRunner<uint32_t, uint32_t> r(tier);
uint16_t* memory =
r.builder().AddMemoryElems<uint16_t>(kWasmPageSize / sizeof(uint16_t));
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_BINOP(wasm_op, WASM_ZERO, WASM_LOCAL_GET(0),
MachineRepresentation::kWord16)});
FOR_UINT16_INPUTS(i) {
uint16_t initial = i;
FOR_UINT16_INPUTS(j) {
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call(j));
uint16_t expected = expected_op(i, j);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
}
void RunU16BinOp_Const(TestExecutionTier tier, WasmOpcode wasm_op,
Uint16BinOp expected_op) {
FOR_UINT16_INPUTS(i) {
WasmRunner<uint32_t> r(tier);
uint16_t* memory =
r.builder().AddMemoryElems<uint16_t>(kWasmPageSize / sizeof(uint16_t));
r.Build({WASM_ATOMICS_BINOP(wasm_op, WASM_ZERO, WASM_I32V(i),
MachineRepresentation::kWord16)});
FOR_UINT16_INPUTS(j) {
uint16_t initial = j;
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call());
uint16_t expected = expected_op(j, i);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
}
#define TEST_OPERATION(Name) \
WASM_EXEC_TEST(I32Atomic##Name##16U) { \
RunU16BinOp(execution_tier, kExprI32Atomic##Name##16U, Name); \
RunU16BinOp_Const(execution_tier, kExprI32Atomic##Name##16U, Name); \
}
WASM_ATOMIC_OPERATION_LIST(TEST_OPERATION)
#undef TEST_OPERATION
void RunU8BinOp(TestExecutionTier execution_tier, WasmOpcode wasm_op,
Uint8BinOp expected_op) {
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint8_t* memory = r.builder().AddMemoryElems<uint8_t>(kWasmPageSize);
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_BINOP(wasm_op, WASM_ZERO, WASM_LOCAL_GET(0),
MachineRepresentation::kWord8)});
FOR_UINT8_INPUTS(i) {
uint8_t initial = i;
FOR_UINT8_INPUTS(j) {
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call(j));
uint8_t expected = expected_op(i, j);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
}
void RunU8BinOp_Const(TestExecutionTier execution_tier, WasmOpcode wasm_op,
Uint8BinOp expected_op) {
FOR_UINT8_INPUTS(i) {
WasmRunner<uint32_t> r(execution_tier);
uint8_t* memory = r.builder().AddMemoryElems<uint8_t>(kWasmPageSize);
r.Build({WASM_ATOMICS_BINOP(wasm_op, WASM_ZERO, WASM_I32V(i),
MachineRepresentation::kWord8)});
FOR_UINT8_INPUTS(j) {
uint8_t initial = j;
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call());
uint8_t expected = expected_op(j, i);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
}
#define TEST_OPERATION(Name) \
WASM_EXEC_TEST(I32Atomic##Name##8U) { \
RunU8BinOp(execution_tier, kExprI32Atomic##Name##8U, Name); \
RunU8BinOp_Const(execution_tier, kExprI32Atomic##Name##8U, Name); \
}
WASM_ATOMIC_OPERATION_LIST(TEST_OPERATION)
#undef TEST_OPERATION
void RunU64BinOp(TestExecutionTier execution_tier, WasmOpcode wasm_op,
Uint64BinOp expected_op) {
WasmRunner<uint64_t, uint64_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
r.Build({WASM_ATOMICS_BINOP(wasm_op, WASM_ZERO, WASM_LOCAL_GET(0),
MachineRepresentation::kWord64)});
FOR_UINT64_INPUTS(i) {
uint64_t initial = i;
FOR_UINT64_INPUTS(j) {
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call(j));
uint64_t expected = expected_op(i, j);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
}
void RunU64BinOp_Const(TestExecutionTier execution_tier, WasmOpcode wasm_op,
Uint64BinOp expected_op) {
FOR_UINT64_INPUTS(i) {
WasmRunner<uint64_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
r.Build({WASM_ATOMICS_BINOP(wasm_op, WASM_ZERO, WASM_I64V_10(i),
MachineRepresentation::kWord64)});
FOR_UINT64_INPUTS(j) {
uint64_t initial = j;
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call());
uint64_t expected = expected_op(j, i);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
}
#define TEST_OPERATION(Name) \
WASM_EXEC_TEST(I64Atomic##Name) { \
RunU64BinOp(execution_tier, kExprI64Atomic##Name, Name); \
RunU64BinOp_Const(execution_tier, kExprI64Atomic##Name, Name); \
}
WASM_ATOMIC_OPERATION_LIST(TEST_OPERATION)
#undef TEST_OPERATION
WASM_EXEC_TEST(I32AtomicCompareExchange) {
WasmRunner<uint32_t, uint32_t, uint32_t> r(execution_tier);
uint32_t* memory =
r.builder().AddMemoryElems<uint32_t>(kWasmPageSize / sizeof(uint32_t));
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_TERNARY_OP(kExprI32AtomicCompareExchange, WASM_ZERO,
WASM_LOCAL_GET(0), WASM_LOCAL_GET(1),
MachineRepresentation::kWord32)});
FOR_UINT32_INPUTS(i) {
uint32_t initial = i;
FOR_UINT32_INPUTS(j) {
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call(i, j));
uint32_t expected = CompareExchange(initial, i, j);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
}
WASM_EXEC_TEST(I32AtomicCompareExchange16U) {
WasmRunner<uint32_t, uint32_t, uint32_t> r(execution_tier);
uint16_t* memory =
r.builder().AddMemoryElems<uint16_t>(kWasmPageSize / sizeof(uint16_t));
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_TERNARY_OP(kExprI32AtomicCompareExchange16U, WASM_ZERO,
WASM_LOCAL_GET(0), WASM_LOCAL_GET(1),
MachineRepresentation::kWord16)});
FOR_UINT16_INPUTS(i) {
uint16_t initial = i;
FOR_UINT16_INPUTS(j) {
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call(i, j));
uint16_t expected = CompareExchange(initial, i, j);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
}
WASM_EXEC_TEST(I32AtomicCompareExchange8U) {
WasmRunner<uint32_t, uint32_t, uint32_t> r(execution_tier);
uint8_t* memory = r.builder().AddMemoryElems<uint8_t>(kWasmPageSize);
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_TERNARY_OP(kExprI32AtomicCompareExchange8U, WASM_ZERO,
WASM_LOCAL_GET(0), WASM_LOCAL_GET(1),
MachineRepresentation::kWord8)});
FOR_UINT8_INPUTS(i) {
uint8_t initial = i;
FOR_UINT8_INPUTS(j) {
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call(i, j));
uint8_t expected = CompareExchange(initial, i, j);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
}
WASM_EXEC_TEST(I32AtomicCompareExchange_fail) {
WasmRunner<uint32_t, uint32_t, uint32_t> r(execution_tier);
uint32_t* memory =
r.builder().AddMemoryElems<uint32_t>(kWasmPageSize / sizeof(uint32_t));
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_TERNARY_OP(kExprI32AtomicCompareExchange, WASM_ZERO,
WASM_LOCAL_GET(0), WASM_LOCAL_GET(1),
MachineRepresentation::kWord32)});
// The original value at the memory location.
uint32_t old_val = 4;
// The value we use as the expected value for the compare-exchange so that it
// fails.
uint32_t expected = 6;
// The new value for the compare-exchange.
uint32_t new_val = 5;
r.builder().WriteMemory(&memory[0], old_val);
CHECK_EQ(old_val, r.Call(expected, new_val));
}
WASM_EXEC_TEST(I32AtomicLoad) {
WasmRunner<uint32_t> r(execution_tier);
uint32_t* memory =
r.builder().AddMemoryElems<uint32_t>(kWasmPageSize / sizeof(uint32_t));
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_LOAD_OP(kExprI32AtomicLoad, WASM_ZERO,
MachineRepresentation::kWord32)});
FOR_UINT32_INPUTS(i) {
uint32_t expected = i;
r.builder().WriteMemory(&memory[0], expected);
CHECK_EQ(expected, r.Call());
}
}
WASM_EXEC_TEST(I32AtomicLoad16U) {
WasmRunner<uint32_t> r(execution_tier);
uint16_t* memory =
r.builder().AddMemoryElems<uint16_t>(kWasmPageSize / sizeof(uint16_t));
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_LOAD_OP(kExprI32AtomicLoad16U, WASM_ZERO,
MachineRepresentation::kWord16)});
FOR_UINT16_INPUTS(i) {
uint16_t expected = i;
r.builder().WriteMemory(&memory[0], expected);
CHECK_EQ(expected, r.Call());
}
}
WASM_EXEC_TEST(I32AtomicLoad8U) {
WasmRunner<uint32_t> r(execution_tier);
uint8_t* memory = r.builder().AddMemoryElems<uint8_t>(kWasmPageSize);
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_LOAD_OP(kExprI32AtomicLoad8U, WASM_ZERO,
MachineRepresentation::kWord8)});
FOR_UINT8_INPUTS(i) {
uint8_t expected = i;
r.builder().WriteMemory(&memory[0], expected);
CHECK_EQ(expected, r.Call());
}
}
WASM_EXEC_TEST(I32AtomicStoreLoad) {
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint32_t* memory =
r.builder().AddMemoryElems<uint32_t>(kWasmPageSize / sizeof(uint32_t));
r.builder().SetMemoryShared();
r.Build(
{WASM_ATOMICS_STORE_OP(kExprI32AtomicStore, WASM_ZERO, WASM_LOCAL_GET(0),
MachineRepresentation::kWord32),
WASM_ATOMICS_LOAD_OP(kExprI32AtomicLoad, WASM_ZERO,
MachineRepresentation::kWord32)});
FOR_UINT32_INPUTS(i) {
uint32_t expected = i;
CHECK_EQ(expected, r.Call(i));
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
WASM_EXEC_TEST(I32AtomicStoreLoad16U) {
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint16_t* memory =
r.builder().AddMemoryElems<uint16_t>(kWasmPageSize / sizeof(uint16_t));
r.builder().SetMemoryShared();
r.Build(
{WASM_ATOMICS_STORE_OP(kExprI32AtomicStore16U, WASM_ZERO,
WASM_LOCAL_GET(0), MachineRepresentation::kWord16),
WASM_ATOMICS_LOAD_OP(kExprI32AtomicLoad16U, WASM_ZERO,
MachineRepresentation::kWord16)});
FOR_UINT16_INPUTS(i) {
uint16_t expected = i;
CHECK_EQ(expected, r.Call(i));
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
WASM_EXEC_TEST(I32AtomicStoreLoad8U) {
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint8_t* memory = r.builder().AddMemoryElems<uint8_t>(kWasmPageSize);
r.builder().SetMemoryShared();
r.Build(
{WASM_ATOMICS_STORE_OP(kExprI32AtomicStore8U, WASM_ZERO,
WASM_LOCAL_GET(0), MachineRepresentation::kWord8),
WASM_ATOMICS_LOAD_OP(kExprI32AtomicLoad8U, WASM_ZERO,
MachineRepresentation::kWord8)});
FOR_UINT8_INPUTS(i) {
uint8_t expected = i;
CHECK_EQ(expected, r.Call(i));
CHECK_EQ(i, r.builder().ReadMemory(&memory[0]));
}
}
WASM_EXEC_TEST(I32AtomicStoreParameter) {
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint32_t* memory =
r.builder().AddMemoryElems<uint32_t>(kWasmPageSize / sizeof(uint32_t));
r.builder().SetMemoryShared();
r.Build(
{WASM_ATOMICS_STORE_OP(kExprI32AtomicStore, WASM_ZERO, WASM_LOCAL_GET(0),
MachineRepresentation::kWord32),
WASM_ATOMICS_BINOP(kExprI32AtomicAdd, WASM_ZERO, WASM_LOCAL_GET(0),
MachineRepresentation::kWord32)});
CHECK_EQ(10, r.Call(10));
CHECK_EQ(20, r.builder().ReadMemory(&memory[0]));
}
WASM_EXEC_TEST(AtomicFence) {
WasmRunner<uint32_t> r(execution_tier);
// Note that this test specifically doesn't use a shared memory, as the fence
// instruction does not target a particular linear memory. It may occur in
// modules which declare no memory, or a non-shared memory, without causing a
// validation error.
r.Build({WASM_ATOMICS_FENCE, WASM_ZERO});
CHECK_EQ(0, r.Call());
}
WASM_EXEC_TEST(AtomicStoreNoConsideredEffectful) {
// Use {Load} instead of {ProtectedLoad}.
FLAG_SCOPE(wasm_enforce_bounds_checks);
WasmRunner<uint32_t> r(execution_tier);
r.builder().AddMemoryElems<int32_t>(kWasmPageSize / sizeof(int32_t));
r.builder().SetMemoryShared();
r.Build(
{WASM_LOAD_MEM(MachineType::Int64(), WASM_ZERO),
WASM_ATOMICS_STORE_OP(kExprI32AtomicStore, WASM_ZERO, WASM_I32V_1(20),
MachineRepresentation::kWord32),
kExprI64Eqz});
CHECK_EQ(1, r.Call());
}
void RunNoEffectTest(TestExecutionTier execution_tier, WasmOpcode wasm_op) {
// Use {Load} instead of {ProtectedLoad}.
FLAG_SCOPE(wasm_enforce_bounds_checks);
WasmRunner<uint32_t> r(execution_tier);
r.builder().AddMemoryElems<int32_t>(kWasmPageSize / sizeof(int32_t));
r.builder().SetMemoryShared();
r.Build({WASM_LOAD_MEM(MachineType::Int64(), WASM_ZERO),
WASM_ATOMICS_BINOP(wasm_op, WASM_ZERO, WASM_I32V_1(20),
MachineRepresentation::kWord32),
WASM_DROP, kExprI64Eqz});
CHECK_EQ(1, r.Call());
}
WASM_EXEC_TEST(AtomicAddNoConsideredEffectful) {
RunNoEffectTest(execution_tier, kExprI32AtomicAdd);
}
WASM_EXEC_TEST(AtomicExchangeNoConsideredEffectful) {
RunNoEffectTest(execution_tier, kExprI32AtomicExchange);
}
WASM_EXEC_TEST(AtomicCompareExchangeNoConsideredEffectful) {
// Use {Load} instead of {ProtectedLoad}.
FLAG_SCOPE(wasm_enforce_bounds_checks);
WasmRunner<uint32_t> r(execution_tier);
r.builder().AddMemoryElems<int32_t>(kWasmPageSize / sizeof(int32_t));
r.builder().SetMemoryShared();
r.Build({WASM_LOAD_MEM(MachineType::Int32(), WASM_ZERO),
WASM_ATOMICS_TERNARY_OP(kExprI32AtomicCompareExchange, WASM_ZERO,
WASM_ZERO, WASM_I32V_1(30),
MachineRepresentation::kWord32),
WASM_DROP, kExprI32Eqz});
CHECK_EQ(1, r.Call());
}
WASM_EXEC_TEST(I32AtomicLoad_trap) {
WasmRunner<uint32_t> r(execution_tier);
r.builder().AddMemory(kWasmPageSize);
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_LOAD_OP(kExprI32AtomicLoad, WASM_I32V_3(kWasmPageSize),
MachineRepresentation::kWord32)});
CHECK_TRAP(r.Call());
}
WASM_EXEC_TEST(I64AtomicLoad_trap) {
WasmRunner<uint64_t> r(execution_tier);
r.builder().AddMemory(kWasmPageSize);
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_LOAD_OP(kExprI64AtomicLoad, WASM_I32V_3(kWasmPageSize),
MachineRepresentation::kWord64)});
CHECK_TRAP64(r.Call());
}
WASM_EXEC_TEST(I32AtomicStore_trap) {
WasmRunner<uint32_t> r(execution_tier);
r.builder().AddMemory(kWasmPageSize);
r.builder().SetMemoryShared();
r.Build(
{WASM_ATOMICS_STORE_OP(kExprI32AtomicStore, WASM_I32V_3(kWasmPageSize),
WASM_ZERO, MachineRepresentation::kWord32),
WASM_ZERO});
CHECK_TRAP(r.Call());
}
WASM_EXEC_TEST(I64AtomicStore_trap) {
WasmRunner<uint32_t> r(execution_tier);
r.builder().AddMemory(kWasmPageSize);
r.builder().SetMemoryShared();
r.Build(
{WASM_ATOMICS_STORE_OP(kExprI64AtomicStore, WASM_I32V_3(kWasmPageSize),
WASM_ZERO64, MachineRepresentation::kWord64),
WASM_ZERO});
CHECK_TRAP(r.Call());
}
WASM_EXEC_TEST(I32AtomicLoad_NotOptOut) {
WasmRunner<uint32_t> r(execution_tier);
r.builder().AddMemory(kWasmPageSize);
r.builder().SetMemoryShared();
r.Build({WASM_I32_AND(
WASM_ATOMICS_LOAD_OP(kExprI32AtomicLoad, WASM_I32V_3(kWasmPageSize),
MachineRepresentation::kWord32),
WASM_ZERO)});
CHECK_TRAP(r.Call());
}
void RunU32BinOp_OOB(TestExecutionTier execution_tier, WasmOpcode wasm_op) {
WasmRunner<uint32_t> r(execution_tier);
r.builder().AddMemory(kWasmPageSize);
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_BINOP(wasm_op, WASM_I32V_3(kWasmPageSize), WASM_ZERO,
MachineRepresentation::kWord32)});
CHECK_TRAP(r.Call());
}
#define TEST_OPERATION(Name) \
WASM_EXEC_TEST(OOB_I32Atomic##Name) { \
RunU32BinOp_OOB(execution_tier, kExprI32Atomic##Name); \
}
WASM_ATOMIC_OPERATION_LIST(TEST_OPERATION)
#undef TEST_OPERATION
void RunU64BinOp_OOB(TestExecutionTier execution_tier, WasmOpcode wasm_op) {
WasmRunner<uint64_t> r(execution_tier);
r.builder().AddMemory(kWasmPageSize);
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_BINOP(wasm_op, WASM_I32V_3(kWasmPageSize), WASM_ZERO64,
MachineRepresentation::kWord64)});
CHECK_TRAP64(r.Call());
}
#define TEST_OPERATION(Name) \
WASM_EXEC_TEST(OOB_I64Atomic##Name) { \
RunU64BinOp_OOB(execution_tier, kExprI64Atomic##Name); \
}
WASM_ATOMIC_OPERATION_LIST(TEST_OPERATION)
#undef TEST_OPERATION
WASM_EXEC_TEST(I32AtomicCompareExchange_trap) {
WasmRunner<uint32_t, uint32_t, uint32_t> r(execution_tier);
uint32_t* memory =
r.builder().AddMemoryElems<uint32_t>(kWasmPageSize / sizeof(uint32_t));
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_TERNARY_OP(
kExprI32AtomicCompareExchange, WASM_I32V_3(kWasmPageSize),
WASM_LOCAL_GET(0), WASM_LOCAL_GET(1), MachineRepresentation::kWord32)});
FOR_UINT32_INPUTS(i) {
uint32_t initial = i;
FOR_UINT32_INPUTS(j) {
r.builder().WriteMemory(&memory[0], initial);
CHECK_TRAP(r.Call(i, j));
}
}
}
WASM_EXEC_TEST(I64AtomicCompareExchange_trap) {
WasmRunner<uint64_t> r(execution_tier);
r.builder().AddMemory(kWasmPageSize);
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_TERNARY_OP(
kExprI64AtomicCompareExchange, WASM_I32V_3(kWasmPageSize), WASM_ZERO64,
WASM_ZERO64, MachineRepresentation::kWord64)});
CHECK_TRAP64(r.Call());
}
} // namespace test_run_wasm_atomics
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,792 @@
// Copyright 2018 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/cctest/wasm/wasm-atomics-utils.h"
#include "test/common/wasm/wasm-macro-gen.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace test_run_wasm_atomics_64 {
void RunU64BinOp(TestExecutionTier execution_tier, WasmOpcode wasm_op,
Uint64BinOp expected_op) {
WasmRunner<uint64_t, uint64_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_BINOP(wasm_op, WASM_I32V_1(0), WASM_LOCAL_GET(0),
MachineRepresentation::kWord64)});
FOR_UINT64_INPUTS(i) {
uint64_t initial = i;
FOR_UINT64_INPUTS(j) {
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call(j));
uint64_t expected = expected_op(i, j);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
}
#define TEST_OPERATION(Name) \
WASM_EXEC_TEST(I64Atomic##Name) { \
RunU64BinOp(execution_tier, kExprI64Atomic##Name, Name); \
}
WASM_ATOMIC_OPERATION_LIST(TEST_OPERATION)
#undef TEST_OPERATION
void RunU32BinOp(TestExecutionTier execution_tier, WasmOpcode wasm_op,
Uint32BinOp expected_op) {
WasmRunner<uint64_t, uint64_t> r(execution_tier);
uint32_t* memory =
r.builder().AddMemoryElems<uint32_t>(kWasmPageSize / sizeof(uint32_t));
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_BINOP(wasm_op, WASM_I32V_1(0), WASM_LOCAL_GET(0),
MachineRepresentation::kWord32)});
FOR_UINT32_INPUTS(i) {
uint32_t initial = i;
FOR_UINT32_INPUTS(j) {
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call(j));
uint32_t expected = expected_op(i, j);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
}
#define TEST_OPERATION(Name) \
WASM_EXEC_TEST(I64Atomic##Name##32U) { \
RunU32BinOp(execution_tier, kExprI64Atomic##Name##32U, Name); \
}
WASM_ATOMIC_OPERATION_LIST(TEST_OPERATION)
#undef TEST_OPERATION
void RunU16BinOp(TestExecutionTier tier, WasmOpcode wasm_op,
Uint16BinOp expected_op) {
WasmRunner<uint64_t, uint64_t> r(tier);
uint16_t* memory =
r.builder().AddMemoryElems<uint16_t>(kWasmPageSize / sizeof(uint16_t));
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_BINOP(wasm_op, WASM_I32V_1(0), WASM_LOCAL_GET(0),
MachineRepresentation::kWord16)});
FOR_UINT16_INPUTS(i) {
uint16_t initial = i;
FOR_UINT16_INPUTS(j) {
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call(j));
uint16_t expected = expected_op(i, j);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
}
#define TEST_OPERATION(Name) \
WASM_EXEC_TEST(I64Atomic##Name##16U) { \
RunU16BinOp(execution_tier, kExprI64Atomic##Name##16U, Name); \
}
WASM_ATOMIC_OPERATION_LIST(TEST_OPERATION)
#undef TEST_OPERATION
void RunU8BinOp(TestExecutionTier execution_tier, WasmOpcode wasm_op,
Uint8BinOp expected_op) {
WasmRunner<uint64_t, uint64_t> r(execution_tier);
uint8_t* memory = r.builder().AddMemoryElems<uint8_t>(kWasmPageSize);
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_BINOP(wasm_op, WASM_I32V_1(0), WASM_LOCAL_GET(0),
MachineRepresentation::kWord8)});
FOR_UINT8_INPUTS(i) {
uint8_t initial = i;
FOR_UINT8_INPUTS(j) {
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call(j));
uint8_t expected = expected_op(i, j);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
}
#define TEST_OPERATION(Name) \
WASM_EXEC_TEST(I64Atomic##Name##8U) { \
RunU8BinOp(execution_tier, kExprI64Atomic##Name##8U, Name); \
}
WASM_ATOMIC_OPERATION_LIST(TEST_OPERATION)
#undef TEST_OPERATION
WASM_EXEC_TEST(I64AtomicCompareExchange) {
WasmRunner<uint64_t, uint64_t, uint64_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_TERNARY_OP(
kExprI64AtomicCompareExchange, WASM_I32V_1(0), WASM_LOCAL_GET(0),
WASM_LOCAL_GET(1), MachineRepresentation::kWord64)});
FOR_UINT64_INPUTS(i) {
uint64_t initial = i;
FOR_UINT64_INPUTS(j) {
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call(i, j));
uint64_t expected = CompareExchange(initial, i, j);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
}
WASM_EXEC_TEST(I64AtomicCompareExchange32U) {
WasmRunner<uint64_t, uint64_t, uint64_t> r(execution_tier);
uint32_t* memory =
r.builder().AddMemoryElems<uint32_t>(kWasmPageSize / sizeof(uint32_t));
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_TERNARY_OP(
kExprI64AtomicCompareExchange32U, WASM_I32V_1(0), WASM_LOCAL_GET(0),
WASM_LOCAL_GET(1), MachineRepresentation::kWord32)});
FOR_UINT32_INPUTS(i) {
uint32_t initial = i;
FOR_UINT32_INPUTS(j) {
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call(i, j));
uint32_t expected = CompareExchange(initial, i, j);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
}
WASM_EXEC_TEST(I64AtomicCompareExchange16U) {
WasmRunner<uint64_t, uint64_t, uint64_t> r(execution_tier);
uint16_t* memory =
r.builder().AddMemoryElems<uint16_t>(kWasmPageSize / sizeof(uint16_t));
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_TERNARY_OP(
kExprI64AtomicCompareExchange16U, WASM_I32V_1(0), WASM_LOCAL_GET(0),
WASM_LOCAL_GET(1), MachineRepresentation::kWord16)});
FOR_UINT16_INPUTS(i) {
uint16_t initial = i;
FOR_UINT16_INPUTS(j) {
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call(i, j));
uint16_t expected = CompareExchange(initial, i, j);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
}
WASM_EXEC_TEST(I32AtomicCompareExchange8U) {
WasmRunner<uint64_t, uint64_t, uint64_t> r(execution_tier);
uint8_t* memory = r.builder().AddMemoryElems<uint8_t>(kWasmPageSize);
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_TERNARY_OP(
kExprI64AtomicCompareExchange8U, WASM_I32V_1(0), WASM_LOCAL_GET(0),
WASM_LOCAL_GET(1), MachineRepresentation::kWord8)});
FOR_UINT8_INPUTS(i) {
uint8_t initial = i;
FOR_UINT8_INPUTS(j) {
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call(i, j));
uint8_t expected = CompareExchange(initial, i, j);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
}
WASM_EXEC_TEST(I64AtomicLoad) {
WasmRunner<uint64_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_LOAD_OP(kExprI64AtomicLoad, WASM_ZERO,
MachineRepresentation::kWord64)});
FOR_UINT64_INPUTS(i) {
uint64_t expected = i;
r.builder().WriteMemory(&memory[0], expected);
CHECK_EQ(expected, r.Call());
}
}
WASM_EXEC_TEST(I64AtomicLoad32U) {
WasmRunner<uint64_t> r(execution_tier);
uint32_t* memory =
r.builder().AddMemoryElems<uint32_t>(kWasmPageSize / sizeof(uint32_t));
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_LOAD_OP(kExprI64AtomicLoad32U, WASM_ZERO,
MachineRepresentation::kWord32)});
FOR_UINT32_INPUTS(i) {
uint32_t expected = i;
r.builder().WriteMemory(&memory[0], expected);
CHECK_EQ(expected, r.Call());
}
}
WASM_EXEC_TEST(I64AtomicLoad16U) {
WasmRunner<uint64_t> r(execution_tier);
uint16_t* memory =
r.builder().AddMemoryElems<uint16_t>(kWasmPageSize / sizeof(uint16_t));
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_LOAD_OP(kExprI64AtomicLoad16U, WASM_ZERO,
MachineRepresentation::kWord16)});
FOR_UINT16_INPUTS(i) {
uint16_t expected = i;
r.builder().WriteMemory(&memory[0], expected);
CHECK_EQ(expected, r.Call());
}
}
WASM_EXEC_TEST(I64AtomicLoad8U) {
WasmRunner<uint64_t> r(execution_tier);
uint8_t* memory = r.builder().AddMemoryElems<uint8_t>(kWasmPageSize);
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_LOAD_OP(kExprI64AtomicLoad8U, WASM_ZERO,
MachineRepresentation::kWord8)});
FOR_UINT8_INPUTS(i) {
uint8_t expected = i;
r.builder().WriteMemory(&memory[0], expected);
CHECK_EQ(expected, r.Call());
}
}
WASM_EXEC_TEST(I64AtomicStoreLoad) {
WasmRunner<uint64_t, uint64_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
r.builder().SetMemoryShared();
r.Build(
{WASM_ATOMICS_STORE_OP(kExprI64AtomicStore, WASM_ZERO, WASM_LOCAL_GET(0),
MachineRepresentation::kWord64),
WASM_ATOMICS_LOAD_OP(kExprI64AtomicLoad, WASM_ZERO,
MachineRepresentation::kWord64)});
FOR_UINT64_INPUTS(i) {
uint64_t expected = i;
CHECK_EQ(expected, r.Call(i));
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
WASM_EXEC_TEST(I64AtomicStoreLoad32U) {
WasmRunner<uint64_t, uint64_t> r(execution_tier);
uint32_t* memory =
r.builder().AddMemoryElems<uint32_t>(kWasmPageSize / sizeof(uint32_t));
r.builder().SetMemoryShared();
r.Build(
{WASM_ATOMICS_STORE_OP(kExprI64AtomicStore32U, WASM_ZERO,
WASM_LOCAL_GET(0), MachineRepresentation::kWord32),
WASM_ATOMICS_LOAD_OP(kExprI64AtomicLoad32U, WASM_ZERO,
MachineRepresentation::kWord32)});
FOR_UINT32_INPUTS(i) {
uint32_t expected = i;
CHECK_EQ(expected, r.Call(i));
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
WASM_EXEC_TEST(I64AtomicStoreLoad16U) {
WasmRunner<uint64_t, uint64_t> r(execution_tier);
uint16_t* memory =
r.builder().AddMemoryElems<uint16_t>(kWasmPageSize / sizeof(uint16_t));
r.builder().SetMemoryShared();
r.Build(
{WASM_ATOMICS_STORE_OP(kExprI64AtomicStore16U, WASM_ZERO,
WASM_LOCAL_GET(0), MachineRepresentation::kWord16),
WASM_ATOMICS_LOAD_OP(kExprI64AtomicLoad16U, WASM_ZERO,
MachineRepresentation::kWord16)});
FOR_UINT16_INPUTS(i) {
uint16_t expected = i;
CHECK_EQ(expected, r.Call(i));
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
}
WASM_EXEC_TEST(I64AtomicStoreLoad8U) {
WasmRunner<uint64_t, uint64_t> r(execution_tier);
uint8_t* memory = r.builder().AddMemoryElems<uint8_t>(kWasmPageSize);
r.builder().SetMemoryShared();
r.Build(
{WASM_ATOMICS_STORE_OP(kExprI64AtomicStore8U, WASM_ZERO,
WASM_LOCAL_GET(0), MachineRepresentation::kWord8),
WASM_ATOMICS_LOAD_OP(kExprI64AtomicLoad8U, WASM_ZERO,
MachineRepresentation::kWord8)});
FOR_UINT8_INPUTS(i) {
uint8_t expected = i;
CHECK_EQ(expected, r.Call(i));
CHECK_EQ(i, r.builder().ReadMemory(&memory[0]));
}
}
// Drop tests verify atomic operations are run correctly when the
// entire 64-bit output is optimized out
void RunDropTest(TestExecutionTier execution_tier, WasmOpcode wasm_op,
Uint64BinOp op) {
WasmRunner<uint64_t, uint64_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_BINOP(wasm_op, WASM_I32V_1(0), WASM_LOCAL_GET(0),
MachineRepresentation::kWord64),
WASM_DROP, WASM_LOCAL_GET(0)});
uint64_t initial = 0x1111222233334444, local = 0x1111111111111111;
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(local, r.Call(local));
uint64_t expected = op(initial, local);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
#define TEST_OPERATION(Name) \
WASM_EXEC_TEST(I64Atomic##Name##Drop) { \
RunDropTest(execution_tier, kExprI64Atomic##Name, Name); \
}
WASM_ATOMIC_OPERATION_LIST(TEST_OPERATION)
#undef TEST_OPERATION
WASM_EXEC_TEST(I64AtomicSub16UDrop) {
WasmRunner<uint64_t, uint64_t> r(execution_tier);
uint16_t* memory =
r.builder().AddMemoryElems<uint16_t>(kWasmPageSize / sizeof(uint16_t));
r.builder().SetMemoryShared();
r.Build(
{WASM_ATOMICS_BINOP(kExprI64AtomicSub16U, WASM_I32V_1(0),
WASM_LOCAL_GET(0), MachineRepresentation::kWord16),
WASM_DROP, WASM_LOCAL_GET(0)});
uint16_t initial = 0x7, local = 0xffe0;
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(local, r.Call(local));
uint16_t expected = Sub(initial, local);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
WASM_EXEC_TEST(I64AtomicCompareExchangeDrop) {
WasmRunner<uint64_t, uint64_t, uint64_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_TERNARY_OP(
kExprI64AtomicCompareExchange, WASM_I32V_1(0), WASM_LOCAL_GET(0),
WASM_LOCAL_GET(1), MachineRepresentation::kWord64),
WASM_DROP, WASM_LOCAL_GET(1)});
uint64_t initial = 0x1111222233334444, local = 0x1111111111111111;
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(local, r.Call(initial, local));
uint64_t expected = CompareExchange(initial, initial, local);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
WASM_EXEC_TEST(I64AtomicStoreLoadDrop) {
WasmRunner<uint64_t, uint64_t, uint64_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
r.builder().SetMemoryShared();
r.Build(
{WASM_ATOMICS_STORE_OP(kExprI64AtomicStore, WASM_ZERO, WASM_LOCAL_GET(0),
MachineRepresentation::kWord64),
WASM_ATOMICS_LOAD_OP(kExprI64AtomicLoad, WASM_ZERO,
MachineRepresentation::kWord64),
WASM_DROP, WASM_LOCAL_GET(1)});
uint64_t store_value = 0x1111111111111111, expected = 0xC0DE;
CHECK_EQ(expected, r.Call(store_value, expected));
CHECK_EQ(store_value, r.builder().ReadMemory(&memory[0]));
}
WASM_EXEC_TEST(I64AtomicAddConvertDrop) {
WasmRunner<uint64_t, uint64_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
r.builder().SetMemoryShared();
r.Build(
{WASM_ATOMICS_BINOP(kExprI64AtomicAdd, WASM_I32V_1(0), WASM_LOCAL_GET(0),
MachineRepresentation::kWord64),
kExprI32ConvertI64, WASM_DROP, WASM_LOCAL_GET(0)});
uint64_t initial = 0x1111222233334444, local = 0x1111111111111111;
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(local, r.Call(local));
uint64_t expected = Add(initial, local);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
WASM_EXEC_TEST(I64AtomicLoadConvertDrop) {
WasmRunner<uint32_t, uint64_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
r.builder().SetMemoryShared();
r.Build({WASM_I32_CONVERT_I64(WASM_ATOMICS_LOAD_OP(
kExprI64AtomicLoad, WASM_ZERO, MachineRepresentation::kWord64))});
uint64_t initial = 0x1111222233334444;
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(static_cast<uint32_t>(initial), r.Call(initial));
}
// Convert tests verify atomic operations are run correctly when the
// upper half of the 64-bit output is optimized out
void RunConvertTest(TestExecutionTier execution_tier, WasmOpcode wasm_op,
Uint64BinOp op) {
WasmRunner<uint32_t, uint64_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
r.builder().SetMemoryShared();
r.Build({WASM_I32_CONVERT_I64(WASM_ATOMICS_BINOP(
wasm_op, WASM_ZERO, WASM_LOCAL_GET(0), MachineRepresentation::kWord64))});
uint64_t initial = 0x1111222233334444, local = 0x1111111111111111;
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(static_cast<uint32_t>(initial), r.Call(local));
uint64_t expected = op(initial, local);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
#define TEST_OPERATION(Name) \
WASM_EXEC_TEST(I64AtomicConvert##Name) { \
RunConvertTest(execution_tier, kExprI64Atomic##Name, Name); \
}
WASM_ATOMIC_OPERATION_LIST(TEST_OPERATION)
#undef TEST_OPERATION
WASM_EXEC_TEST(I64AtomicConvertCompareExchange) {
WasmRunner<uint32_t, uint64_t, uint64_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
r.builder().SetMemoryShared();
r.Build({WASM_I32_CONVERT_I64(WASM_ATOMICS_TERNARY_OP(
kExprI64AtomicCompareExchange, WASM_I32V_1(0), WASM_LOCAL_GET(0),
WASM_LOCAL_GET(1), MachineRepresentation::kWord64))});
uint64_t initial = 0x1111222233334444, local = 0x1111111111111111;
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(static_cast<uint32_t>(initial), r.Call(initial, local));
uint64_t expected = CompareExchange(initial, initial, local);
CHECK_EQ(expected, r.builder().ReadMemory(&memory[0]));
}
// The WASM_I64_EQ operation is used here to test that the index node
// is lowered correctly.
void RunNonConstIndexTest(TestExecutionTier execution_tier, WasmOpcode wasm_op,
Uint64BinOp op, MachineRepresentation rep) {
WasmRunner<uint32_t, uint64_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
r.builder().SetMemoryShared();
r.Build({WASM_I32_CONVERT_I64(
WASM_ATOMICS_BINOP(wasm_op, WASM_I64_EQ(WASM_I64V(1), WASM_I64V(0)),
WASM_LOCAL_GET(0), rep))});
uint64_t initial = 0x1111222233334444, local = 0x5555666677778888;
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(static_cast<uint32_t>(initial), r.Call(local));
CHECK_EQ(static_cast<uint32_t>(op(initial, local)),
static_cast<uint32_t>(r.builder().ReadMemory(&memory[0])));
}
// Test a set of Narrow operations
#define TEST_OPERATION(Name) \
WASM_EXEC_TEST(I64AtomicConstIndex##Name##Narrow) { \
RunNonConstIndexTest(execution_tier, kExprI64Atomic##Name##32U, Name, \
MachineRepresentation::kWord32); \
}
WASM_ATOMIC_OPERATION_LIST(TEST_OPERATION)
#undef TEST_OPERATION
// Test a set of Regular operations
#define TEST_OPERATION(Name) \
WASM_EXEC_TEST(I64AtomicConstIndex##Name) { \
RunNonConstIndexTest(execution_tier, kExprI64Atomic##Name, Name, \
MachineRepresentation::kWord64); \
}
WASM_ATOMIC_OPERATION_LIST(TEST_OPERATION)
#undef TEST_OPERATION
WASM_EXEC_TEST(I64AtomicNonConstIndexCompareExchangeNarrow) {
WasmRunner<uint32_t, uint64_t, uint64_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
r.builder().SetMemoryShared();
r.Build({WASM_I32_CONVERT_I64(WASM_ATOMICS_TERNARY_OP(
kExprI64AtomicCompareExchange16U, WASM_I64_EQ(WASM_I64V(1), WASM_I64V(0)),
WASM_LOCAL_GET(0), WASM_LOCAL_GET(1), MachineRepresentation::kWord16))});
uint64_t initial = 0x4444333322221111, local = 0x9999888877776666;
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(static_cast<uint16_t>(initial), r.Call(initial, local));
CHECK_EQ(static_cast<uint16_t>(CompareExchange(initial, initial, local)),
static_cast<uint16_t>(r.builder().ReadMemory(&memory[0])));
}
WASM_EXEC_TEST(I64AtomicNonConstIndexCompareExchange) {
WasmRunner<uint32_t, uint64_t, uint64_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
r.builder().SetMemoryShared();
r.Build({WASM_I32_CONVERT_I64(WASM_ATOMICS_TERNARY_OP(
kExprI64AtomicCompareExchange, WASM_I64_EQ(WASM_I64V(1), WASM_I64V(0)),
WASM_LOCAL_GET(0), WASM_LOCAL_GET(1), MachineRepresentation::kWord64))});
uint64_t initial = 4444333322221111, local = 0x9999888877776666;
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(static_cast<uint32_t>(initial), r.Call(initial, local));
CHECK_EQ(CompareExchange(initial, initial, local),
r.builder().ReadMemory(&memory[0]));
}
WASM_EXEC_TEST(I64AtomicNonConstIndexLoad8U) {
WasmRunner<uint32_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
r.builder().SetMemoryShared();
r.Build({WASM_I32_CONVERT_I64(WASM_ATOMICS_LOAD_OP(
kExprI64AtomicLoad8U, WASM_I64_EQ(WASM_I64V(1), WASM_I64V(0)),
MachineRepresentation::kWord8))});
uint64_t expected = 0xffffeeeeddddcccc;
r.builder().WriteMemory(&memory[0], expected);
CHECK_EQ(static_cast<uint8_t>(expected), r.Call());
}
WASM_EXEC_TEST(I64AtomicCompareExchangeFail) {
WasmRunner<uint64_t, uint64_t, uint64_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_TERNARY_OP(
kExprI64AtomicCompareExchange, WASM_I32V_1(0), WASM_LOCAL_GET(0),
WASM_LOCAL_GET(1), MachineRepresentation::kWord64)});
uint64_t initial = 0x1111222233334444, local = 0x1111111111111111,
test = 0x2222222222222222;
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call(test, local));
// No memory change on failed compare exchange
CHECK_EQ(initial, r.builder().ReadMemory(&memory[0]));
}
WASM_EXEC_TEST(I64AtomicCompareExchange32UFail) {
WasmRunner<uint64_t, uint64_t, uint64_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
r.builder().SetMemoryShared();
r.Build({WASM_ATOMICS_TERNARY_OP(
kExprI64AtomicCompareExchange32U, WASM_I32V_1(0), WASM_LOCAL_GET(0),
WASM_LOCAL_GET(1), MachineRepresentation::kWord32)});
uint64_t initial = 0x1111222233334444, test = 0xffffffff, local = 0xeeeeeeee;
r.builder().WriteMemory(&memory[0], initial);
CHECK_EQ(static_cast<uint32_t>(initial), r.Call(test, local));
// No memory change on failed compare exchange
CHECK_EQ(initial, r.builder().ReadMemory(&memory[0]));
}
WASM_EXEC_TEST(AtomicStoreNoConsideredEffectful) {
// Use {Load} instead of {ProtectedLoad}.
FLAG_SCOPE(wasm_enforce_bounds_checks);
WasmRunner<uint32_t> r(execution_tier);
r.builder().AddMemoryElems<int64_t>(kWasmPageSize / sizeof(int64_t));
r.builder().SetMemoryShared();
r.Build({WASM_LOAD_MEM(MachineType::Int64(), WASM_ZERO),
WASM_ATOMICS_STORE_OP(kExprI64AtomicStore, WASM_ZERO, WASM_I64V(20),
MachineRepresentation::kWord64),
kExprI64Eqz});
CHECK_EQ(1, r.Call());
}
void RunNoEffectTest(TestExecutionTier execution_tier, WasmOpcode wasm_op) {
// Use {Load} instead of {ProtectedLoad}.
FLAG_SCOPE(wasm_enforce_bounds_checks);
WasmRunner<uint32_t> r(execution_tier);
r.builder().AddMemoryElems<int64_t>(kWasmPageSize / sizeof(int64_t));
r.builder().SetMemoryShared();
r.Build({WASM_LOAD_MEM(MachineType::Int64(), WASM_ZERO),
WASM_ATOMICS_BINOP(wasm_op, WASM_ZERO, WASM_I64V(20),
MachineRepresentation::kWord64),
WASM_DROP, kExprI64Eqz});
CHECK_EQ(1, r.Call());
}
WASM_EXEC_TEST(AtomicAddNoConsideredEffectful) {
RunNoEffectTest(execution_tier, kExprI64AtomicAdd);
}
WASM_EXEC_TEST(AtomicExchangeNoConsideredEffectful) {
RunNoEffectTest(execution_tier, kExprI64AtomicExchange);
}
WASM_EXEC_TEST(AtomicCompareExchangeNoConsideredEffectful) {
// Use {Load} instead of {ProtectedLoad}.
FLAG_SCOPE(wasm_enforce_bounds_checks);
WasmRunner<uint32_t> r(execution_tier);
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
r.builder().SetMemoryShared();
r.Build({WASM_LOAD_MEM(MachineType::Int64(), WASM_ZERO),
WASM_ATOMICS_TERNARY_OP(kExprI64AtomicCompareExchange, WASM_ZERO,
WASM_I64V(0), WASM_I64V(30),
MachineRepresentation::kWord64),
WASM_DROP, kExprI64Eqz});
CHECK_EQ(1, r.Call());
}
WASM_EXEC_TEST(I64AtomicLoadUseOnlyLowWord) {
WasmRunner<uint32_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
uint64_t initial = 0x1234567890abcdef;
r.builder().WriteMemory(&memory[1], initial);
r.builder().SetMemoryShared();
// Test that we can use just the low word of an I64AtomicLoad.
r.Build({WASM_I32_CONVERT_I64(WASM_ATOMICS_LOAD_OP(
kExprI64AtomicLoad, WASM_I32V(8), MachineRepresentation::kWord64))});
CHECK_EQ(0x90abcdef, r.Call());
}
WASM_EXEC_TEST(I64AtomicLoadUseOnlyHighWord) {
WasmRunner<uint32_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
uint64_t initial = 0x1234567890abcdef;
r.builder().WriteMemory(&memory[1], initial);
r.builder().SetMemoryShared();
// Test that we can use just the high word of an I64AtomicLoad.
r.Build({WASM_I32_CONVERT_I64(
WASM_I64_ROR(WASM_ATOMICS_LOAD_OP(kExprI64AtomicLoad, WASM_I32V(8),
MachineRepresentation::kWord64),
WASM_I64V(32)))});
CHECK_EQ(0x12345678, r.Call());
}
WASM_EXEC_TEST(I64AtomicAddUseOnlyLowWord) {
WasmRunner<uint32_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
uint64_t initial = 0x1234567890abcdef;
r.builder().WriteMemory(&memory[1], initial);
r.builder().SetMemoryShared();
// Test that we can use just the low word of an I64AtomicLoad.
r.Build({WASM_I32_CONVERT_I64(
WASM_ATOMICS_BINOP(kExprI64AtomicAdd, WASM_I32V(8), WASM_I64V(1),
MachineRepresentation::kWord64))});
CHECK_EQ(0x90abcdef, r.Call());
}
WASM_EXEC_TEST(I64AtomicAddUseOnlyHighWord) {
WasmRunner<uint32_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
uint64_t initial = 0x1234567890abcdef;
r.builder().WriteMemory(&memory[1], initial);
r.builder().SetMemoryShared();
// Test that we can use just the high word of an I64AtomicLoad.
r.Build({WASM_I32_CONVERT_I64(WASM_I64_ROR(
WASM_ATOMICS_BINOP(kExprI64AtomicAdd, WASM_I32V(8), WASM_I64V(1),
MachineRepresentation::kWord64),
WASM_I64V(32)))});
CHECK_EQ(0x12345678, r.Call());
}
WASM_EXEC_TEST(I64AtomicCompareExchangeUseOnlyLowWord) {
WasmRunner<uint32_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
uint64_t initial = 0x1234567890abcdef;
r.builder().WriteMemory(&memory[1], initial);
r.builder().SetMemoryShared();
// Test that we can use just the low word of an I64AtomicLoad.
r.Build({WASM_I32_CONVERT_I64(WASM_ATOMICS_TERNARY_OP(
kExprI64AtomicCompareExchange, WASM_I32V(8), WASM_I64V(1),
WASM_I64V(memory[1]), MachineRepresentation::kWord64))});
CHECK_EQ(0x90abcdef, r.Call());
}
WASM_EXEC_TEST(I64AtomicCompareExchangeUseOnlyHighWord) {
WasmRunner<uint32_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
uint64_t initial = 0x1234567890abcdef;
r.builder().WriteMemory(&memory[1], initial);
r.builder().SetMemoryShared();
// Test that we can use just the high word of an I64AtomicLoad.
r.Build({WASM_I32_CONVERT_I64(WASM_I64_ROR(
WASM_ATOMICS_TERNARY_OP(kExprI64AtomicCompareExchange, WASM_I32V(8),
WASM_I64V(1), WASM_I64V(memory[1]),
MachineRepresentation::kWord64),
WASM_I64V(32)))});
CHECK_EQ(0x12345678, r.Call());
}
WASM_EXEC_TEST(I64AtomicExchangeUseOnlyLowWord) {
WasmRunner<uint32_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
uint64_t initial = 0x1234567890abcdef;
r.builder().WriteMemory(&memory[1], initial);
r.builder().SetMemoryShared();
// Test that we can use just the low word of an I64AtomicLoad.
r.Build({WASM_I32_CONVERT_I64(
WASM_ATOMICS_BINOP(kExprI64AtomicExchange, WASM_I32V(8), WASM_I64V(1),
MachineRepresentation::kWord64))});
CHECK_EQ(0x90abcdef, r.Call());
}
WASM_EXEC_TEST(I64AtomicExchangeUseOnlyHighWord) {
WasmRunner<uint32_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
uint64_t initial = 0x1234567890abcdef;
r.builder().WriteMemory(&memory[1], initial);
r.builder().SetMemoryShared();
// Test that we can use just the high word of an I64AtomicLoad.
r.Build({WASM_I32_CONVERT_I64(WASM_I64_ROR(
WASM_ATOMICS_BINOP(kExprI64AtomicExchange, WASM_I32V(8), WASM_I64V(1),
MachineRepresentation::kWord64),
WASM_I64V(32)))});
CHECK_EQ(0x12345678, r.Call());
}
WASM_EXEC_TEST(I64AtomicCompareExchange32UZeroExtended) {
WasmRunner<uint32_t> r(execution_tier);
uint64_t* memory =
r.builder().AddMemoryElems<uint64_t>(kWasmPageSize / sizeof(uint64_t));
memory[1] = 0;
r.builder().SetMemoryShared();
// Test that the high word of the expected value is cleared in the return
// value.
r.Build({WASM_I64_EQZ(
WASM_ATOMICS_TERNARY_OP(kExprI64AtomicCompareExchange32U, WASM_I32V(8),
WASM_I64V(0x1234567800000000), WASM_I64V(0),
MachineRepresentation::kWord32))});
CHECK_EQ(1, r.Call());
}
} // namespace test_run_wasm_atomics_64
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,661 @@
// 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 "src/wasm/wasm-module-builder.h"
#include "test/cctest/cctest.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace test_run_wasm_bulk_memory {
namespace {
void CheckMemoryEquals(TestingModuleBuilder* builder, size_t index,
const std::vector<uint8_t>& expected) {
const uint8_t* mem_start = builder->raw_mem_start<uint8_t>();
const uint8_t* mem_end = builder->raw_mem_end<uint8_t>();
size_t mem_size = mem_end - mem_start;
CHECK_LE(index, mem_size);
CHECK_LE(index + expected.size(), mem_size);
for (size_t i = 0; i < expected.size(); ++i) {
CHECK_EQ(expected[i], mem_start[index + i]);
}
}
void CheckMemoryEqualsZero(TestingModuleBuilder* builder, size_t index,
size_t length) {
const uint8_t* mem_start = builder->raw_mem_start<uint8_t>();
const uint8_t* mem_end = builder->raw_mem_end<uint8_t>();
size_t mem_size = mem_end - mem_start;
CHECK_LE(index, mem_size);
CHECK_LE(index + length, mem_size);
for (size_t i = 0; i < length; ++i) {
CHECK_EQ(0, mem_start[index + i]);
}
}
void CheckMemoryEqualsFollowedByZeroes(TestingModuleBuilder* builder,
const std::vector<uint8_t>& expected) {
CheckMemoryEquals(builder, 0, expected);
CheckMemoryEqualsZero(builder, expected.size(),
builder->mem_size() - expected.size());
}
} // namespace
WASM_EXEC_TEST(MemoryInit) {
WasmRunner<uint32_t, uint32_t, uint32_t, uint32_t> r(execution_tier);
r.builder().AddMemory(kWasmPageSize);
const uint8_t data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
r.builder().AddPassiveDataSegment(base::ArrayVector(data));
r.Build({WASM_MEMORY_INIT(0, WASM_LOCAL_GET(0), WASM_LOCAL_GET(1),
WASM_LOCAL_GET(2)),
kExprI32Const, 0});
// All zeroes.
CheckMemoryEqualsZero(&r.builder(), 0, kWasmPageSize);
// Copy all bytes from data segment 0, to memory at [10, 20).
CHECK_EQ(0, r.Call(10, 0, 10));
CheckMemoryEqualsFollowedByZeroes(
&r.builder(),
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
// Copy bytes in range [5, 10) from data segment 0, to memory at [0, 5).
CHECK_EQ(0, r.Call(0, 5, 5));
CheckMemoryEqualsFollowedByZeroes(
&r.builder(),
{5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
// Copy 0 bytes does nothing.
CHECK_EQ(0, r.Call(10, 1, 0));
CheckMemoryEqualsFollowedByZeroes(
&r.builder(),
{5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
// Copy 0 at end of memory region or data segment is OK.
CHECK_EQ(0, r.Call(kWasmPageSize, 0, 0));
CHECK_EQ(0, r.Call(0, sizeof(data), 0));
}
WASM_EXEC_TEST(MemoryInitOutOfBoundsData) {
WasmRunner<uint32_t, uint32_t, uint32_t, uint32_t> r(execution_tier);
r.builder().AddMemory(kWasmPageSize);
const uint8_t data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
r.builder().AddPassiveDataSegment(base::ArrayVector(data));
r.Build({WASM_MEMORY_INIT(0, WASM_LOCAL_GET(0), WASM_LOCAL_GET(1),
WASM_LOCAL_GET(2)),
kExprI32Const, 0});
const uint32_t last_5_bytes = kWasmPageSize - 5;
// Failing memory.init should not have any effect.
CHECK_EQ(0xDEADBEEF, r.Call(kWasmPageSize - 5, 0, 6));
CheckMemoryEquals(&r.builder(), last_5_bytes, {0, 0, 0, 0, 0});
r.builder().BlankMemory();
CHECK_EQ(0xDEADBEEF, r.Call(0, 5, 6));
CheckMemoryEquals(&r.builder(), last_5_bytes, {0, 0, 0, 0, 0});
}
WASM_EXEC_TEST(MemoryInitOutOfBounds) {
WasmRunner<uint32_t, uint32_t, uint32_t, uint32_t> r(execution_tier);
r.builder().AddMemory(kWasmPageSize);
const uint8_t data[kWasmPageSize] = {};
r.builder().AddPassiveDataSegment(base::ArrayVector(data));
r.Build({WASM_MEMORY_INIT(0, WASM_LOCAL_GET(0), WASM_LOCAL_GET(1),
WASM_LOCAL_GET(2)),
kExprI32Const, 0});
// OK, copy the full data segment to memory.
r.Call(0, 0, kWasmPageSize);
// Source range must not be out of bounds.
CHECK_EQ(0xDEADBEEF, r.Call(0, 1, kWasmPageSize));
CHECK_EQ(0xDEADBEEF, r.Call(0, 1000, kWasmPageSize));
CHECK_EQ(0xDEADBEEF, r.Call(0, kWasmPageSize, 1));
// Destination range must not be out of bounds.
CHECK_EQ(0xDEADBEEF, r.Call(1, 0, kWasmPageSize));
CHECK_EQ(0xDEADBEEF, r.Call(1000, 0, kWasmPageSize));
CHECK_EQ(0xDEADBEEF, r.Call(kWasmPageSize, 0, 1));
// Copy 0 out-of-bounds fails if target is invalid.
CHECK_EQ(0xDEADBEEF, r.Call(kWasmPageSize + 1, 0, 0));
CHECK_EQ(0xDEADBEEF, r.Call(0, kWasmPageSize + 1, 0));
// Make sure bounds aren't checked with 32-bit wrapping.
CHECK_EQ(0xDEADBEEF, r.Call(1, 1, 0xFFFFFFFF));
}
WASM_EXEC_TEST(MemoryCopy) {
WasmRunner<uint32_t, uint32_t, uint32_t, uint32_t> r(execution_tier);
uint8_t* mem = r.builder().AddMemory(kWasmPageSize);
r.Build({WASM_MEMORY0_COPY(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1),
WASM_LOCAL_GET(2)),
kExprI32Const, 0});
const uint8_t initial[] = {0, 11, 22, 33, 44, 55, 66, 77};
memcpy(mem, initial, sizeof(initial));
// Copy from [1, 8] to [10, 16].
CHECK_EQ(0, r.Call(10, 1, 8));
CheckMemoryEqualsFollowedByZeroes(
&r.builder(),
{0, 11, 22, 33, 44, 55, 66, 77, 0, 0, 11, 22, 33, 44, 55, 66, 77});
// Copy 0 bytes does nothing.
CHECK_EQ(0, r.Call(10, 2, 0));
CheckMemoryEqualsFollowedByZeroes(
&r.builder(),
{0, 11, 22, 33, 44, 55, 66, 77, 0, 0, 11, 22, 33, 44, 55, 66, 77});
// Copy 0 at end of memory region is OK.
CHECK_EQ(0, r.Call(kWasmPageSize, 0, 0));
CHECK_EQ(0, r.Call(0, kWasmPageSize, 0));
}
WASM_EXEC_TEST(MemoryCopyOverlapping) {
WasmRunner<uint32_t, uint32_t, uint32_t, uint32_t> r(execution_tier);
uint8_t* mem = r.builder().AddMemory(kWasmPageSize);
r.Build({WASM_MEMORY0_COPY(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1),
WASM_LOCAL_GET(2)),
kExprI32Const, 0});
const uint8_t initial[] = {10, 20, 30};
memcpy(mem, initial, sizeof(initial));
// Copy from [0, 3] -> [2, 5]. The copy must not overwrite 30 before copying
// it (i.e. cannot copy forward in this case).
CHECK_EQ(0, r.Call(2, 0, 3));
CheckMemoryEqualsFollowedByZeroes(&r.builder(), {10, 20, 10, 20, 30});
// Copy from [2, 5] -> [0, 3]. The copy must not write the first 10 (i.e.
// cannot copy backward in this case).
CHECK_EQ(0, r.Call(0, 2, 3));
CheckMemoryEqualsFollowedByZeroes(&r.builder(), {10, 20, 30, 20, 30});
}
WASM_EXEC_TEST(MemoryCopyOutOfBoundsData) {
WasmRunner<uint32_t, uint32_t, uint32_t, uint32_t> r(execution_tier);
uint8_t* mem = r.builder().AddMemory(kWasmPageSize);
r.Build({WASM_MEMORY0_COPY(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1),
WASM_LOCAL_GET(2)),
kExprI32Const, 0});
const uint8_t data[] = {11, 22, 33, 44, 55, 66, 77, 88};
memcpy(mem, data, sizeof(data));
const uint32_t last_5_bytes = kWasmPageSize - 5;
CheckMemoryEquals(&r.builder(), last_5_bytes, {0, 0, 0, 0, 0});
CHECK_EQ(0xDEADBEEF, r.Call(last_5_bytes, 0, 6));
CheckMemoryEquals(&r.builder(), last_5_bytes, {0, 0, 0, 0, 0});
r.builder().BlankMemory();
memcpy(mem + last_5_bytes, data, 5);
CHECK_EQ(0xDEADBEEF, r.Call(0, last_5_bytes, kWasmPageSize));
CheckMemoryEquals(&r.builder(), last_5_bytes, {11, 22, 33, 44, 55});
r.builder().BlankMemory();
memcpy(mem + last_5_bytes, data, 5);
CHECK_EQ(0xDEADBEEF, r.Call(last_5_bytes, 0, kWasmPageSize));
CheckMemoryEquals(&r.builder(), last_5_bytes, {11, 22, 33, 44, 55});
}
WASM_EXEC_TEST(MemoryCopyOutOfBounds) {
WasmRunner<uint32_t, uint32_t, uint32_t, uint32_t> r(execution_tier);
r.builder().AddMemory(kWasmPageSize);
r.Build({WASM_MEMORY0_COPY(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1),
WASM_LOCAL_GET(2)),
kExprI32Const, 0});
// Copy full range is OK.
CHECK_EQ(0, r.Call(0, 0, kWasmPageSize));
// Source range must not be out of bounds.
CHECK_EQ(0xDEADBEEF, r.Call(0, 1, kWasmPageSize));
CHECK_EQ(0xDEADBEEF, r.Call(0, 1000, kWasmPageSize));
CHECK_EQ(0xDEADBEEF, r.Call(0, kWasmPageSize, 1));
// Destination range must not be out of bounds.
CHECK_EQ(0xDEADBEEF, r.Call(1, 0, kWasmPageSize));
CHECK_EQ(0xDEADBEEF, r.Call(1000, 0, kWasmPageSize));
CHECK_EQ(0xDEADBEEF, r.Call(kWasmPageSize, 0, 1));
// Copy 0 out-of-bounds fails if target is invalid.
CHECK_EQ(0xDEADBEEF, r.Call(kWasmPageSize + 1, 0, 0));
CHECK_EQ(0xDEADBEEF, r.Call(0, kWasmPageSize + 1, 0));
// Make sure bounds aren't checked with 32-bit wrapping.
CHECK_EQ(0xDEADBEEF, r.Call(1, 1, 0xFFFFFFFF));
}
WASM_EXEC_TEST(MemoryFill) {
WasmRunner<uint32_t, uint32_t, uint32_t, uint32_t> r(execution_tier);
r.builder().AddMemory(kWasmPageSize);
r.Build({WASM_MEMORY_FILL(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1),
WASM_LOCAL_GET(2)),
kExprI32Const, 0});
CHECK_EQ(0, r.Call(1, 33, 5));
CheckMemoryEqualsFollowedByZeroes(&r.builder(), {0, 33, 33, 33, 33, 33});
CHECK_EQ(0, r.Call(4, 66, 4));
CheckMemoryEqualsFollowedByZeroes(&r.builder(),
{0, 33, 33, 33, 66, 66, 66, 66});
// Fill 0 bytes does nothing.
CHECK_EQ(0, r.Call(4, 66, 0));
CheckMemoryEqualsFollowedByZeroes(&r.builder(),
{0, 33, 33, 33, 66, 66, 66, 66});
// Fill 0 at end of memory region is OK.
CHECK_EQ(0, r.Call(kWasmPageSize, 66, 0));
}
WASM_EXEC_TEST(MemoryFillValueWrapsToByte) {
WasmRunner<uint32_t, uint32_t, uint32_t, uint32_t> r(execution_tier);
r.builder().AddMemory(kWasmPageSize);
r.Build({WASM_MEMORY_FILL(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1),
WASM_LOCAL_GET(2)),
kExprI32Const, 0});
CHECK_EQ(0, r.Call(0, 1000, 3));
const uint8_t expected = 1000 & 255;
CheckMemoryEqualsFollowedByZeroes(&r.builder(),
{expected, expected, expected});
}
WASM_EXEC_TEST(MemoryFillOutOfBoundsData) {
WasmRunner<uint32_t, uint32_t, uint32_t, uint32_t> r(execution_tier);
r.builder().AddMemory(kWasmPageSize);
r.Build({WASM_MEMORY_FILL(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1),
WASM_LOCAL_GET(2)),
kExprI32Const, 0});
const uint8_t v = 123;
CHECK_EQ(0xDEADBEEF, r.Call(kWasmPageSize - 5, v, 999));
CheckMemoryEquals(&r.builder(), kWasmPageSize - 6, {0, 0, 0, 0, 0, 0});
}
WASM_EXEC_TEST(MemoryFillOutOfBounds) {
WasmRunner<uint32_t, uint32_t, uint32_t, uint32_t> r(execution_tier);
r.builder().AddMemory(kWasmPageSize);
r.Build({WASM_MEMORY_FILL(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1),
WASM_LOCAL_GET(2)),
kExprI32Const, 0});
const uint8_t v = 123;
// Destination range must not be out of bounds.
CHECK_EQ(0xDEADBEEF, r.Call(1, v, kWasmPageSize));
CHECK_EQ(0xDEADBEEF, r.Call(1000, v, kWasmPageSize));
CHECK_EQ(0xDEADBEEF, r.Call(kWasmPageSize, v, 1));
// Fill 0 out-of-bounds still fails.
CHECK_EQ(0xDEADBEEF, r.Call(kWasmPageSize + 1, v, 0));
// Make sure bounds aren't checked with 32-bit wrapping.
CHECK_EQ(0xDEADBEEF, r.Call(1, v, 0xFFFFFFFF));
}
WASM_EXEC_TEST(DataDropTwice) {
WasmRunner<uint32_t> r(execution_tier);
r.builder().AddMemory(kWasmPageSize);
const uint8_t data[] = {0};
r.builder().AddPassiveDataSegment(base::ArrayVector(data));
r.Build({WASM_DATA_DROP(0), kExprI32Const, 0});
CHECK_EQ(0, r.Call());
CHECK_EQ(0, r.Call());
}
WASM_EXEC_TEST(DataDropThenMemoryInit) {
WasmRunner<uint32_t> r(execution_tier);
r.builder().AddMemory(kWasmPageSize);
const uint8_t data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
r.builder().AddPassiveDataSegment(base::ArrayVector(data));
r.Build({WASM_DATA_DROP(0),
WASM_MEMORY_INIT(0, WASM_I32V_1(0), WASM_I32V_1(1), WASM_I32V_1(2)),
kExprI32Const, 0});
CHECK_EQ(0xDEADBEEF, r.Call());
}
void TestTableCopyInbounds(TestExecutionTier execution_tier, int table_dst,
int table_src) {
WasmRunner<uint32_t, uint32_t, uint32_t, uint32_t> r(execution_tier);
const uint32_t kTableSize = 5;
// Add 10 function tables, even though we only test one table.
for (int i = 0; i < 10; ++i) {
r.builder().AddIndirectFunctionTable(nullptr, kTableSize);
}
r.Build({WASM_TABLE_COPY(table_dst, table_src, WASM_LOCAL_GET(0),
WASM_LOCAL_GET(1), WASM_LOCAL_GET(2)),
kExprI32Const, 0});
for (uint32_t i = 0; i <= kTableSize; ++i) {
r.CheckCallViaJS(0, 0, 0, i); // nop
r.CheckCallViaJS(0, 0, i, kTableSize - i);
r.CheckCallViaJS(0, i, 0, kTableSize - i);
}
}
WASM_COMPILED_EXEC_TEST(TableCopyInboundsFrom0To0) {
TestTableCopyInbounds(execution_tier, 0, 0);
}
WASM_COMPILED_EXEC_TEST(TableCopyInboundsFrom3To0) {
TestTableCopyInbounds(execution_tier, 3, 0);
}
WASM_COMPILED_EXEC_TEST(TableCopyInboundsFrom5To9) {
TestTableCopyInbounds(execution_tier, 5, 9);
}
WASM_COMPILED_EXEC_TEST(TableCopyInboundsFrom6To6) {
TestTableCopyInbounds(execution_tier, 6, 6);
}
namespace {
template <typename... Args>
void CheckTable(Isolate* isolate, DirectHandle<WasmTableObject> table,
Args... args) {
uint32_t args_length = static_cast<uint32_t>(sizeof...(args));
CHECK_EQ(table->current_length(), args_length);
DirectHandle<Object> handles[] = {args...};
for (uint32_t i = 0; i < args_length; ++i) {
CHECK(WasmTableObject::Get(isolate, table, i).is_identical_to(handles[i]));
}
}
template <typename WasmRunner, typename... Args>
void CheckTableCall(Isolate* isolate, DirectHandle<WasmTableObject> table,
WasmRunner* r, uint32_t function_index, Args... args) {
uint32_t args_length = static_cast<uint32_t>(sizeof...(args));
CHECK_EQ(table->current_length(), args_length);
double expected[] = {args...};
for (uint32_t i = 0; i < args_length; ++i) {
DirectHandle<Object> buffer[] = {isolate->factory()->NewNumber(i)};
r->CheckCallApplyViaJS(expected[i], function_index, base::VectorOf(buffer));
}
}
} // namespace
void TestTableCopyElems(TestExecutionTier execution_tier, int table_dst,
int table_src) {
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
TestSignatures sigs;
WasmRunner<uint32_t, uint32_t, uint32_t, uint32_t> r(execution_tier);
const uint32_t kTableSize = 5;
uint16_t function_indexes[kTableSize];
const ModuleTypeIndex sig_index = r.builder().AddSignature(sigs.i_v());
for (uint32_t i = 0; i < kTableSize; ++i) {
WasmFunctionCompiler& fn = r.NewFunction(sigs.i_v(), "f");
fn.Build({WASM_I32V_1(i)});
fn.SetSigIndex(sig_index);
function_indexes[i] = fn.function_index();
}
for (int i = 0; i < 10; ++i) {
r.builder().AddIndirectFunctionTable(function_indexes, kTableSize);
}
r.Build({WASM_TABLE_COPY(table_dst, table_src, WASM_LOCAL_GET(0),
WASM_LOCAL_GET(1), WASM_LOCAL_GET(2)),
kExprI32Const, 0});
r.builder().InitializeWrapperCache();
auto table =
handle(Cast<WasmTableObject>(
r.builder().trusted_instance_data()->tables()->get(table_dst)),
isolate);
r.CheckCallViaJS(0, 0, 0, kTableSize);
auto f0 = WasmTableObject::Get(isolate, table, 0);
auto f1 = WasmTableObject::Get(isolate, table, 1);
auto f2 = WasmTableObject::Get(isolate, table, 2);
auto f3 = WasmTableObject::Get(isolate, table, 3);
auto f4 = WasmTableObject::Get(isolate, table, 4);
if (table_dst == table_src) {
CheckTable(isolate, table, f0, f1, f2, f3, f4);
r.CheckCallViaJS(0, 0, 1, 1);
CheckTable(isolate, table, f1, f1, f2, f3, f4);
r.CheckCallViaJS(0, 0, 1, 2);
CheckTable(isolate, table, f1, f2, f2, f3, f4);
r.CheckCallViaJS(0, 3, 0, 2);
CheckTable(isolate, table, f1, f2, f2, f1, f2);
r.CheckCallViaJS(0, 1, 0, 2);
CheckTable(isolate, table, f1, f1, f2, f1, f2);
} else {
CheckTable(isolate, table, f0, f1, f2, f3, f4);
r.CheckCallViaJS(0, 0, 1, 1);
CheckTable(isolate, table, f1, f1, f2, f3, f4);
r.CheckCallViaJS(0, 0, 1, 2);
CheckTable(isolate, table, f1, f2, f2, f3, f4);
r.CheckCallViaJS(0, 3, 0, 2);
CheckTable(isolate, table, f1, f2, f2, f0, f1);
r.CheckCallViaJS(0, 1, 0, 2);
CheckTable(isolate, table, f1, f0, f1, f0, f1);
}
}
WASM_COMPILED_EXEC_TEST(TableCopyElemsFrom0To0) {
TestTableCopyElems(execution_tier, 0, 0);
}
WASM_COMPILED_EXEC_TEST(TableCopyElemsFrom3To0) {
TestTableCopyElems(execution_tier, 3, 0);
}
WASM_COMPILED_EXEC_TEST(TableCopyElemsFrom5To9) {
TestTableCopyElems(execution_tier, 5, 9);
}
WASM_COMPILED_EXEC_TEST(TableCopyElemsFrom6To6) {
TestTableCopyElems(execution_tier, 6, 6);
}
void TestTableCopyCalls(TestExecutionTier execution_tier, int table_dst,
int table_src) {
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
TestSignatures sigs;
WasmRunner<uint32_t, uint32_t, uint32_t, uint32_t> r(execution_tier);
const uint32_t kTableSize = 5;
uint16_t function_indexes[kTableSize];
const ModuleTypeIndex sig_index = r.builder().AddSignature(sigs.i_v());
for (uint32_t i = 0; i < kTableSize; ++i) {
WasmFunctionCompiler& fn = r.NewFunction(sigs.i_v(), "f");
fn.Build({WASM_I32V_1(i)});
fn.SetSigIndex(sig_index);
function_indexes[i] = fn.function_index();
}
for (int i = 0; i < 10; ++i) {
r.builder().AddIndirectFunctionTable(function_indexes, kTableSize);
}
WasmFunctionCompiler& call = r.NewFunction(sigs.i_i(), "call");
call.Build(
{WASM_CALL_INDIRECT_TABLE(table_dst, sig_index, WASM_LOCAL_GET(0))});
const uint32_t call_index = call.function_index();
r.Build({WASM_TABLE_COPY(table_dst, table_src, WASM_LOCAL_GET(0),
WASM_LOCAL_GET(1), WASM_LOCAL_GET(2)),
kExprI32Const, 0});
auto table =
handle(Cast<WasmTableObject>(
r.builder().trusted_instance_data()->tables()->get(table_dst)),
isolate);
if (table_dst == table_src) {
CheckTableCall(isolate, table, &r, call_index, 0.0, 1.0, 2.0, 3.0, 4.0);
r.CheckCallViaJS(0, 0, 1, 1);
CheckTableCall(isolate, table, &r, call_index, 1.0, 1.0, 2.0, 3.0, 4.0);
r.CheckCallViaJS(0, 0, 1, 2);
CheckTableCall(isolate, table, &r, call_index, 1.0, 2.0, 2.0, 3.0, 4.0);
r.CheckCallViaJS(0, 3, 0, 2);
CheckTableCall(isolate, table, &r, call_index, 1.0, 2.0, 2.0, 1.0, 2.0);
} else {
CheckTableCall(isolate, table, &r, call_index, 0.0, 1.0, 2.0, 3.0, 4.0);
r.CheckCallViaJS(0, 0, 1, 1);
CheckTableCall(isolate, table, &r, call_index, 1.0, 1.0, 2.0, 3.0, 4.0);
r.CheckCallViaJS(0, 0, 1, 2);
CheckTableCall(isolate, table, &r, call_index, 1.0, 2.0, 2.0, 3.0, 4.0);
r.CheckCallViaJS(0, 3, 0, 2);
CheckTableCall(isolate, table, &r, call_index, 1.0, 2.0, 2.0, 0.0, 1.0);
}
}
WASM_COMPILED_EXEC_TEST(TableCopyCallsTo0From0) {
TestTableCopyCalls(execution_tier, 0, 0);
}
WASM_COMPILED_EXEC_TEST(TableCopyCallsTo3From0) {
TestTableCopyCalls(execution_tier, 3, 0);
}
WASM_COMPILED_EXEC_TEST(TableCopyCallsTo5From9) {
TestTableCopyCalls(execution_tier, 5, 9);
}
WASM_COMPILED_EXEC_TEST(TableCopyCallsTo6From6) {
TestTableCopyCalls(execution_tier, 6, 6);
}
void TestTableCopyOobWrites(TestExecutionTier execution_tier, int table_dst,
int table_src) {
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
TestSignatures sigs;
WasmRunner<uint32_t, uint32_t, uint32_t, uint32_t> r(execution_tier);
const uint32_t kTableSize = 5;
uint16_t function_indexes[kTableSize];
const ModuleTypeIndex sig_index = r.builder().AddSignature(sigs.i_v());
for (uint32_t i = 0; i < kTableSize; ++i) {
WasmFunctionCompiler& fn = r.NewFunction(sigs.i_v(), "f");
fn.Build({WASM_I32V_1(i)});
fn.SetSigIndex(sig_index);
function_indexes[i] = fn.function_index();
}
for (int i = 0; i < 10; ++i) {
r.builder().AddIndirectFunctionTable(function_indexes, kTableSize);
}
r.Build({WASM_TABLE_COPY(table_dst, table_src, WASM_LOCAL_GET(0),
WASM_LOCAL_GET(1), WASM_LOCAL_GET(2)),
kExprI32Const, 0});
r.builder().InitializeWrapperCache();
auto table =
handle(Cast<WasmTableObject>(
r.builder().trusted_instance_data()->tables()->get(table_dst)),
isolate);
// Fill the dst table with values from the src table, to make checks easier.
r.CheckCallViaJS(0, 0, 0, kTableSize);
auto f0 = WasmTableObject::Get(isolate, table, 0);
auto f1 = WasmTableObject::Get(isolate, table, 1);
auto f2 = WasmTableObject::Get(isolate, table, 2);
auto f3 = WasmTableObject::Get(isolate, table, 3);
auto f4 = WasmTableObject::Get(isolate, table, 4);
CheckTable(isolate, table, f0, f1, f2, f3, f4);
// Failing table.copy should not have any effect.
r.CheckCallViaJS(0xDEADBEEF, 3, 0, 3);
CheckTable(isolate, table, f0, f1, f2, f3, f4);
r.CheckCallViaJS(0xDEADBEEF, 0, 4, 2);
CheckTable(isolate, table, f0, f1, f2, f3, f4);
r.CheckCallViaJS(0xDEADBEEF, 3, 0, 99);
CheckTable(isolate, table, f0, f1, f2, f3, f4);
r.CheckCallViaJS(0xDEADBEEF, 0, 1, 99);
CheckTable(isolate, table, f0, f1, f2, f3, f4);
}
WASM_COMPILED_EXEC_TEST(TableCopyOobWritesFrom0To0) {
TestTableCopyOobWrites(execution_tier, 0, 0);
}
WASM_COMPILED_EXEC_TEST(TableCopyOobWritesFrom3To0) {
TestTableCopyOobWrites(execution_tier, 3, 0);
}
WASM_COMPILED_EXEC_TEST(TableCopyOobWritesFrom5To9) {
TestTableCopyOobWrites(execution_tier, 5, 9);
}
WASM_COMPILED_EXEC_TEST(TableCopyOobWritesFrom6To6) {
TestTableCopyOobWrites(execution_tier, 6, 6);
}
void TestTableCopyOob1(TestExecutionTier execution_tier, int table_dst,
int table_src) {
WasmRunner<uint32_t, uint32_t, uint32_t, uint32_t> r(execution_tier);
const uint32_t kTableSize = 5;
for (int i = 0; i < 10; ++i) {
r.builder().AddIndirectFunctionTable(nullptr, kTableSize);
}
r.Build({WASM_TABLE_COPY(table_dst, table_src, WASM_LOCAL_GET(0),
WASM_LOCAL_GET(1), WASM_LOCAL_GET(2)),
kExprI32Const, 0});
r.CheckCallViaJS(0, 0, 0, 1); // nop
r.CheckCallViaJS(0, 0, 0, kTableSize); // nop
r.CheckCallViaJS(0xDEADBEEF, 0, 0, kTableSize + 1);
r.CheckCallViaJS(0xDEADBEEF, 1, 0, kTableSize);
r.CheckCallViaJS(0xDEADBEEF, 0, 1, kTableSize);
{
const uint32_t big = 1000000;
r.CheckCallViaJS(0xDEADBEEF, big, 0, 0);
r.CheckCallViaJS(0xDEADBEEF, 0, big, 0);
}
for (uint32_t big = 4294967295; big > 1000; big >>= 1) {
r.CheckCallViaJS(0xDEADBEEF, big, 0, 1);
r.CheckCallViaJS(0xDEADBEEF, 0, big, 1);
r.CheckCallViaJS(0xDEADBEEF, 0, 0, big);
}
for (uint32_t big = -1000; big != 0; big <<= 1) {
r.CheckCallViaJS(0xDEADBEEF, big, 0, 1);
r.CheckCallViaJS(0xDEADBEEF, 0, big, 1);
r.CheckCallViaJS(0xDEADBEEF, 0, 0, big);
}
}
WASM_COMPILED_EXEC_TEST(TableCopyOob1From0To0) {
TestTableCopyOob1(execution_tier, 0, 0);
}
WASM_COMPILED_EXEC_TEST(TableCopyOob1From3To0) {
TestTableCopyOob1(execution_tier, 3, 0);
}
WASM_COMPILED_EXEC_TEST(TableCopyOob1From5To9) {
TestTableCopyOob1(execution_tier, 5, 9);
}
WASM_COMPILED_EXEC_TEST(TableCopyOob1From6To6) {
TestTableCopyOob1(execution_tier, 6, 6);
}
} // namespace test_run_wasm_bulk_memory
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,520 @@
// 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 "include/v8-function.h"
#include "src/api/api-inl.h"
#include "test/cctest/wasm/wasm-atomics-utils.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
namespace v8::internal::wasm {
WASM_EXEC_TEST(TryCatchThrow) {
TestSignatures sigs;
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint8_t except = r.builder().AddException(sigs.v_v());
constexpr uint32_t kResult0 = 23;
constexpr uint32_t kResult1 = 42;
// Build the main test function.
r.Build({WASM_TRY_CATCH_T(
kWasmI32,
WASM_STMTS(WASM_I32V(kResult1),
WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)), WASM_THROW(except))),
WASM_STMTS(WASM_I32V(kResult0)), except)});
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJS(kResult0, 0);
r.CheckCallViaJS(kResult1, 1);
}
WASM_EXEC_TEST(TryCatchThrowWithValue) {
TestSignatures sigs;
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint8_t except = r.builder().AddException(sigs.v_i());
constexpr uint32_t kResult0 = 23;
constexpr uint32_t kResult1 = 42;
// Build the main test function.
r.Build({WASM_TRY_CATCH_T(
kWasmI32,
WASM_STMTS(WASM_I32V(kResult1),
WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)), WASM_I32V(kResult0),
WASM_THROW(except))),
WASM_STMTS(kExprNop), except)});
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJS(kResult0, 0);
r.CheckCallViaJS(kResult1, 1);
}
WASM_EXEC_TEST(TryMultiCatchThrow) {
TestSignatures sigs;
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint8_t except1 = r.builder().AddException(sigs.v_v());
uint8_t except2 = r.builder().AddException(sigs.v_v());
constexpr uint32_t kResult0 = 23;
constexpr uint32_t kResult1 = 42;
constexpr uint32_t kResult2 = 51;
// Build the main test function.
r.Build(
{kExprTry, static_cast<uint8_t>((kWasmI32).value_type_code()),
WASM_STMTS(WASM_I32V(kResult2),
WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)), WASM_THROW(except1)),
WASM_IF(WASM_I32_EQ(WASM_LOCAL_GET(0), WASM_I32V(1)),
WASM_THROW(except2))),
kExprCatch, except1, WASM_STMTS(WASM_I32V(kResult0)), kExprCatch,
except2, WASM_STMTS(WASM_I32V(kResult1)), kExprEnd});
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJS(kResult0, 0);
r.CheckCallViaJS(kResult1, 1);
r.CheckCallViaJS(kResult2, 2);
}
WASM_EXEC_TEST(TryCatchAllThrow) {
TestSignatures sigs;
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint8_t except = r.builder().AddException(sigs.v_v());
constexpr uint32_t kResult0 = 23;
constexpr uint32_t kResult1 = 42;
// Build the main test function.
r.Build(
{kExprTry, static_cast<uint8_t>((kWasmI32).value_type_code()),
WASM_STMTS(WASM_I32V(kResult1),
WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)), WASM_THROW(except))),
kExprCatchAll, WASM_I32V(kResult0), kExprEnd});
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJS(kResult0, 0);
r.CheckCallViaJS(kResult1, 1);
}
WASM_EXEC_TEST(TryCatchCatchAllThrow) {
TestSignatures sigs;
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint8_t except1 = r.builder().AddException(sigs.v_v());
uint8_t except2 = r.builder().AddException(sigs.v_v());
constexpr uint32_t kResult0 = 23;
constexpr uint32_t kResult1 = 42;
constexpr uint32_t kResult2 = 51;
// Build the main test function.
r.Build(
{kExprTry, static_cast<uint8_t>((kWasmI32).value_type_code()),
WASM_STMTS(WASM_I32V(kResult2),
WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)), WASM_THROW(except1)),
WASM_IF(WASM_I32_EQ(WASM_LOCAL_GET(0), WASM_I32V(1)),
WASM_THROW(except2))),
kExprCatch, except1, WASM_I32V(kResult0), kExprCatchAll,
WASM_I32V(kResult1), kExprEnd});
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJS(kResult0, 0);
r.CheckCallViaJS(kResult1, 1);
r.CheckCallViaJS(kResult2, 2);
}
WASM_EXEC_TEST(TryImplicitRethrow) {
TestSignatures sigs;
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint8_t except1 = r.builder().AddException(sigs.v_v());
uint8_t except2 = r.builder().AddException(sigs.v_v());
constexpr uint32_t kResult0 = 23;
constexpr uint32_t kResult1 = 42;
constexpr uint32_t kResult2 = 51;
// Build the main test function.
r.Build({WASM_TRY_CATCH_T(
kWasmI32,
WASM_TRY_CATCH_T(kWasmI32,
WASM_STMTS(WASM_I32V(kResult1),
WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)),
WASM_THROW(except2))),
WASM_STMTS(WASM_I32V(kResult2)), except1),
WASM_I32V(kResult0), except2)});
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJS(kResult0, 0);
r.CheckCallViaJS(kResult1, 1);
}
WASM_EXEC_TEST(TryDelegate) {
TestSignatures sigs;
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint8_t except = r.builder().AddException(sigs.v_v());
constexpr uint32_t kResult0 = 23;
constexpr uint32_t kResult1 = 42;
// Build the main test function.
r.Build({WASM_TRY_CATCH_T(
kWasmI32,
WASM_TRY_DELEGATE_T(kWasmI32,
WASM_STMTS(WASM_I32V(kResult1),
WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)),
WASM_THROW(except))),
0),
WASM_I32V(kResult0), except)});
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJS(kResult0, 0);
r.CheckCallViaJS(kResult1, 1);
}
WASM_EXEC_TEST(TestCatchlessTry) {
TestSignatures sigs;
WasmRunner<uint32_t> r(execution_tier);
uint8_t except = r.builder().AddException(sigs.v_i());
r.Build({WASM_TRY_CATCH_T(
kWasmI32,
WASM_TRY_T(kWasmI32, WASM_STMTS(WASM_I32V(0), WASM_THROW(except))),
WASM_NOP, except)});
r.CheckCallViaJS(0);
}
WASM_EXEC_TEST(TryCatchRethrow) {
TestSignatures sigs;
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint8_t except1 = r.builder().AddException(sigs.v_v());
uint8_t except2 = r.builder().AddException(sigs.v_v());
constexpr uint32_t kResult0 = 23;
constexpr uint32_t kResult1 = 42;
constexpr uint32_t kUnreachable = 51;
// Build the main test function.
r.Build({WASM_TRY_CATCH_CATCH_T(
kWasmI32,
WASM_TRY_CATCH_T(
kWasmI32, WASM_THROW(except2),
WASM_TRY_CATCH_T(
kWasmI32, WASM_THROW(except1),
WASM_STMTS(WASM_I32V(kUnreachable),
WASM_IF_ELSE(WASM_I32_EQZ(WASM_LOCAL_GET(0)),
WASM_RETHROW(1), WASM_RETHROW(2))),
except1),
except2),
except1, WASM_I32V(kResult0), except2, WASM_I32V(kResult1))});
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJS(kResult0, 0);
r.CheckCallViaJS(kResult1, 1);
}
WASM_EXEC_TEST(TryDelegateToCaller) {
TestSignatures sigs;
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint8_t except = r.builder().AddException(sigs.v_v());
constexpr uint32_t kResult0 = 23;
constexpr uint32_t kResult1 = 42;
// Build the main test function.
r.Build({WASM_TRY_CATCH_T(
kWasmI32,
WASM_TRY_DELEGATE_T(kWasmI32,
WASM_STMTS(WASM_I32V(kResult1),
WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)),
WASM_THROW(except))),
1),
WASM_I32V(kResult0), except)});
// Need to call through JS to allow for creation of stack traces.
constexpr int64_t trap = 0xDEADBEEF;
r.CheckCallViaJS(trap, 0);
r.CheckCallViaJS(kResult1, 1);
}
WASM_EXEC_TEST(TryCatchCallDirect) {
TestSignatures sigs;
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint8_t except = r.builder().AddException(sigs.v_v());
constexpr uint32_t kResult0 = 23;
constexpr uint32_t kResult1 = 42;
// Build a throwing helper function.
WasmFunctionCompiler& throw_func = r.NewFunction(sigs.i_ii());
throw_func.Build({WASM_THROW(except)});
// Build the main test function.
r.Build({WASM_TRY_CATCH_T(
kWasmI32,
WASM_STMTS(
WASM_I32V(kResult1),
WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)),
WASM_STMTS(WASM_CALL_FUNCTION(throw_func.function_index(),
WASM_I32V(7), WASM_I32V(9)),
WASM_DROP))),
WASM_STMTS(WASM_I32V(kResult0)), except)});
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJS(kResult0, 0);
r.CheckCallViaJS(kResult1, 1);
}
WASM_EXEC_TEST(TryCatchAllCallDirect) {
TestSignatures sigs;
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint8_t except = r.builder().AddException(sigs.v_v());
constexpr uint32_t kResult0 = 23;
constexpr uint32_t kResult1 = 42;
// Build a throwing helper function.
WasmFunctionCompiler& throw_func = r.NewFunction(sigs.i_ii());
throw_func.Build({WASM_THROW(except)});
// Build the main test function.
r.Build({WASM_TRY_CATCH_ALL_T(
kWasmI32,
WASM_STMTS(
WASM_I32V(kResult1),
WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)),
WASM_STMTS(WASM_CALL_FUNCTION(throw_func.function_index(),
WASM_I32V(7), WASM_I32V(9)),
WASM_DROP))),
WASM_STMTS(WASM_I32V(kResult0)))});
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJS(kResult0, 0);
r.CheckCallViaJS(kResult1, 1);
}
WASM_EXEC_TEST(TryCatchCallIndirect) {
TestSignatures sigs;
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint8_t except = r.builder().AddException(sigs.v_v());
constexpr uint32_t kResult0 = 23;
constexpr uint32_t kResult1 = 42;
// Build a throwing helper function.
WasmFunctionCompiler& throw_func = r.NewFunction(sigs.i_ii());
throw_func.Build({WASM_THROW(except)});
// Add an indirect function table.
uint16_t indirect_function_table[] = {
static_cast<uint16_t>(throw_func.function_index())};
r.builder().AddIndirectFunctionTable(indirect_function_table,
arraysize(indirect_function_table));
// Build the main test function.
r.Build({WASM_TRY_CATCH_T(
kWasmI32,
WASM_STMTS(WASM_I32V(kResult1),
WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)),
WASM_STMTS(WASM_CALL_INDIRECT(
throw_func.sig_index(), WASM_I32V(7),
WASM_I32V(9), WASM_LOCAL_GET(0)),
WASM_DROP))),
WASM_I32V(kResult0), except)});
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJS(kResult0, 0);
r.CheckCallViaJS(kResult1, 1);
}
WASM_EXEC_TEST(TryCatchAllCallIndirect) {
TestSignatures sigs;
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint8_t except = r.builder().AddException(sigs.v_v());
constexpr uint32_t kResult0 = 23;
constexpr uint32_t kResult1 = 42;
// Build a throwing helper function.
WasmFunctionCompiler& throw_func = r.NewFunction(sigs.i_ii());
throw_func.Build({WASM_THROW(except)});
// Add an indirect function table.
uint16_t indirect_function_table[] = {
static_cast<uint16_t>(throw_func.function_index())};
r.builder().AddIndirectFunctionTable(indirect_function_table,
arraysize(indirect_function_table));
// Build the main test function.
r.Build({WASM_TRY_CATCH_ALL_T(
kWasmI32,
WASM_STMTS(WASM_I32V(kResult1),
WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)),
WASM_STMTS(WASM_CALL_INDIRECT(
throw_func.sig_index(), WASM_I32V(7),
WASM_I32V(9), WASM_LOCAL_GET(0)),
WASM_DROP))),
WASM_I32V(kResult0))});
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJS(kResult0, 0);
r.CheckCallViaJS(kResult1, 1);
}
WASM_COMPILED_EXEC_TEST(TryCatchCallExternal) {
TestSignatures sigs;
HandleScope scope(CcTest::InitIsolateOnce());
const char* source = "(function() { throw 'ball'; })";
DirectHandle<JSFunction> js_function =
Cast<JSFunction>(v8::Utils::OpenDirectHandle(
*v8::Local<v8::Function>::Cast(CompileRun(source))));
ManuallyImportedJSFunction import = {sigs.i_ii(), js_function};
WasmRunner<uint32_t, uint32_t> r(execution_tier, kWasmOrigin, &import);
constexpr uint32_t kResult0 = 23;
constexpr uint32_t kResult1 = 42;
constexpr uint32_t kJSFunc = 0;
// Build the main test function.
r.Build({WASM_TRY_CATCH_ALL_T(
kWasmI32,
WASM_STMTS(WASM_I32V(kResult1),
WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)),
WASM_STMTS(WASM_CALL_FUNCTION(kJSFunc, WASM_I32V(7),
WASM_I32V(9)),
WASM_DROP))),
WASM_I32V(kResult0))});
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJS(kResult0, 0);
r.CheckCallViaJS(kResult1, 1);
}
WASM_COMPILED_EXEC_TEST(TryCatchAllCallExternal) {
TestSignatures sigs;
HandleScope scope(CcTest::InitIsolateOnce());
const char* source = "(function() { throw 'ball'; })";
DirectHandle<JSFunction> js_function =
Cast<JSFunction>(v8::Utils::OpenDirectHandle(
*v8::Local<v8::Function>::Cast(CompileRun(source))));
ManuallyImportedJSFunction import = {sigs.i_ii(), js_function};
WasmRunner<uint32_t, uint32_t> r(execution_tier, kWasmOrigin, &import);
constexpr uint32_t kResult0 = 23;
constexpr uint32_t kResult1 = 42;
constexpr uint32_t kJSFunc = 0;
// Build the main test function.
r.Build({WASM_TRY_CATCH_ALL_T(
kWasmI32,
WASM_STMTS(WASM_I32V(kResult1),
WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)),
WASM_STMTS(WASM_CALL_FUNCTION(kJSFunc, WASM_I32V(7),
WASM_I32V(9)),
WASM_DROP))),
WASM_I32V(kResult0))});
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJS(kResult0, 0);
r.CheckCallViaJS(kResult1, 1);
}
namespace {
void TestTrapNotCaught(uint8_t* code, size_t code_size,
TestExecutionTier execution_tier) {
TestSignatures sigs;
WasmRunner<uint32_t> r(execution_tier, kWasmOrigin, nullptr, "main");
r.builder().AddMemory(kWasmPageSize);
constexpr uint32_t kResultSuccess = 23;
constexpr uint32_t kResultCaught = 47;
// Add an indirect function table.
const int kTableSize = 2;
r.builder().AddIndirectFunctionTable(nullptr, kTableSize);
// Build a trapping helper function.
WasmFunctionCompiler& trap_func = r.NewFunction(sigs.i_ii());
trap_func.Build(base::VectorOf(code, code_size));
// Build the main test function.
r.Build({WASM_TRY_CATCH_ALL_T(
kWasmI32,
WASM_STMTS(WASM_I32V(kResultSuccess),
WASM_CALL_FUNCTION(trap_func.function_index(), WASM_I32V(7),
WASM_I32V(9)),
WASM_DROP),
WASM_STMTS(WASM_I32V(kResultCaught)))});
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJSTraps();
}
} // namespace
WASM_EXEC_TEST(TryCatchTrapUnreachable) {
uint8_t code[] = {WASM_UNREACHABLE};
TestTrapNotCaught(code, arraysize(code), execution_tier);
}
WASM_EXEC_TEST(TryCatchTrapMemOutOfBounds) {
uint8_t code[] = {WASM_LOAD_MEM(MachineType::Int32(), WASM_I32V_1(-1))};
TestTrapNotCaught(code, arraysize(code), execution_tier);
}
WASM_EXEC_TEST(TryCatchTrapDivByZero) {
uint8_t code[] = {WASM_I32_DIVS(WASM_LOCAL_GET(0), WASM_I32V_1(0))};
TestTrapNotCaught(code, arraysize(code), execution_tier);
}
WASM_EXEC_TEST(TryCatchTrapRemByZero) {
uint8_t code[] = {WASM_I32_REMS(WASM_LOCAL_GET(0), WASM_I32V_1(0))};
TestTrapNotCaught(code, arraysize(code), execution_tier);
}
WASM_EXEC_TEST(TryCatchTrapTableFill) {
int table_index = 0;
int length = 10; // OOB.
int start = 10; // OOB.
uint8_t code[] = {
WASM_TABLE_FILL(table_index, WASM_I32V(length),
WASM_REF_NULL(kFuncRefCode), WASM_I32V(start)),
WASM_I32V_1(42)};
TestTrapNotCaught(code, arraysize(code), execution_tier);
}
namespace {
// TODO(cleanup): Define in cctest.h and reuse where appropriate.
class IsolateScope {
public:
IsolateScope() {
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
isolate_ = v8::Isolate::New(create_params);
isolate_->Enter();
}
~IsolateScope() {
isolate_->Exit();
isolate_->Dispose();
}
v8::Isolate* isolate() { return isolate_; }
Isolate* i_isolate() { return reinterpret_cast<Isolate*>(isolate_); }
private:
v8::Isolate* isolate_;
};
} // namespace
UNINITIALIZED_WASM_EXEC_TEST(TestStackOverflowNotCaught) {
TestSignatures sigs;
// v8_flags.stack_size must be set before isolate initialization.
FlagScope<int32_t> stack_size(&v8_flags.stack_size, 8);
IsolateScope isolate_scope;
LocalContext context(isolate_scope.isolate());
WasmRunner<uint32_t> r(execution_tier, kWasmOrigin, nullptr, "main",
isolate_scope.i_isolate());
// Build a function that calls itself until stack overflow.
WasmFunctionCompiler& stack_overflow = r.NewFunction(sigs.v_v());
stack_overflow.Build({kExprCallFunction,
static_cast<uint8_t>(stack_overflow.function_index())});
// Build the main test function.
r.Build({WASM_TRY_CATCH_ALL_T(
kWasmI32,
WASM_STMTS(WASM_I32V(1), kExprCallFunction,
static_cast<uint8_t>(stack_overflow.function_index())),
WASM_STMTS(WASM_I32V(1)))});
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJSTraps();
}
} // namespace v8::internal::wasm

View File

@ -0,0 +1,436 @@
// Copyright 2024 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 "src/base/overflowing-math.h"
#include "src/codegen/assembler-inl.h"
#include "src/numbers/conversions.h"
#include "src/wasm/wasm-opcodes.h"
#include "test/cctest/cctest.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/cctest/wasm/wasm-simd-utils.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
#include "third_party/fp16/src/include/fp16.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace test_run_wasm_f16 {
WASM_EXEC_TEST(F16Load) {
i::v8_flags.experimental_wasm_fp16 = true;
WasmRunner<float> r(execution_tier);
uint16_t* memory = r.builder().AddMemoryElems<uint16_t>(4);
r.Build({WASM_F16_LOAD_MEM(WASM_I32V_1(4))});
r.builder().WriteMemory(&memory[2], fp16_ieee_from_fp32_value(2.75));
CHECK_EQ(2.75f, r.Call());
}
WASM_EXEC_TEST(F16Store) {
i::v8_flags.experimental_wasm_fp16 = true;
WasmRunner<int32_t> r(execution_tier);
uint16_t* memory = r.builder().AddMemoryElems<uint16_t>(4);
r.Build({WASM_F16_STORE_MEM(WASM_I32V_1(4), WASM_F32(2.75)), WASM_ZERO});
r.Call();
CHECK_EQ(r.builder().ReadMemory(&memory[2]), fp16_ieee_from_fp32_value(2.75));
}
WASM_EXEC_TEST(F16x8Splat) {
i::v8_flags.experimental_wasm_fp16 = true;
WasmRunner<int32_t, float> r(execution_tier);
// Set up a global to hold output vector.
uint16_t* g = r.builder().AddGlobal<uint16_t>(kWasmS128);
uint8_t param1 = 0;
r.Build({WASM_GLOBAL_SET(0, WASM_SIMD_F16x8_SPLAT(WASM_LOCAL_GET(param1))),
WASM_ONE});
FOR_FLOAT32_INPUTS(x) {
r.Call(x);
uint16_t expected = fp16_ieee_from_fp32_value(x);
for (int i = 0; i < 8; i++) {
uint16_t actual = LANE(g, i);
if (std::isnan(x)) {
CHECK(isnan(actual));
} else {
CHECK_EQ(actual, expected);
}
}
}
}
WASM_EXEC_TEST(F16x8ReplaceLane) {
i::v8_flags.experimental_wasm_fp16 = true;
WasmRunner<int32_t> r(execution_tier);
// Set up a global to hold output vector.
uint16_t* g = r.builder().AddGlobal<uint16_t>(kWasmS128);
// Build function to replace each lane with its (FP) index.
r.Build({WASM_SIMD_F16x8_SPLAT(WASM_F32(3.14159f)),
WASM_F32(0.0f),
WASM_SIMD_OP(kExprF16x8ReplaceLane),
0,
WASM_F32(1.0f),
WASM_SIMD_OP(kExprF16x8ReplaceLane),
1,
WASM_F32(2.0f),
WASM_SIMD_OP(kExprF16x8ReplaceLane),
2,
WASM_F32(3.0f),
WASM_SIMD_OP(kExprF16x8ReplaceLane),
3,
WASM_F32(4.0f),
WASM_SIMD_OP(kExprF16x8ReplaceLane),
4,
WASM_F32(5.0f),
WASM_SIMD_OP(kExprF16x8ReplaceLane),
5,
WASM_F32(6.0f),
WASM_SIMD_OP(kExprF16x8ReplaceLane),
6,
WASM_F32(7.0f),
WASM_SIMD_OP(kExprF16x8ReplaceLane),
7,
kExprGlobalSet,
0,
WASM_ONE});
r.Call();
for (int i = 0; i < 8; i++) {
CHECK_EQ(fp16_ieee_from_fp32_value(i), LANE(g, i));
}
}
WASM_EXEC_TEST(F16x8ExtractLane) {
i::v8_flags.experimental_wasm_fp16 = true;
WasmRunner<int32_t> r(execution_tier);
uint16_t* g = r.builder().AddGlobal<uint16_t>(kWasmS128);
float* globals[8];
for (int i = 0; i < 8; i++) {
LANE(g, i) = fp16_ieee_from_fp32_value(i);
globals[i] = r.builder().AddGlobal<float>(kWasmF32);
}
r.Build(
{WASM_GLOBAL_SET(1, WASM_SIMD_F16x8_EXTRACT_LANE(0, WASM_GLOBAL_GET(0))),
WASM_GLOBAL_SET(2, WASM_SIMD_F16x8_EXTRACT_LANE(1, WASM_GLOBAL_GET(0))),
WASM_GLOBAL_SET(3, WASM_SIMD_F16x8_EXTRACT_LANE(2, WASM_GLOBAL_GET(0))),
WASM_GLOBAL_SET(4, WASM_SIMD_F16x8_EXTRACT_LANE(3, WASM_GLOBAL_GET(0))),
WASM_GLOBAL_SET(5, WASM_SIMD_F16x8_EXTRACT_LANE(4, WASM_GLOBAL_GET(0))),
WASM_GLOBAL_SET(6, WASM_SIMD_F16x8_EXTRACT_LANE(5, WASM_GLOBAL_GET(0))),
WASM_GLOBAL_SET(7, WASM_SIMD_F16x8_EXTRACT_LANE(6, WASM_GLOBAL_GET(0))),
WASM_GLOBAL_SET(8, WASM_SIMD_F16x8_EXTRACT_LANE(7, WASM_GLOBAL_GET(0))),
WASM_ONE});
r.Call();
for (int i = 0; i < 8; i++) {
CHECK_EQ(*globals[i], i);
}
}
#define UN_OP_LIST(V) \
V(Abs, std::abs) \
V(Neg, -) \
V(Sqrt, std::sqrt) \
V(Ceil, ceilf) \
V(Floor, floorf) \
V(Trunc, truncf) \
V(NearestInt, nearbyintf)
#define TEST_UN_OP(WasmName, COp) \
uint16_t WasmName##F16(uint16_t a) { \
return fp16_ieee_from_fp32_value(COp(fp16_ieee_to_fp32_value(a))); \
} \
WASM_EXEC_TEST(F16x8##WasmName) { \
i::v8_flags.experimental_wasm_fp16 = true; \
RunF16x8UnOpTest(execution_tier, kExprF16x8##WasmName, WasmName##F16); \
}
UN_OP_LIST(TEST_UN_OP)
#undef TEST_UN_OP
#undef UN_OP_LIST
#define CMP_OP_LIST(V) \
V(Eq, ==) \
V(Ne, !=) \
V(Gt, >) \
V(Ge, >=) \
V(Lt, <) \
V(Le, <=)
#define TEST_CMP_OP(WasmName, COp) \
int16_t WasmName(uint16_t a, uint16_t b) { \
return fp16_ieee_to_fp32_value(a) COp fp16_ieee_to_fp32_value(b) ? -1 : 0; \
} \
WASM_EXEC_TEST(F16x8##WasmName) { \
i::v8_flags.experimental_wasm_fp16 = true; \
RunF16x8CompareOpTest(execution_tier, kExprF16x8##WasmName, WasmName); \
}
CMP_OP_LIST(TEST_CMP_OP)
#undef TEST_CMP_OP
#undef UN_CMP_LIST
float Add(float a, float b) { return a + b; }
float Sub(float a, float b) { return a - b; }
float Mul(float a, float b) { return a * b; }
#define BIN_OP_LIST(V) \
V(Add, Add) \
V(Sub, Sub) \
V(Mul, Mul) \
V(Div, base::Divide) \
V(Min, JSMin) \
V(Max, JSMax) \
V(Pmin, Minimum) \
V(Pmax, Maximum)
#define TEST_BIN_OP(WasmName, COp) \
uint16_t WasmName##F16(uint16_t a, uint16_t b) { \
return fp16_ieee_from_fp32_value( \
COp(fp16_ieee_to_fp32_value(a), fp16_ieee_to_fp32_value(b))); \
} \
WASM_EXEC_TEST(F16x8##WasmName) { \
i::v8_flags.experimental_wasm_fp16 = true; \
RunF16x8BinOpTest(execution_tier, kExprF16x8##WasmName, WasmName##F16); \
}
BIN_OP_LIST(TEST_BIN_OP)
#undef TEST_BIN_OP
#undef BIN_OP_LIST
WASM_EXEC_TEST(F16x8ConvertI16x8) {
i::v8_flags.experimental_wasm_fp16 = true;
WasmRunner<int32_t, int32_t> r(execution_tier);
// Create two output vectors to hold signed and unsigned results.
uint16_t* g0 = r.builder().AddGlobal<uint16_t>(kWasmS128);
uint16_t* g1 = r.builder().AddGlobal<uint16_t>(kWasmS128);
// Build fn to splat test value, perform conversions, and write the results.
uint8_t value = 0;
uint8_t temp1 = r.AllocateLocal(kWasmS128);
r.Build({WASM_LOCAL_SET(temp1, WASM_SIMD_I16x8_SPLAT(WASM_LOCAL_GET(value))),
WASM_GLOBAL_SET(0, WASM_SIMD_UNOP(kExprF16x8SConvertI16x8,
WASM_LOCAL_GET(temp1))),
WASM_GLOBAL_SET(1, WASM_SIMD_UNOP(kExprF16x8UConvertI16x8,
WASM_LOCAL_GET(temp1))),
WASM_ONE});
FOR_INT16_INPUTS(x) {
r.Call(x);
uint16_t expected_signed = fp16_ieee_from_fp32_value(x);
uint16_t expected_unsigned =
fp16_ieee_from_fp32_value(static_cast<uint16_t>(x));
for (int i = 0; i < 8; i++) {
CHECK_EQ(expected_signed, LANE(g0, i));
CHECK_EQ(expected_unsigned, LANE(g1, i));
}
}
}
int16_t ConvertToInt(uint16_t f16, bool unsigned_result) {
float f32 = fp16_ieee_to_fp32_value(f16);
if (std::isnan(f32)) return 0;
if (unsigned_result) {
if (f32 > float{kMaxUInt16}) return static_cast<uint16_t>(kMaxUInt16);
if (f32 < 0) return 0;
return static_cast<uint16_t>(f32);
} else {
if (f32 > float{kMaxInt16}) return static_cast<int16_t>(kMaxInt16);
if (f32 < float{kMinInt16}) return static_cast<int16_t>(kMinInt16);
return static_cast<int16_t>(f32);
}
}
// Tests both signed and unsigned conversion.
WASM_EXEC_TEST(I16x8ConvertF16x8) {
i::v8_flags.experimental_wasm_fp16 = true;
WasmRunner<int32_t, float> r(execution_tier);
// Create two output vectors to hold signed and unsigned results.
int16_t* g0 = r.builder().AddGlobal<int16_t>(kWasmS128);
int16_t* g1 = r.builder().AddGlobal<int16_t>(kWasmS128);
// Build fn to splat test value, perform conversions, and write the results.
uint8_t value = 0;
uint8_t temp1 = r.AllocateLocal(kWasmS128);
r.Build({WASM_LOCAL_SET(temp1, WASM_SIMD_F16x8_SPLAT(WASM_LOCAL_GET(value))),
WASM_GLOBAL_SET(0, WASM_SIMD_UNOP(kExprI16x8SConvertF16x8,
WASM_LOCAL_GET(temp1))),
WASM_GLOBAL_SET(1, WASM_SIMD_UNOP(kExprI16x8UConvertF16x8,
WASM_LOCAL_GET(temp1))),
WASM_ONE});
FOR_FLOAT32_INPUTS(x) {
if (!PlatformCanRepresent(x)) continue;
r.Call(x);
int16_t expected_signed = ConvertToInt(fp16_ieee_from_fp32_value(x), false);
int16_t expected_unsigned =
ConvertToInt(fp16_ieee_from_fp32_value(x), true);
for (int i = 0; i < 8; i++) {
CHECK_EQ(expected_signed, LANE(g0, i));
CHECK_EQ(expected_unsigned, LANE(g1, i));
}
}
}
WASM_EXEC_TEST(F16x8DemoteF32x4Zero) {
i::v8_flags.experimental_wasm_fp16 = true;
WasmRunner<int32_t, float> r(execution_tier);
uint16_t* g = r.builder().AddGlobal<uint16_t>(kWasmS128);
r.Build({WASM_GLOBAL_SET(
0, WASM_SIMD_UNOP(kExprF16x8DemoteF32x4Zero,
WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(0)))),
WASM_ONE});
FOR_FLOAT32_INPUTS(x) {
r.Call(x);
uint16_t expected = fp16_ieee_from_fp32_value(x);
for (int i = 0; i < 4; i++) {
uint16_t actual = LANE(g, i);
CheckFloat16LaneResult(x, x, expected, actual, true);
}
for (int i = 4; i < 8; i++) {
uint16_t actual = LANE(g, i);
CheckFloat16LaneResult(x, x, 0, actual, true);
}
}
}
WASM_EXEC_TEST(F16x8DemoteF64x2Zero) {
i::v8_flags.experimental_wasm_fp16 = true;
WasmRunner<int32_t, double> r(execution_tier);
uint16_t* g = r.builder().AddGlobal<uint16_t>(kWasmS128);
r.Build({WASM_GLOBAL_SET(
0, WASM_SIMD_UNOP(kExprF16x8DemoteF64x2Zero,
WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(0)))),
WASM_ONE});
FOR_FLOAT64_INPUTS(x) {
r.Call(x);
uint16_t expected = DoubleToFloat16(x);
for (int i = 0; i < 2; i++) {
uint16_t actual = LANE(g, i);
CheckFloat16LaneResult(x, x, expected, actual, true);
}
for (int i = 2; i < 8; i++) {
uint16_t actual = LANE(g, i);
CheckFloat16LaneResult(x, x, 0, actual, true);
}
}
}
WASM_EXEC_TEST(F32x4PromoteLowF16x8) {
i::v8_flags.experimental_wasm_fp16 = true;
WasmRunner<int32_t, float> r(execution_tier);
float* g = r.builder().AddGlobal<float>(kWasmS128);
r.Build({WASM_GLOBAL_SET(
0, WASM_SIMD_UNOP(kExprF32x4PromoteLowF16x8,
WASM_SIMD_F16x8_SPLAT(WASM_LOCAL_GET(0)))),
WASM_ONE});
FOR_FLOAT32_INPUTS(x) {
r.Call(x);
float expected = fp16_ieee_to_fp32_value(fp16_ieee_from_fp32_value(x));
for (int i = 0; i < 4; i++) {
float actual = LANE(g, i);
CheckFloatResult(x, x, expected, actual, true);
}
}
}
struct FMOperation {
const float a;
const float b;
const float c;
const float fused_result;
};
constexpr float large_n = 1e4;
constexpr float finf = std::numeric_limits<float>::infinity();
constexpr float qNan = std::numeric_limits<float>::quiet_NaN();
// Fused Multiply-Add performs a * b + c.
static FMOperation qfma_array[] = {
{2.0f, 3.0f, 1.0f, 7.0f},
// fused: a * b + c = (positive overflow) + -inf = -inf
// unfused: a * b + c = inf + -inf = NaN
{large_n, large_n, -finf, -finf},
// fused: a * b + c = (negative overflow) + inf = inf
// unfused: a * b + c = -inf + inf = NaN
{-large_n, large_n, finf, finf},
// NaN
{2.0f, 3.0f, qNan, qNan},
// -NaN
{2.0f, 3.0f, -qNan, qNan}};
base::Vector<const FMOperation> qfma_vector() {
return base::ArrayVector(qfma_array);
}
// Fused Multiply-Subtract performs -(a * b) + c.
static FMOperation qfms_array[]{
{2.0f, 3.0f, 1.0f, -5.0f},
// fused: -(a * b) + c = - (positive overflow) + inf = inf
// unfused: -(a * b) + c = - inf + inf = NaN
{large_n, large_n, finf, finf},
// fused: -(a * b) + c = (negative overflow) + -inf = -inf
// unfused: -(a * b) + c = -inf - -inf = NaN
{-large_n, large_n, -finf, -finf},
// NaN
{2.0f, 3.0f, qNan, qNan},
// -NaN
{2.0f, 3.0f, -qNan, qNan}};
base::Vector<const FMOperation> qfms_vector() {
return base::ArrayVector(qfms_array);
}
WASM_EXEC_TEST(F16x8Qfma) {
i::v8_flags.experimental_wasm_fp16 = true;
WasmRunner<int32_t, float, float, float> r(execution_tier);
// Set up global to hold output.
uint16_t* g = r.builder().AddGlobal<uint16_t>(kWasmS128);
uint8_t value1 = 0, value2 = 1, value3 = 2;
r.Build(
{WASM_GLOBAL_SET(0, WASM_SIMD_F16x8_QFMA(
WASM_SIMD_F16x8_SPLAT(WASM_LOCAL_GET(value1)),
WASM_SIMD_F16x8_SPLAT(WASM_LOCAL_GET(value2)),
WASM_SIMD_F16x8_SPLAT(WASM_LOCAL_GET(value3)))),
WASM_ONE});
for (FMOperation x : qfma_vector()) {
r.Call(x.a, x.b, x.c);
uint16_t expected = fp16_ieee_from_fp32_value(x.fused_result);
for (int i = 0; i < 8; i++) {
uint16_t actual = LANE(g, i);
CheckFloat16LaneResult(x.a, x.b, x.c, expected, actual, true /* exact */);
}
}
}
WASM_EXEC_TEST(F16x8Qfms) {
i::v8_flags.experimental_wasm_fp16 = true;
WasmRunner<int32_t, float, float, float> r(execution_tier);
// Set up global to hold output.
uint16_t* g = r.builder().AddGlobal<uint16_t>(kWasmS128);
uint8_t value1 = 0, value2 = 1, value3 = 2;
r.Build(
{WASM_GLOBAL_SET(0, WASM_SIMD_F16x8_QFMS(
WASM_SIMD_F16x8_SPLAT(WASM_LOCAL_GET(value1)),
WASM_SIMD_F16x8_SPLAT(WASM_LOCAL_GET(value2)),
WASM_SIMD_F16x8_SPLAT(WASM_LOCAL_GET(value3)))),
WASM_ONE});
for (FMOperation x : qfms_vector()) {
r.Call(x.a, x.b, x.c);
uint16_t expected = fp16_ieee_from_fp32_value(x.fused_result);
for (int i = 0; i < 8; i++) {
uint16_t actual = LANE(g, i);
CheckFloat16LaneResult(x.a, x.b, x.c, expected, actual, true /* exact */);
}
}
}
} // namespace test_run_wasm_f16
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,533 @@
// Copyright 2015 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 <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "include/v8-function.h"
#include "src/api/api-inl.h"
#include "src/codegen/assembler-inl.h"
#include "src/objects/heap-number-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/common/value-helper.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace {
// A helper for generating predictable but unique argument values that
// are easy to debug (e.g. with misaligned stacks).
class PredictableInputValues {
public:
int base_;
explicit PredictableInputValues(int base) : base_(base) {}
double arg_d(int which) { return base_ * which + ((which & 1) * 0.5); }
float arg_f(int which) { return base_ * which + ((which & 1) * 0.25); }
int32_t arg_i(int which) { return base_ * which + ((which & 1) * kMinInt); }
int64_t arg_l(int which) {
return base_ * which + ((which & 1) * (0x04030201LL << 32));
}
};
ManuallyImportedJSFunction CreateJSSelector(FunctionSig* sig, int which) {
const int kMaxParams = 11;
static const char* formals[kMaxParams] = {"",
"a",
"a,b",
"a,b,c",
"a,b,c,d",
"a,b,c,d,e",
"a,b,c,d,e,f",
"a,b,c,d,e,f,g",
"a,b,c,d,e,f,g,h",
"a,b,c,d,e,f,g,h,i",
"a,b,c,d,e,f,g,h,i,j"};
CHECK_LT(which, static_cast<int>(sig->parameter_count()));
CHECK_LT(static_cast<int>(sig->parameter_count()), kMaxParams);
base::EmbeddedVector<char, 256> source;
char param = 'a' + which;
SNPrintF(source, "(function(%s) { return %c; })",
formals[sig->parameter_count()], param);
DirectHandle<JSFunction> js_function =
Cast<JSFunction>(v8::Utils::OpenDirectHandle(
*v8::Local<v8::Function>::Cast(CompileRun(source.begin()))));
ManuallyImportedJSFunction import = {sig, js_function};
return import;
}
} // namespace
WASM_COMPILED_EXEC_TEST(Run_Int32Sub_jswrapped) {
WasmRunner<int, int, int> r(execution_tier);
r.Build({WASM_I32_SUB(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1))});
r.CheckCallViaJS(33, 44, 11);
r.CheckCallViaJS(-8723487, -8000000, 723487);
}
WASM_COMPILED_EXEC_TEST(Run_Float32Div_jswrapped) {
WasmRunner<float, float, float> r(execution_tier);
r.Build({WASM_F32_DIV(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1))});
r.CheckCallViaJS(92, 46, 0.5);
r.CheckCallViaJS(64, -16, -0.25);
}
WASM_COMPILED_EXEC_TEST(Run_Float64Add_jswrapped) {
WasmRunner<double, double, double> r(execution_tier);
r.Build({WASM_F64_ADD(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1))});
r.CheckCallViaJS(3, 2, 1);
r.CheckCallViaJS(-5.5, -5.25, -0.25);
}
WASM_COMPILED_EXEC_TEST(Run_I32Popcount_jswrapped) {
WasmRunner<int, int> r(execution_tier);
r.Build({WASM_I32_POPCNT(WASM_LOCAL_GET(0))});
r.CheckCallViaJS(2, 9);
r.CheckCallViaJS(3, 11);
r.CheckCallViaJS(6, 0x3F);
}
WASM_COMPILED_EXEC_TEST(Run_CallJS_Add_jswrapped) {
TestSignatures sigs;
HandleScope scope(CcTest::InitIsolateOnce());
const char* source = "(function(a) { return a + 99; })";
DirectHandle<JSFunction> js_function =
Cast<JSFunction>(v8::Utils::OpenDirectHandle(
*v8::Local<v8::Function>::Cast(CompileRun(source))));
ManuallyImportedJSFunction import = {sigs.i_i(), js_function};
WasmRunner<int, int> r(execution_tier, kWasmOrigin, &import);
uint32_t js_index = 0;
r.Build({WASM_CALL_FUNCTION(js_index, WASM_LOCAL_GET(0))});
r.CheckCallViaJS(101, 2);
r.CheckCallViaJS(199, 100);
r.CheckCallViaJS(-666666801, -666666900);
}
void RunJSSelectTest(TestExecutionTier tier, int which) {
const int kMaxParams = 8;
PredictableInputValues inputs(0x100);
ValueType type = kWasmF64;
ValueType types[kMaxParams + 1] = {type, type, type, type, type,
type, type, type, type};
for (int num_params = which + 1; num_params < kMaxParams; num_params++) {
HandleScope scope(CcTest::InitIsolateOnce());
FunctionSig sig(1, num_params, types);
ManuallyImportedJSFunction import = CreateJSSelector(&sig, which);
WasmRunner<void> r(tier, kWasmOrigin, &import);
uint32_t js_index = 0;
WasmFunctionCompiler& t = r.NewFunction(&sig);
{
std::vector<uint8_t> code;
for (int i = 0; i < num_params; i++) {
ADD_CODE(code, WASM_F64(inputs.arg_d(i)));
}
ADD_CODE(code, kExprCallFunction, static_cast<uint8_t>(js_index));
size_t end = code.size();
code.push_back(0);
t.Build(base::VectorOf(code.data(), end));
}
double expected = inputs.arg_d(which);
r.CheckCallApplyViaJS(expected, t.function_index(), {});
}
}
WASM_COMPILED_EXEC_TEST(Run_JSSelect_0) {
CcTest::InitializeVM();
RunJSSelectTest(execution_tier, 0);
}
WASM_COMPILED_EXEC_TEST(Run_JSSelect_1) {
CcTest::InitializeVM();
RunJSSelectTest(execution_tier, 1);
}
WASM_COMPILED_EXEC_TEST(Run_JSSelect_2) {
CcTest::InitializeVM();
RunJSSelectTest(execution_tier, 2);
}
WASM_COMPILED_EXEC_TEST(Run_JSSelect_3) {
CcTest::InitializeVM();
RunJSSelectTest(execution_tier, 3);
}
WASM_COMPILED_EXEC_TEST(Run_JSSelect_4) {
CcTest::InitializeVM();
RunJSSelectTest(execution_tier, 4);
}
WASM_COMPILED_EXEC_TEST(Run_JSSelect_5) {
CcTest::InitializeVM();
RunJSSelectTest(execution_tier, 5);
}
WASM_COMPILED_EXEC_TEST(Run_JSSelect_6) {
CcTest::InitializeVM();
RunJSSelectTest(execution_tier, 6);
}
WASM_COMPILED_EXEC_TEST(Run_JSSelect_7) {
CcTest::InitializeVM();
RunJSSelectTest(execution_tier, 7);
}
void RunWASMSelectTest(TestExecutionTier tier, int which) {
PredictableInputValues inputs(0x200);
Isolate* isolate = CcTest::InitIsolateOnce();
const int kMaxParams = 8;
for (int num_params = which + 1; num_params < kMaxParams; num_params++) {
ValueType type = kWasmF64;
ValueType types[kMaxParams + 1] = {type, type, type, type, type,
type, type, type, type};
FunctionSig sig(1, num_params, types);
WasmRunner<void> r(tier);
WasmFunctionCompiler& t = r.NewFunction(&sig);
t.Build({WASM_LOCAL_GET(which)});
DirectHandle<Object> args[] = {
isolate->factory()->NewNumber(inputs.arg_d(0)),
isolate->factory()->NewNumber(inputs.arg_d(1)),
isolate->factory()->NewNumber(inputs.arg_d(2)),
isolate->factory()->NewNumber(inputs.arg_d(3)),
isolate->factory()->NewNumber(inputs.arg_d(4)),
isolate->factory()->NewNumber(inputs.arg_d(5)),
isolate->factory()->NewNumber(inputs.arg_d(6)),
isolate->factory()->NewNumber(inputs.arg_d(7)),
};
double expected = inputs.arg_d(which);
r.CheckCallApplyViaJS(expected, t.function_index(), {args, kMaxParams});
}
}
WASM_COMPILED_EXEC_TEST(Run_WASMSelect_0) {
CcTest::InitializeVM();
RunWASMSelectTest(execution_tier, 0);
}
WASM_COMPILED_EXEC_TEST(Run_WASMSelect_1) {
CcTest::InitializeVM();
RunWASMSelectTest(execution_tier, 1);
}
WASM_COMPILED_EXEC_TEST(Run_WASMSelect_2) {
CcTest::InitializeVM();
RunWASMSelectTest(execution_tier, 2);
}
WASM_COMPILED_EXEC_TEST(Run_WASMSelect_3) {
CcTest::InitializeVM();
RunWASMSelectTest(execution_tier, 3);
}
WASM_COMPILED_EXEC_TEST(Run_WASMSelect_4) {
CcTest::InitializeVM();
RunWASMSelectTest(execution_tier, 4);
}
WASM_COMPILED_EXEC_TEST(Run_WASMSelect_5) {
CcTest::InitializeVM();
RunWASMSelectTest(execution_tier, 5);
}
WASM_COMPILED_EXEC_TEST(Run_WASMSelect_6) {
CcTest::InitializeVM();
RunWASMSelectTest(execution_tier, 6);
}
WASM_COMPILED_EXEC_TEST(Run_WASMSelect_7) {
CcTest::InitializeVM();
RunWASMSelectTest(execution_tier, 7);
}
void RunWASMSelectAlignTest(TestExecutionTier tier, int num_args,
int num_params) {
PredictableInputValues inputs(0x300);
Isolate* isolate = CcTest::InitIsolateOnce();
const int kMaxParams = 10;
DCHECK_LE(num_args, kMaxParams);
ValueType type = kWasmF64;
ValueType types[kMaxParams + 1] = {type, type, type, type, type, type,
type, type, type, type, type};
FunctionSig sig(1, num_params, types);
for (int which = 0; which < num_params; which++) {
WasmRunner<void> r(tier);
WasmFunctionCompiler& t = r.NewFunction(&sig);
t.Build({WASM_LOCAL_GET(which)});
DirectHandle<Object> args[] = {
isolate->factory()->NewNumber(inputs.arg_d(0)),
isolate->factory()->NewNumber(inputs.arg_d(1)),
isolate->factory()->NewNumber(inputs.arg_d(2)),
isolate->factory()->NewNumber(inputs.arg_d(3)),
isolate->factory()->NewNumber(inputs.arg_d(4)),
isolate->factory()->NewNumber(inputs.arg_d(5)),
isolate->factory()->NewNumber(inputs.arg_d(6)),
isolate->factory()->NewNumber(inputs.arg_d(7)),
isolate->factory()->NewNumber(inputs.arg_d(8)),
isolate->factory()->NewNumber(inputs.arg_d(9))};
double nan = std::numeric_limits<double>::quiet_NaN();
double expected = which < num_args ? inputs.arg_d(which) : nan;
r.CheckCallApplyViaJS(expected, t.function_index(),
{args, static_cast<size_t>(num_args)});
}
}
WASM_COMPILED_EXEC_TEST(Run_WASMSelectAlign_0) {
CcTest::InitializeVM();
RunWASMSelectAlignTest(execution_tier, 0, 1);
RunWASMSelectAlignTest(execution_tier, 0, 2);
}
WASM_COMPILED_EXEC_TEST(Run_WASMSelectAlign_1) {
CcTest::InitializeVM();
RunWASMSelectAlignTest(execution_tier, 1, 2);
RunWASMSelectAlignTest(execution_tier, 1, 3);
}
WASM_COMPILED_EXEC_TEST(Run_WASMSelectAlign_2) {
CcTest::InitializeVM();
RunWASMSelectAlignTest(execution_tier, 2, 3);
RunWASMSelectAlignTest(execution_tier, 2, 4);
}
WASM_COMPILED_EXEC_TEST(Run_WASMSelectAlign_3) {
CcTest::InitializeVM();
RunWASMSelectAlignTest(execution_tier, 3, 3);
RunWASMSelectAlignTest(execution_tier, 3, 4);
}
WASM_COMPILED_EXEC_TEST(Run_WASMSelectAlign_4) {
CcTest::InitializeVM();
RunWASMSelectAlignTest(execution_tier, 4, 3);
RunWASMSelectAlignTest(execution_tier, 4, 4);
}
WASM_COMPILED_EXEC_TEST(Run_WASMSelectAlign_7) {
CcTest::InitializeVM();
RunWASMSelectAlignTest(execution_tier, 7, 5);
RunWASMSelectAlignTest(execution_tier, 7, 6);
RunWASMSelectAlignTest(execution_tier, 7, 7);
}
WASM_COMPILED_EXEC_TEST(Run_WASMSelectAlign_8) {
CcTest::InitializeVM();
RunWASMSelectAlignTest(execution_tier, 8, 5);
RunWASMSelectAlignTest(execution_tier, 8, 6);
RunWASMSelectAlignTest(execution_tier, 8, 7);
RunWASMSelectAlignTest(execution_tier, 8, 8);
}
WASM_COMPILED_EXEC_TEST(Run_WASMSelectAlign_9) {
CcTest::InitializeVM();
RunWASMSelectAlignTest(execution_tier, 9, 6);
RunWASMSelectAlignTest(execution_tier, 9, 7);
RunWASMSelectAlignTest(execution_tier, 9, 8);
RunWASMSelectAlignTest(execution_tier, 9, 9);
}
WASM_COMPILED_EXEC_TEST(Run_WASMSelectAlign_10) {
CcTest::InitializeVM();
RunWASMSelectAlignTest(execution_tier, 10, 7);
RunWASMSelectAlignTest(execution_tier, 10, 8);
RunWASMSelectAlignTest(execution_tier, 10, 9);
RunWASMSelectAlignTest(execution_tier, 10, 10);
}
void RunJSSelectAlignTest(TestExecutionTier tier, int num_args,
int num_params) {
PredictableInputValues inputs(0x400);
Isolate* isolate = CcTest::InitIsolateOnce();
Factory* factory = isolate->factory();
const int kMaxParams = 10;
CHECK_LE(num_args, kMaxParams);
CHECK_LE(num_params, kMaxParams);
ValueType type = kWasmF64;
ValueType types[kMaxParams + 1] = {type, type, type, type, type, type,
type, type, type, type, type};
FunctionSig sig(1, num_params, types);
i::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
// Build the calling code.
std::vector<uint8_t> code;
for (int i = 0; i < num_params; i++) {
ADD_CODE(code, WASM_LOCAL_GET(i));
}
uint8_t imported_js_index = 0;
ADD_CODE(code, kExprCallFunction, imported_js_index);
size_t end = code.size();
code.push_back(0);
// Call different select JS functions.
for (int which = 0; which < num_params; which++) {
HandleScope scope(isolate);
ManuallyImportedJSFunction import = CreateJSSelector(&sig, which);
WasmRunner<void> r(tier, kWasmOrigin, &import);
WasmFunctionCompiler& t = r.NewFunction(&sig);
t.Build(base::VectorOf(code.data(), end));
DirectHandle<Object> args[] = {
factory->NewNumber(inputs.arg_d(0)),
factory->NewNumber(inputs.arg_d(1)),
factory->NewNumber(inputs.arg_d(2)),
factory->NewNumber(inputs.arg_d(3)),
factory->NewNumber(inputs.arg_d(4)),
factory->NewNumber(inputs.arg_d(5)),
factory->NewNumber(inputs.arg_d(6)),
factory->NewNumber(inputs.arg_d(7)),
factory->NewNumber(inputs.arg_d(8)),
factory->NewNumber(inputs.arg_d(9)),
};
double nan = std::numeric_limits<double>::quiet_NaN();
double expected = which < num_args ? inputs.arg_d(which) : nan;
r.CheckCallApplyViaJS(expected, t.function_index(),
{args, static_cast<size_t>(num_args)});
}
}
WASM_COMPILED_EXEC_TEST(Run_JSSelectAlign_0) {
CcTest::InitializeVM();
RunJSSelectAlignTest(execution_tier, 0, 1);
RunJSSelectAlignTest(execution_tier, 0, 2);
}
WASM_COMPILED_EXEC_TEST(Run_JSSelectAlign_1) {
CcTest::InitializeVM();
RunJSSelectAlignTest(execution_tier, 1, 2);
RunJSSelectAlignTest(execution_tier, 1, 3);
}
WASM_COMPILED_EXEC_TEST(Run_JSSelectAlign_2) {
CcTest::InitializeVM();
RunJSSelectAlignTest(execution_tier, 2, 3);
RunJSSelectAlignTest(execution_tier, 2, 4);
}
WASM_COMPILED_EXEC_TEST(Run_JSSelectAlign_3) {
CcTest::InitializeVM();
RunJSSelectAlignTest(execution_tier, 3, 3);
RunJSSelectAlignTest(execution_tier, 3, 4);
}
WASM_COMPILED_EXEC_TEST(Run_JSSelectAlign_4) {
CcTest::InitializeVM();
RunJSSelectAlignTest(execution_tier, 4, 3);
RunJSSelectAlignTest(execution_tier, 4, 4);
}
WASM_COMPILED_EXEC_TEST(Run_JSSelectAlign_7) {
CcTest::InitializeVM();
RunJSSelectAlignTest(execution_tier, 7, 3);
RunJSSelectAlignTest(execution_tier, 7, 4);
RunJSSelectAlignTest(execution_tier, 7, 4);
RunJSSelectAlignTest(execution_tier, 7, 4);
}
WASM_COMPILED_EXEC_TEST(Run_JSSelectAlign_8) {
CcTest::InitializeVM();
RunJSSelectAlignTest(execution_tier, 8, 5);
RunJSSelectAlignTest(execution_tier, 8, 6);
RunJSSelectAlignTest(execution_tier, 8, 7);
RunJSSelectAlignTest(execution_tier, 8, 8);
}
WASM_COMPILED_EXEC_TEST(Run_JSSelectAlign_9) {
CcTest::InitializeVM();
RunJSSelectAlignTest(execution_tier, 9, 6);
RunJSSelectAlignTest(execution_tier, 9, 7);
RunJSSelectAlignTest(execution_tier, 9, 8);
RunJSSelectAlignTest(execution_tier, 9, 9);
}
WASM_COMPILED_EXEC_TEST(Run_JSSelectAlign_10) {
CcTest::InitializeVM();
RunJSSelectAlignTest(execution_tier, 10, 7);
RunJSSelectAlignTest(execution_tier, 10, 8);
RunJSSelectAlignTest(execution_tier, 10, 9);
RunJSSelectAlignTest(execution_tier, 10, 10);
}
// Set up a test with an import, so we can return call it.
// Create a javascript function that returns left or right arguments
// depending on the value of the third argument
// function (a,b,c){ if(c)return a; return b; }
void RunPickerTest(TestExecutionTier tier, bool indirect) {
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
TestSignatures sigs;
const char* source = "(function(a,b,c) { if(c)return a; return b; })";
DirectHandle<JSFunction> js_function =
Cast<JSFunction>(v8::Utils::OpenDirectHandle(
*v8::Local<v8::Function>::Cast(CompileRun(source))));
ManuallyImportedJSFunction import = {sigs.i_iii(), js_function};
WasmRunner<int32_t, int32_t> r(tier, kWasmOrigin, &import);
const uint32_t js_index = 0;
const int32_t left = -2;
const int32_t right = 3;
WasmFunctionCompiler& rc_fn = r.NewFunction(sigs.i_i(), "rc");
if (indirect) {
ModuleTypeIndex sig_index = r.builder().AddSignature(sigs.i_iii());
uint16_t indirect_function_table[] = {static_cast<uint16_t>(js_index)};
r.builder().AddIndirectFunctionTable(indirect_function_table,
arraysize(indirect_function_table));
rc_fn.Build(
{WASM_RETURN_CALL_INDIRECT(sig_index, WASM_I32V(left), WASM_I32V(right),
WASM_LOCAL_GET(0), WASM_I32V(js_index))});
} else {
rc_fn.Build({WASM_RETURN_CALL_FUNCTION(
js_index, WASM_I32V(left), WASM_I32V(right), WASM_LOCAL_GET(0))});
}
DirectHandle<Object> args_left[] = {isolate->factory()->NewNumber(1)};
r.CheckCallApplyViaJS(left, rc_fn.function_index(),
base::VectorOf(args_left));
DirectHandle<Object> args_right[] = {isolate->factory()->NewNumber(0)};
r.CheckCallApplyViaJS(right, rc_fn.function_index(),
base::VectorOf(args_right));
}
WASM_COMPILED_EXEC_TEST(Run_ReturnCallImportedFunction) {
RunPickerTest(execution_tier, false);
}
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,116 @@
// Copyright 2020 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 "src/wasm/wasm-opcodes-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
#include "test/common/wasm/wasm-module-runner.h"
namespace v8::internal::wasm {
template <typename ReturnType, typename... ParamTypes>
class Memory64Runner : public WasmRunner<ReturnType, ParamTypes...> {
public:
explicit Memory64Runner(TestExecutionTier execution_tier)
: WasmRunner<ReturnType, ParamTypes...>(execution_tier, kWasmOrigin,
nullptr, "main") {}
template <typename T>
T* AddMemoryElems(uint32_t count) {
return this->builder().template AddMemoryElems<T>(count, AddressType::kI64);
}
uint8_t* AddMemory(uint32_t size, size_t max_size,
SharedFlag shared = SharedFlag::kNotShared) {
return this->builder().AddMemory(size, shared, AddressType::kI64, max_size);
}
};
WASM_EXEC_TEST(Load) {
Memory64Runner<uint32_t, uint64_t> r(execution_tier);
uint32_t* memory =
r.AddMemoryElems<uint32_t>(kWasmPageSize / sizeof(int32_t));
r.Build({WASM_LOAD_MEM(MachineType::Int32(), WASM_LOCAL_GET(0))});
CHECK_EQ(0, r.Call(0));
#if V8_TARGET_BIG_ENDIAN
memory[0] = 0x78563412;
#else
memory[0] = 0x12345678;
#endif
CHECK_EQ(0x12345678, r.Call(0));
CHECK_EQ(0x123456, r.Call(1));
CHECK_EQ(0x1234, r.Call(2));
CHECK_EQ(0x12, r.Call(3));
CHECK_EQ(0x0, r.Call(4));
CHECK_TRAP(r.Call(-1));
CHECK_TRAP(r.Call(kWasmPageSize));
CHECK_TRAP(r.Call(kWasmPageSize - 3));
CHECK_EQ(0x0, r.Call(kWasmPageSize - 4));
CHECK_TRAP(r.Call(uint64_t{1} << 32));
}
// TODO(clemensb): Test atomic instructions.
WASM_EXEC_TEST(InitExpression) {
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
ErrorThrower thrower(isolate, "TestMemory64InitExpression");
const uint8_t data[] = {
WASM_MODULE_HEADER, //
SECTION(Memory, //
ENTRY_COUNT(1), //
kMemory64WithMaximum, // type
1, // initial size
2), // maximum size
SECTION(Data, //
ENTRY_COUNT(1), //
0, // linear memory index
WASM_I64V_3(0xFFFF), kExprEnd, // destination offset
U32V_1(1), // source size
'c') // data bytes
};
testing::CompileAndInstantiateForTesting(isolate, &thrower,
base::VectorOf(data));
if (thrower.error()) {
Print(*thrower.Reify());
FATAL("compile or instantiate error");
}
}
WASM_EXEC_TEST(MemorySize) {
Memory64Runner<uint64_t> r(execution_tier);
constexpr int kNumPages = 13;
r.AddMemoryElems<uint8_t>(kNumPages * kWasmPageSize);
r.Build({WASM_MEMORY_SIZE});
CHECK_EQ(kNumPages, r.Call());
}
WASM_EXEC_TEST(MemoryGrow) {
Memory64Runner<int64_t, int64_t> r(execution_tier);
r.AddMemory(kWasmPageSize, 13 * kWasmPageSize);
r.Build({WASM_MEMORY_GROW(WASM_LOCAL_GET(0))});
CHECK_EQ(1, r.Call(6));
CHECK_EQ(7, r.Call(1));
CHECK_EQ(-1, r.Call(-1));
CHECK_EQ(-1, r.Call(int64_t{1} << 31));
CHECK_EQ(-1, r.Call(int64_t{1} << 32));
CHECK_EQ(-1, r.Call(int64_t{1} << 33));
CHECK_EQ(-1, r.Call(int64_t{1} << 63));
CHECK_EQ(-1, r.Call(6)); // Above the maximum of 13.
CHECK_EQ(8, r.Call(5)); // Just at the maximum of 13.
}
} // namespace v8::internal::wasm

View File

@ -0,0 +1,922 @@
// Copyright 2015 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 <stdlib.h>
#include <string.h>
#include <atomic>
#include "src/api/api-inl.h"
#include "src/objects/objects-inl.h"
#include "src/snapshot/code-serializer.h"
#include "src/utils/version.h"
#include "src/wasm/module-decoder.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-module-builder.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-opcodes.h"
#include "test/cctest/cctest.h"
#include "test/common/wasm/flag-utils.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
#include "test/common/wasm/wasm-module-runner.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace test_run_wasm_module {
using base::ReadLittleEndianValue;
using base::WriteLittleEndianValue;
using testing::CompileAndInstantiateForTesting;
namespace {
void Cleanup(Isolate* isolate = CcTest::InitIsolateOnce()) {
// By sending a low memory notifications, we will try hard to collect all
// garbage and will therefore also invoke all weak callbacks of actually
// unreachable persistent handles.
reinterpret_cast<v8::Isolate*>(isolate)->LowMemoryNotification();
}
void TestModule(Zone* zone, WasmModuleBuilder* builder,
int32_t expected_result) {
ZoneBuffer buffer(zone);
builder->WriteTo(&buffer);
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
testing::SetupIsolateForWasmModule(isolate);
int32_t result =
testing::CompileAndRunWasmModule(isolate, buffer.begin(), buffer.end());
CHECK_EQ(expected_result, result);
}
void TestModuleException(Zone* zone, WasmModuleBuilder* builder) {
ZoneBuffer buffer(zone);
builder->WriteTo(&buffer);
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
testing::SetupIsolateForWasmModule(isolate);
v8::TryCatch try_catch(reinterpret_cast<v8::Isolate*>(isolate));
testing::CompileAndRunWasmModule(isolate, buffer.begin(), buffer.end());
CHECK(try_catch.HasCaught());
isolate->clear_exception();
}
void ExportAsMain(WasmFunctionBuilder* f) {
f->builder()->AddExport(base::CStrVector("main"), f);
}
#define EMIT_CODE_WITH_END(f, code) \
do { \
f->EmitCode(code, sizeof(code)); \
f->Emit(kExprEnd); \
} while (false)
} // namespace
TEST(Run_WasmModule_Return114) {
{
static const int32_t kReturnValue = 114;
TestSignatures sigs;
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
ExportAsMain(f);
uint8_t code[] = {WASM_I32V_2(kReturnValue)};
EMIT_CODE_WITH_END(f, code);
TestModule(&zone, builder, kReturnValue);
}
Cleanup();
}
TEST(Run_WasmModule_CompilationHintsLazy) {
if (!v8_flags.wasm_tier_up || !v8_flags.liftoff) return;
{
EXPERIMENTAL_FLAG_SCOPE(compilation_hints);
static const int32_t kReturnValue = 114;
TestSignatures sigs;
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
// Build module with one lazy function.
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
ExportAsMain(f);
uint8_t code[] = {WASM_I32V_2(kReturnValue)};
EMIT_CODE_WITH_END(f, code);
f->SetCompilationHint(WasmCompilationHintStrategy::kLazy,
WasmCompilationHintTier::kBaseline,
WasmCompilationHintTier::kOptimized);
// Compile module. No function is actually compiled as the function is lazy.
ZoneBuffer buffer(&zone);
builder->WriteTo(&buffer);
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "CompileAndRunWasmModule");
MaybeDirectHandle<WasmModuleObject> module =
testing::CompileForTesting(isolate, &thrower, base::VectorOf(buffer));
CHECK(!module.is_null());
// Lazy function was not invoked and therefore not compiled yet.
static const int kFuncIndex = 0;
NativeModule* native_module = module.ToHandleChecked()->native_module();
CHECK(!native_module->HasCode(kFuncIndex));
auto* compilation_state = native_module->compilation_state();
CHECK(compilation_state->baseline_compilation_finished());
// Instantiate and invoke function.
MaybeDirectHandle<WasmInstanceObject> instance =
GetWasmEngine()->SyncInstantiate(isolate, &thrower,
module.ToHandleChecked(), {}, {});
CHECK(!instance.is_null());
int32_t result = testing::CallWasmFunctionForTesting(
isolate, instance.ToHandleChecked(), "main", {});
CHECK_EQ(kReturnValue, result);
// Lazy function was invoked and therefore compiled.
CHECK(native_module->HasCode(kFuncIndex));
WasmCodeRefScope code_ref_scope;
ExecutionTier actual_tier = native_module->GetCode(kFuncIndex)->tier();
static_assert(ExecutionTier::kLiftoff < ExecutionTier::kTurbofan,
"Assume an order on execution tiers");
ExecutionTier baseline_tier = ExecutionTier::kLiftoff;
CHECK_LE(baseline_tier, actual_tier);
CHECK(compilation_state->baseline_compilation_finished());
}
Cleanup();
}
TEST(Run_WasmModule_CompilationHintsNoTiering) {
FlagScope<bool> no_lazy_compilation(&v8_flags.wasm_lazy_compilation, false);
if (!v8_flags.wasm_tier_up || !v8_flags.liftoff) return;
{
EXPERIMENTAL_FLAG_SCOPE(compilation_hints);
static const int32_t kReturnValue = 114;
TestSignatures sigs;
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
// Build module with regularly compiled function (no tiering).
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
ExportAsMain(f);
uint8_t code[] = {WASM_I32V_2(kReturnValue)};
EMIT_CODE_WITH_END(f, code);
f->SetCompilationHint(WasmCompilationHintStrategy::kEager,
WasmCompilationHintTier::kBaseline,
WasmCompilationHintTier::kBaseline);
// Compile module.
ZoneBuffer buffer(&zone);
builder->WriteTo(&buffer);
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "CompileAndRunWasmModule");
MaybeDirectHandle<WasmModuleObject> module =
testing::CompileForTesting(isolate, &thrower, base::VectorOf(buffer));
CHECK(!module.is_null());
// Synchronous compilation finished and no tiering units were initialized.
static const int kFuncIndex = 0;
NativeModule* native_module = module.ToHandleChecked()->native_module();
CHECK(native_module->HasCode(kFuncIndex));
ExecutionTier expected_tier = ExecutionTier::kLiftoff;
WasmCodeRefScope code_ref_scope;
ExecutionTier actual_tier = native_module->GetCode(kFuncIndex)->tier();
CHECK_EQ(expected_tier, actual_tier);
auto* compilation_state = native_module->compilation_state();
CHECK(compilation_state->baseline_compilation_finished());
}
Cleanup();
}
TEST(Run_WasmModule_CompilationHintsTierUp) {
FlagScope<bool> no_wasm_dynamic_tiering(&v8_flags.wasm_dynamic_tiering,
false);
FlagScope<bool> no_lazy_compilation(&v8_flags.wasm_lazy_compilation, false);
if (!v8_flags.wasm_tier_up || !v8_flags.liftoff) return;
{
EXPERIMENTAL_FLAG_SCOPE(compilation_hints);
static const int32_t kReturnValue = 114;
TestSignatures sigs;
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
// Build module with tiering compilation hint.
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
ExportAsMain(f);
uint8_t code[] = {WASM_I32V_2(kReturnValue)};
EMIT_CODE_WITH_END(f, code);
f->SetCompilationHint(WasmCompilationHintStrategy::kEager,
WasmCompilationHintTier::kBaseline,
WasmCompilationHintTier::kOptimized);
// Compile module.
ZoneBuffer buffer(&zone);
builder->WriteTo(&buffer);
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "CompileAndRunWasmModule");
MaybeDirectHandle<WasmModuleObject> module =
testing::CompileForTesting(isolate, &thrower, base::VectorOf(buffer));
CHECK(!module.is_null());
// Expect baseline or top tier code.
static const int kFuncIndex = 0;
NativeModule* native_module = module.ToHandleChecked()->native_module();
auto* compilation_state = native_module->compilation_state();
static_assert(ExecutionTier::kLiftoff < ExecutionTier::kTurbofan,
"Assume an order on execution tiers");
ExecutionTier baseline_tier = ExecutionTier::kLiftoff;
{
CHECK(native_module->HasCode(kFuncIndex));
WasmCodeRefScope code_ref_scope;
ExecutionTier actual_tier = native_module->GetCode(kFuncIndex)->tier();
CHECK_LE(baseline_tier, actual_tier);
CHECK(compilation_state->baseline_compilation_finished());
}
// Tier-up is happening in the background. Eventually we should have top
// tier code.
ExecutionTier top_tier = ExecutionTier::kTurbofan;
ExecutionTier actual_tier = ExecutionTier::kNone;
while (actual_tier != top_tier) {
CHECK(native_module->HasCode(kFuncIndex));
WasmCodeRefScope code_ref_scope;
actual_tier = native_module->GetCode(kFuncIndex)->tier();
}
}
Cleanup();
}
TEST(Run_WasmModule_CompilationHintsLazyBaselineEagerTopTier) {
FlagScope<bool> no_wasm_dynamic_tiering(&v8_flags.wasm_dynamic_tiering,
false);
FlagScope<bool> no_lazy_compilation(&v8_flags.wasm_lazy_compilation, false);
if (!v8_flags.wasm_tier_up || !v8_flags.liftoff) return;
{
EXPERIMENTAL_FLAG_SCOPE(compilation_hints);
static const int32_t kReturnValue = 114;
TestSignatures sigs;
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
// Build module with tiering compilation hint.
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
ExportAsMain(f);
uint8_t code[] = {WASM_I32V_2(kReturnValue)};
EMIT_CODE_WITH_END(f, code);
f->SetCompilationHint(
WasmCompilationHintStrategy::kLazyBaselineEagerTopTier,
WasmCompilationHintTier::kBaseline,
WasmCompilationHintTier::kOptimized);
// Compile module.
ZoneBuffer buffer(&zone);
builder->WriteTo(&buffer);
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "CompileAndRunWasmModule");
MaybeDirectHandle<WasmModuleObject> module =
testing::CompileForTesting(isolate, &thrower, base::VectorOf(buffer));
CHECK(!module.is_null());
NativeModule* native_module = module.ToHandleChecked()->native_module();
auto* compilation_state = native_module->compilation_state();
// We have no code initially (because of lazy baseline), but eventually we
// should have TurboFan ready (because of eager top tier).
static_assert(ExecutionTier::kLiftoff < ExecutionTier::kTurbofan,
"Assume an order on execution tiers");
constexpr int kFuncIndex = 0;
WasmCodeRefScope code_ref_scope;
while (true) {
auto* function_code = native_module->GetCode(kFuncIndex);
if (!function_code) continue;
CHECK_EQ(ExecutionTier::kTurbofan, function_code->tier());
break;
}
CHECK(compilation_state->baseline_compilation_finished());
}
Cleanup();
}
TEST(Run_WasmModule_CallAdd) {
{
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
TestSignatures sigs;
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
WasmFunctionBuilder* f1 = builder->AddFunction(sigs.i_ii());
uint16_t param1 = 0;
uint16_t param2 = 1;
uint8_t code1[] = {
WASM_I32_ADD(WASM_LOCAL_GET(param1), WASM_LOCAL_GET(param2))};
EMIT_CODE_WITH_END(f1, code1);
WasmFunctionBuilder* f2 = builder->AddFunction(sigs.i_v());
ExportAsMain(f2);
uint8_t code2[] = {
WASM_CALL_FUNCTION(f1->func_index(), WASM_I32V_2(77), WASM_I32V_1(22))};
EMIT_CODE_WITH_END(f2, code2);
TestModule(&zone, builder, 99);
}
Cleanup();
}
TEST(Run_WasmModule_ReadLoadedDataSegment) {
{
static const uint8_t kDataSegmentDest0 = 12;
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
TestSignatures sigs;
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
builder->AddMemory(16);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
ExportAsMain(f);
uint8_t code[] = {
WASM_LOAD_MEM(MachineType::Int32(), WASM_I32V_1(kDataSegmentDest0))};
EMIT_CODE_WITH_END(f, code);
uint8_t data[] = {0xAA, 0xBB, 0xCC, 0xDD};
builder->AddDataSegment(data, sizeof(data), kDataSegmentDest0);
TestModule(&zone, builder, 0xDDCCBBAA);
}
Cleanup();
}
TEST(Run_WasmModule_CheckMemoryIsZero) {
{
static const int kCheckSize = 16 * 1024;
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
TestSignatures sigs;
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
builder->AddMemory(16);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
uint16_t localIndex = f->AddLocal(kWasmI32);
ExportAsMain(f);
uint8_t code[] = {WASM_BLOCK_I(
WASM_WHILE(
WASM_I32_LTS(WASM_LOCAL_GET(localIndex), WASM_I32V_3(kCheckSize)),
WASM_IF_ELSE(
WASM_LOAD_MEM(MachineType::Int32(), WASM_LOCAL_GET(localIndex)),
WASM_BRV(3, WASM_I32V_1(-1)),
WASM_INC_LOCAL_BY(localIndex, 4))),
WASM_I32V_1(11))};
EMIT_CODE_WITH_END(f, code);
TestModule(&zone, builder, 11);
}
Cleanup();
}
TEST(Run_WasmModule_CallMain_recursive) {
{
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
TestSignatures sigs;
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
builder->AddMemory(16);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
uint16_t localIndex = f->AddLocal(kWasmI32);
ExportAsMain(f);
uint8_t code[] = {
WASM_LOCAL_SET(localIndex,
WASM_LOAD_MEM(MachineType::Int32(), WASM_ZERO)),
WASM_IF_ELSE_I(WASM_I32_LTS(WASM_LOCAL_GET(localIndex), WASM_I32V_1(5)),
WASM_SEQ(WASM_STORE_MEM(MachineType::Int32(), WASM_ZERO,
WASM_INC_LOCAL(localIndex)),
WASM_CALL_FUNCTION0(0)),
WASM_I32V_1(55))};
EMIT_CODE_WITH_END(f, code);
TestModule(&zone, builder, 55);
}
Cleanup();
}
TEST(Run_WasmModule_Global) {
{
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
TestSignatures sigs;
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
uint32_t global1 = builder->AddGlobal(kWasmI32, true, WasmInitExpr(0));
uint32_t global2 = builder->AddGlobal(kWasmI32, true, WasmInitExpr(0));
WasmFunctionBuilder* f1 = builder->AddFunction(sigs.i_v());
uint8_t code1[] = {
WASM_I32_ADD(WASM_GLOBAL_GET(global1), WASM_GLOBAL_GET(global2))};
EMIT_CODE_WITH_END(f1, code1);
WasmFunctionBuilder* f2 = builder->AddFunction(sigs.i_v());
ExportAsMain(f2);
uint8_t code2[] = {WASM_GLOBAL_SET(global1, WASM_I32V_1(56)),
WASM_GLOBAL_SET(global2, WASM_I32V_1(41)),
WASM_RETURN(WASM_CALL_FUNCTION0(f1->func_index()))};
EMIT_CODE_WITH_END(f2, code2);
TestModule(&zone, builder, 97);
}
Cleanup();
}
TEST(MemorySize) {
{
// Initial memory size is 16, see wasm-module-builder.cc
static const int kExpectedValue = 16;
TestSignatures sigs;
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
builder->AddMemory(16);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
ExportAsMain(f);
uint8_t code[] = {WASM_MEMORY_SIZE};
EMIT_CODE_WITH_END(f, code);
TestModule(&zone, builder, kExpectedValue);
}
Cleanup();
}
TEST(Run_WasmModule_MemSize_GrowMem) {
{
// Initial memory size = 16 + MemoryGrow(10)
static const int kExpectedValue = 26;
TestSignatures sigs;
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
builder->AddMemory(16);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
ExportAsMain(f);
uint8_t code[] = {WASM_MEMORY_GROW(WASM_I32V_1(10)), WASM_DROP,
WASM_MEMORY_SIZE};
EMIT_CODE_WITH_END(f, code);
TestModule(&zone, builder, kExpectedValue);
}
Cleanup();
}
TEST(MemoryGrowZero) {
{
// Initial memory size is 16, see wasm-module-builder.cc
static const int kExpectedValue = 16;
TestSignatures sigs;
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
builder->AddMemory(16);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
ExportAsMain(f);
uint8_t code[] = {WASM_MEMORY_GROW(WASM_I32V(0))};
EMIT_CODE_WITH_END(f, code);
TestModule(&zone, builder, kExpectedValue);
}
Cleanup();
}
class InterruptThread : public v8::base::Thread {
public:
explicit InterruptThread(Isolate* isolate, std::atomic<int32_t>* memory)
: Thread(Options("TestInterruptLoop")),
isolate_(isolate),
memory_(memory) {}
static void OnInterrupt(v8::Isolate* isolate, void* data) {
int32_t* m = reinterpret_cast<int32_t*>(data);
// Set the interrupt location to 0 to break the loop in {TestInterruptLoop}.
Address ptr = reinterpret_cast<Address>(&m[interrupt_location_]);
WriteLittleEndianValue<int32_t>(ptr, interrupt_value_);
}
void Run() override {
// Wait for the main thread to write the signal value.
int32_t val = 0;
do {
val = memory_[0].load(std::memory_order_relaxed);
val = ReadLittleEndianValue<int32_t>(reinterpret_cast<Address>(&val));
} while (val != signal_value_);
isolate_->RequestInterrupt(&OnInterrupt, memory_);
}
Isolate* isolate_;
std::atomic<int32_t>* memory_;
static const int32_t interrupt_location_ = 10;
static const int32_t interrupt_value_ = 154;
static const int32_t signal_value_ = 1221;
};
TEST(TestInterruptLoop) {
{
// Do not dump the module of this test because it contains an infinite loop.
if (v8_flags.dump_wasm_module) return;
// This test tests that WebAssembly loops can be interrupted, i.e. that if
// an
// InterruptCallback is registered by {Isolate::RequestInterrupt}, then the
// InterruptCallback is eventually called even if a loop in WebAssembly code
// is executed.
// Test setup:
// The main thread executes a WebAssembly function with a loop. In the loop
// {signal_value_} is written to memory to signal a helper thread that the
// main thread reached the loop in the WebAssembly program. When the helper
// thread reads {signal_value_} from memory, it registers the
// InterruptCallback. Upon exeution, the InterruptCallback write into the
// WebAssemblyMemory to end the loop in the WebAssembly program.
TestSignatures sigs;
Isolate* isolate = CcTest::InitIsolateOnce();
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
builder->AddMemory(16);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
ExportAsMain(f);
uint8_t code[] = {
WASM_LOOP(
WASM_IF(WASM_NOT(WASM_LOAD_MEM(
MachineType::Int32(),
WASM_I32V(InterruptThread::interrupt_location_ * 4))),
WASM_STORE_MEM(MachineType::Int32(), WASM_ZERO,
WASM_I32V(InterruptThread::signal_value_)),
WASM_BR(1))),
WASM_I32V(121)};
EMIT_CODE_WITH_END(f, code);
ZoneBuffer buffer(&zone);
builder->WriteTo(&buffer);
HandleScope scope(isolate);
testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "Test");
const DirectHandle<WasmInstanceObject> instance =
CompileAndInstantiateForTesting(isolate, &thrower,
base::VectorOf(buffer))
.ToHandleChecked();
DirectHandle<JSArrayBuffer> memory(
instance->trusted_data(isolate)->memory_object(0)->array_buffer(),
isolate);
std::atomic<int32_t>* memory_array =
reinterpret_cast<std::atomic<int32_t>*>(memory->backing_store());
InterruptThread thread(isolate, memory_array);
CHECK(thread.Start());
testing::CallWasmFunctionForTesting(isolate, instance, "main", {});
Address address = reinterpret_cast<Address>(
&memory_array[InterruptThread::interrupt_location_]);
CHECK_EQ(InterruptThread::interrupt_value_,
ReadLittleEndianValue<int32_t>(address));
}
Cleanup();
}
TEST(Run_WasmModule_MemoryGrowInIf) {
{
TestSignatures sigs;
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
builder->AddMemory(16);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
ExportAsMain(f);
uint8_t code[] = {WASM_IF_ELSE_I(
WASM_I32V(0), WASM_MEMORY_GROW(WASM_I32V(1)), WASM_I32V(12))};
EMIT_CODE_WITH_END(f, code);
TestModule(&zone, builder, 12);
}
Cleanup();
}
TEST(Run_WasmModule_GrowMemOobOffset) {
{
static const int kPageSize = 0x10000;
// Initial memory size = 16 + MemoryGrow(10)
static const int index = kPageSize * 17 + 4;
int value = 0xACED;
TestSignatures sigs;
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
ExportAsMain(f);
uint8_t code[] = {WASM_MEMORY_GROW(WASM_I32V_1(1)),
WASM_STORE_MEM(MachineType::Int32(), WASM_I32V(index),
WASM_I32V(value))};
EMIT_CODE_WITH_END(f, code);
TestModuleException(&zone, builder);
}
Cleanup();
}
TEST(Run_WasmModule_GrowMemOobFixedIndex) {
{
static const int kPageSize = 0x10000;
// Initial memory size = 16 + MemoryGrow(10)
static const int index = kPageSize * 26 + 4;
int value = 0xACED;
TestSignatures sigs;
Isolate* isolate = CcTest::InitIsolateOnce();
Zone zone(isolate->allocator(), ZONE_NAME);
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
builder->AddMemory(16);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_i());
ExportAsMain(f);
uint8_t code[] = {WASM_MEMORY_GROW(WASM_LOCAL_GET(0)), WASM_DROP,
WASM_STORE_MEM(MachineType::Int32(), WASM_I32V(index),
WASM_I32V(value)),
WASM_LOAD_MEM(MachineType::Int32(), WASM_I32V(index))};
EMIT_CODE_WITH_END(f, code);
HandleScope scope(isolate);
ZoneBuffer buffer(&zone);
builder->WriteTo(&buffer);
testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "Test");
DirectHandle<WasmInstanceObject> instance =
CompileAndInstantiateForTesting(isolate, &thrower,
base::VectorOf(buffer))
.ToHandleChecked();
// Initial memory size is 16 pages, should trap till index > MemSize on
// consecutive GrowMem calls
for (uint32_t i = 1; i < 5; i++) {
DirectHandle<Object> params[1] = {
direct_handle(Smi::FromInt(i), isolate)};
v8::TryCatch try_catch(reinterpret_cast<v8::Isolate*>(isolate));
testing::CallWasmFunctionForTesting(isolate, instance, "main",
base::ArrayVector(params));
CHECK(try_catch.HasCaught());
isolate->clear_exception();
}
DirectHandle<Object> params[1] = {direct_handle(Smi::FromInt(1), isolate)};
int32_t result = testing::CallWasmFunctionForTesting(
isolate, instance, "main", base::ArrayVector(params));
CHECK_EQ(0xACED, result);
}
Cleanup();
}
TEST(Run_WasmModule_GrowMemOobVariableIndex) {
{
static const int kPageSize = 0x10000;
int value = 0xACED;
TestSignatures sigs;
Isolate* isolate = CcTest::InitIsolateOnce();
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
builder->AddMemory(16);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_i());
ExportAsMain(f);
uint8_t code[] = {WASM_MEMORY_GROW(WASM_I32V_1(1)), WASM_DROP,
WASM_STORE_MEM(MachineType::Int32(), WASM_LOCAL_GET(0),
WASM_I32V(value)),
WASM_LOAD_MEM(MachineType::Int32(), WASM_LOCAL_GET(0))};
EMIT_CODE_WITH_END(f, code);
HandleScope scope(isolate);
ZoneBuffer buffer(&zone);
builder->WriteTo(&buffer);
testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "Test");
DirectHandle<WasmInstanceObject> instance =
CompileAndInstantiateForTesting(isolate, &thrower,
base::VectorOf(buffer))
.ToHandleChecked();
// Initial memory size is 16 pages, should trap till index > MemSize on
// consecutive GrowMem calls
for (int i = 1; i < 5; i++) {
DirectHandle<Object> params[] = {
direct_handle(Smi::FromInt((16 + i) * kPageSize - 3), isolate)};
v8::TryCatch try_catch(reinterpret_cast<v8::Isolate*>(isolate));
testing::CallWasmFunctionForTesting(isolate, instance, "main",
base::ArrayVector(params));
CHECK(try_catch.HasCaught());
isolate->clear_exception();
}
for (int i = 1; i < 5; i++) {
DirectHandle<Object> params[] = {
direct_handle(Smi::FromInt((20 + i) * kPageSize - 4), isolate)};
int32_t result = testing::CallWasmFunctionForTesting(
isolate, instance, "main", base::ArrayVector(params));
CHECK_EQ(0xACED, result);
}
v8::TryCatch try_catch(reinterpret_cast<v8::Isolate*>(isolate));
DirectHandle<Object> params[] = {
direct_handle(Smi::FromInt(25 * kPageSize), isolate)};
testing::CallWasmFunctionForTesting(isolate, instance, "main",
base::ArrayVector(params));
CHECK(try_catch.HasCaught());
isolate->clear_exception();
}
Cleanup();
}
TEST(Run_WasmModule_Global_init) {
{
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
TestSignatures sigs;
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
uint32_t global1 =
builder->AddGlobal(kWasmI32, false, WasmInitExpr(777777));
uint32_t global2 =
builder->AddGlobal(kWasmI32, false, WasmInitExpr(222222));
WasmFunctionBuilder* f1 = builder->AddFunction(sigs.i_v());
uint8_t code[] = {
WASM_I32_ADD(WASM_GLOBAL_GET(global1), WASM_GLOBAL_GET(global2))};
EMIT_CODE_WITH_END(f1, code);
ExportAsMain(f1);
TestModule(&zone, builder, 999999);
}
Cleanup();
}
template <typename CType>
static void RunWasmModuleGlobalInitTest(ValueType type, CType expected) {
{
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
ValueType types[] = {type};
FunctionSig sig(1, 0, types);
for (int padding = 0; padding < 5; padding++) {
// Test with a simple initializer
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
for (int i = 0; i < padding; i++) { // pad global before
builder->AddGlobal(kWasmI32, false, WasmInitExpr(i + 20000));
}
uint32_t global = builder->AddGlobal(type, false, WasmInitExpr(expected));
for (int i = 0; i < padding; i++) { // pad global after
builder->AddGlobal(kWasmI32, false, WasmInitExpr(i + 30000));
}
WasmFunctionBuilder* f1 = builder->AddFunction(&sig);
uint8_t code[] = {WASM_GLOBAL_GET(global)};
EMIT_CODE_WITH_END(f1, code);
ExportAsMain(f1);
TestModule(&zone, builder, expected);
}
}
Cleanup();
}
TEST(Run_WasmModule_Global_i32) {
RunWasmModuleGlobalInitTest<int32_t>(kWasmI32, -983489);
RunWasmModuleGlobalInitTest<int32_t>(kWasmI32, 11223344);
}
TEST(Run_WasmModule_Global_f32) {
RunWasmModuleGlobalInitTest<float>(kWasmF32, -983.9f);
RunWasmModuleGlobalInitTest<float>(kWasmF32, 1122.99f);
}
TEST(Run_WasmModule_Global_f64) {
RunWasmModuleGlobalInitTest<double>(kWasmF64, -833.9);
RunWasmModuleGlobalInitTest<double>(kWasmF64, 86374.25);
}
TEST(InitDataAtTheUpperLimit) {
{
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "Run_WasmModule_InitDataAtTheUpperLimit");
const uint8_t data[] = {
WASM_MODULE_HEADER, // --
kMemorySectionCode, // --
U32V_1(4), // section size
ENTRY_COUNT(1), // --
kWithMaximum, // --
1, // initial size
2, // maximum size
kDataSectionCode, // --
U32V_1(9), // section size
ENTRY_COUNT(1), // --
0, // linear memory index
WASM_I32V_3(0xFFFF), // destination offset
kExprEnd,
U32V_1(1), // source size
'c' // data bytes
};
CompileAndInstantiateForTesting(isolate, &thrower, base::VectorOf(data));
if (thrower.error()) {
Print(*thrower.Reify());
FATAL("compile or instantiate error");
}
}
Cleanup();
}
TEST(EmptyMemoryNonEmptyDataSegment) {
{
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "Run_WasmModule_InitDataAtTheUpperLimit");
const uint8_t data[] = {
WASM_MODULE_HEADER, // --
kMemorySectionCode, // --
U32V_1(4), // section size
ENTRY_COUNT(1), // --
kWithMaximum, // --
0, // initial size
0, // maximum size
kDataSectionCode, // --
U32V_1(7), // section size
ENTRY_COUNT(1), // --
0, // linear memory index
WASM_I32V_1(8), // destination offset
kExprEnd,
U32V_1(1), // source size
'c' // data bytes
};
CompileAndInstantiateForTesting(isolate, &thrower, base::VectorOf(data));
// It should not be possible to instantiate this module.
CHECK(thrower.error());
}
Cleanup();
}
TEST(EmptyMemoryEmptyDataSegment) {
{
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "Run_WasmModule_InitDataAtTheUpperLimit");
const uint8_t data[] = {
WASM_MODULE_HEADER, // --
kMemorySectionCode, // --
U32V_1(4), // section size
ENTRY_COUNT(1), // --
kWithMaximum, // --
0, // initial size
0, // maximum size
kDataSectionCode, // --
U32V_1(6), // section size
ENTRY_COUNT(1), // --
0, // linear memory index
WASM_I32V_1(0), // destination offset
kExprEnd,
U32V_1(0), // source size
};
CompileAndInstantiateForTesting(isolate, &thrower, base::VectorOf(data));
// It should be possible to instantiate this module.
CHECK(!thrower.error());
}
Cleanup();
}
#undef EMIT_CODE_WITH_END
} // namespace test_run_wasm_module
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,895 @@
// Copyright 2021 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 <type_traits>
#include "src/base/overflowing-math.h"
#include "src/base/safe_conversions.h"
#include "src/codegen/cpu-features.h"
#include "src/common/globals.h"
#include "src/wasm/compilation-environment.h"
#include "test/cctest/cctest.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/cctest/wasm/wasm-simd-utils.h"
#include "test/common/wasm/flag-utils.h"
#include "test/common/wasm/wasm-macro-gen.h"
namespace v8::internal::wasm {
// Only used for qfma and qfms tests below.
// FMOperation holds the params (a, b, c) for a Multiply-Add or
// Multiply-Subtract operation, and the expected result if the operation was
// fused, rounded only once for the entire operation, or unfused, rounded after
// multiply and again after add/subtract.
template <typename T>
struct FMOperation {
const T a;
const T b;
const T c;
const T fused_result;
const T unfused_result;
};
// large_n is large number that overflows T when multiplied by itself, this is a
// useful constant to test fused/unfused behavior.
template <typename T>
constexpr T large_n = T(0);
template <>
constexpr double large_n<double> = 1e200;
template <>
constexpr float large_n<float> = 1e20;
// Fused Multiply-Add performs a * b + c.
template <typename T>
static constexpr FMOperation<T> qfma_array[] = {
{2.0f, 3.0f, 1.0f, 7.0f, 7.0f},
// fused: a * b + c = (positive overflow) + -inf = -inf
// unfused: a * b + c = inf + -inf = NaN
{large_n<T>, large_n<T>, -std::numeric_limits<T>::infinity(),
-std::numeric_limits<T>::infinity(), std::numeric_limits<T>::quiet_NaN()},
// fused: a * b + c = (negative overflow) + inf = inf
// unfused: a * b + c = -inf + inf = NaN
{-large_n<T>, large_n<T>, std::numeric_limits<T>::infinity(),
std::numeric_limits<T>::infinity(), std::numeric_limits<T>::quiet_NaN()},
// NaN
{2.0f, 3.0f, std::numeric_limits<T>::quiet_NaN(),
std::numeric_limits<T>::quiet_NaN(), std::numeric_limits<T>::quiet_NaN()},
// -NaN
{2.0f, 3.0f, -std::numeric_limits<T>::quiet_NaN(),
std::numeric_limits<T>::quiet_NaN(), std::numeric_limits<T>::quiet_NaN()}};
template <typename T>
static constexpr base::Vector<const FMOperation<T>> qfma_vector() {
return base::ArrayVector(qfma_array<T>);
}
// Fused Multiply-Subtract performs -(a * b) + c.
template <typename T>
static constexpr FMOperation<T> qfms_array[]{
{2.0f, 3.0f, 1.0f, -5.0f, -5.0f},
// fused: -(a * b) + c = - (positive overflow) + inf = inf
// unfused: -(a * b) + c = - inf + inf = NaN
{large_n<T>, large_n<T>, std::numeric_limits<T>::infinity(),
std::numeric_limits<T>::infinity(), std::numeric_limits<T>::quiet_NaN()},
// fused: -(a * b) + c = (negative overflow) + -inf = -inf
// unfused: -(a * b) + c = -inf - -inf = NaN
{-large_n<T>, large_n<T>, -std::numeric_limits<T>::infinity(),
-std::numeric_limits<T>::infinity(), std::numeric_limits<T>::quiet_NaN()},
// NaN
{2.0f, 3.0f, std::numeric_limits<T>::quiet_NaN(),
std::numeric_limits<T>::quiet_NaN(), std::numeric_limits<T>::quiet_NaN()},
// -NaN
{2.0f, 3.0f, -std::numeric_limits<T>::quiet_NaN(),
std::numeric_limits<T>::quiet_NaN(), std::numeric_limits<T>::quiet_NaN()}};
template <typename T>
static constexpr base::Vector<const FMOperation<T>> qfms_vector() {
return base::ArrayVector(qfms_array<T>);
}
bool ExpectFused(TestExecutionTier tier) {
#if V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_IA32
// Fused results only when fma3 feature is enabled, and running on TurboFan or
// Liftoff (which can fall back to TurboFan if FMA is not implemented).
return CpuFeatures::IsSupported(FMA3) &&
(tier == TestExecutionTier::kTurbofan ||
tier == TestExecutionTier::kLiftoff);
#elif V8_TARGET_ARCH_ARM
// Consistent feature detection for Neonv2 is required before emitting
// fused instructions on Arm32. Not all Neon enabled Arm32 devices have
// FMA instructions.
return false;
#else
// All ARM64 Neon enabled devices have support for FMA instructions, only the
// Liftoff/Turbofan tiers emit codegen for fused results.
return (tier == TestExecutionTier::kTurbofan ||
tier == TestExecutionTier::kLiftoff);
#endif // V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_IA32
}
WASM_EXEC_TEST(F32x4Qfma) {
WasmRunner<int32_t, float, float, float> r(execution_tier);
// Set up global to hold mask output.
float* g = r.builder().AddGlobal<float>(kWasmS128);
// Build fn to splat test values, perform compare op, and write the result.
uint8_t value1 = 0, value2 = 1, value3 = 2;
r.Build(
{WASM_GLOBAL_SET(0, WASM_SIMD_F32x4_QFMA(
WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(value1)),
WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(value2)),
WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(value3)))),
WASM_ONE});
for (FMOperation<float> x : qfma_vector<float>()) {
r.Call(x.a, x.b, x.c);
float expected =
ExpectFused(execution_tier) ? x.fused_result : x.unfused_result;
for (int i = 0; i < 4; i++) {
float actual = LANE(g, i);
CheckFloatResult(x.a, x.b, expected, actual, true /* exact */);
}
}
}
WASM_EXEC_TEST(F32x4Qfms) {
WasmRunner<int32_t, float, float, float> r(execution_tier);
// Set up global to hold mask output.
float* g = r.builder().AddGlobal<float>(kWasmS128);
// Build fn to splat test values, perform compare op, and write the result.
uint8_t value1 = 0, value2 = 1, value3 = 2;
r.Build(
{WASM_GLOBAL_SET(0, WASM_SIMD_F32x4_QFMS(
WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(value1)),
WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(value2)),
WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(value3)))),
WASM_ONE});
for (FMOperation<float> x : qfms_vector<float>()) {
r.Call(x.a, x.b, x.c);
float expected =
ExpectFused(execution_tier) ? x.fused_result : x.unfused_result;
for (int i = 0; i < 4; i++) {
float actual = LANE(g, i);
CheckFloatResult(x.a, x.b, expected, actual, true /* exact */);
}
}
}
WASM_EXEC_TEST(F64x2Qfma) {
WasmRunner<int32_t, double, double, double> r(execution_tier);
// Set up global to hold mask output.
double* g = r.builder().AddGlobal<double>(kWasmS128);
// Build fn to splat test values, perform compare op, and write the result.
uint8_t value1 = 0, value2 = 1, value3 = 2;
r.Build(
{WASM_GLOBAL_SET(0, WASM_SIMD_F64x2_QFMA(
WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(value1)),
WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(value2)),
WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(value3)))),
WASM_ONE});
for (FMOperation<double> x : qfma_vector<double>()) {
r.Call(x.a, x.b, x.c);
double expected =
ExpectFused(execution_tier) ? x.fused_result : x.unfused_result;
for (int i = 0; i < 2; i++) {
double actual = LANE(g, i);
CheckDoubleResult(x.a, x.b, expected, actual, true /* exact */);
}
}
}
WASM_EXEC_TEST(F64x2Qfms) {
WasmRunner<int32_t, double, double, double> r(execution_tier);
// Set up global to hold mask output.
double* g = r.builder().AddGlobal<double>(kWasmS128);
// Build fn to splat test values, perform compare op, and write the result.
uint8_t value1 = 0, value2 = 1, value3 = 2;
r.Build(
{WASM_GLOBAL_SET(0, WASM_SIMD_F64x2_QFMS(
WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(value1)),
WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(value2)),
WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(value3)))),
WASM_ONE});
for (FMOperation<double> x : qfms_vector<double>()) {
r.Call(x.a, x.b, x.c);
double expected =
ExpectFused(execution_tier) ? x.fused_result : x.unfused_result;
for (int i = 0; i < 2; i++) {
double actual = LANE(g, i);
CheckDoubleResult(x.a, x.b, expected, actual, true /* exact */);
}
}
}
TEST(RunWasm_RegressFmaReg_liftoff) {
FLAG_SCOPE(liftoff_only);
TestExecutionTier execution_tier = TestExecutionTier::kLiftoff;
WasmRunner<int32_t, float, float, float> r(execution_tier);
uint8_t local = r.AllocateLocal(kWasmS128);
float* g = r.builder().AddGlobal<float>(kWasmS128);
uint8_t value1 = 0, value2 = 1, value3 = 2;
r.Build(
{// Get the first arg from a local so that the register is blocked even
// after the arguments have been popped off the stack. This ensures that
// the first source register is not also the destination.
WASM_LOCAL_SET(local, WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(value1))),
WASM_GLOBAL_SET(0, WASM_SIMD_F32x4_QFMA(
WASM_LOCAL_GET(local),
WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(value2)),
WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(value3)))),
WASM_ONE});
for (FMOperation<float> x : qfma_vector<float>()) {
r.Call(x.a, x.b, x.c);
float expected =
ExpectFused(execution_tier) ? x.fused_result : x.unfused_result;
for (int i = 0; i < 4; i++) {
float actual = LANE(g, i);
CheckFloatResult(x.a, x.b, expected, actual, true /* exact */);
}
}
}
namespace {
// Helper to convert an array of T into an array of uint8_t to be used a v128
// constants.
template <typename T, size_t N = kSimd128Size / sizeof(T)>
std::array<uint8_t, kSimd128Size> as_uint8(const T* src) {
std::array<uint8_t, kSimd128Size> arr;
for (size_t i = 0; i < N; i++) {
WriteLittleEndianValue<T>(reinterpret_cast<T*>(&arr[0]) + i, src[i]);
}
return arr;
}
template <typename T, int kElems>
void RelaxedLaneSelectTest(TestExecutionTier execution_tier, const T v1[kElems],
const T v2[kElems], const T s[kElems],
const T expected[kElems], WasmOpcode laneselect) {
auto lhs = as_uint8<T>(v1);
auto rhs = as_uint8<T>(v2);
auto mask = as_uint8<T>(s);
WasmRunner<int32_t> r(execution_tier);
T* dst = r.builder().AddGlobal<T>(kWasmS128);
r.Build({WASM_GLOBAL_SET(0, WASM_SIMD_OPN(laneselect, WASM_SIMD_CONSTANT(lhs),
WASM_SIMD_CONSTANT(rhs),
WASM_SIMD_CONSTANT(mask))),
WASM_ONE});
CHECK_EQ(1, r.Call());
for (int i = 0; i < kElems; i++) {
CHECK_EQ(expected[i], LANE(dst, i));
}
}
} // namespace
WASM_EXEC_TEST(I8x16RelaxedLaneSelect) {
constexpr int kElems = 16;
constexpr uint8_t v1[kElems] = {0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15};
constexpr uint8_t v2[kElems] = {16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31};
constexpr uint8_t s[kElems] = {0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF,
0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF};
constexpr uint8_t expected[kElems] = {16, 1, 18, 3, 20, 5, 22, 7,
24, 9, 26, 11, 28, 13, 30, 15};
RelaxedLaneSelectTest<uint8_t, kElems>(execution_tier, v1, v2, s, expected,
kExprI8x16RelaxedLaneSelect);
}
WASM_EXEC_TEST(I16x8RelaxedLaneSelect) {
constexpr int kElems = 8;
uint16_t v1[kElems] = {0, 1, 2, 3, 4, 5, 6, 7};
uint16_t v2[kElems] = {8, 9, 10, 11, 12, 13, 14, 15};
uint16_t s[kElems] = {0, 0xFFFF, 0, 0xFFFF, 0, 0xFFFF, 0, 0xFFFF};
constexpr uint16_t expected[kElems] = {8, 1, 10, 3, 12, 5, 14, 7};
RelaxedLaneSelectTest<uint16_t, kElems>(execution_tier, v1, v2, s, expected,
kExprI16x8RelaxedLaneSelect);
}
WASM_EXEC_TEST(I32x4RelaxedLaneSelect) {
constexpr int kElems = 4;
uint32_t v1[kElems] = {0, 1, 2, 3};
uint32_t v2[kElems] = {4, 5, 6, 7};
uint32_t s[kElems] = {0, 0xFFFF'FFFF, 0, 0xFFFF'FFFF};
constexpr uint32_t expected[kElems] = {4, 1, 6, 3};
RelaxedLaneSelectTest<uint32_t, kElems>(execution_tier, v1, v2, s, expected,
kExprI32x4RelaxedLaneSelect);
}
WASM_EXEC_TEST(I64x2RelaxedLaneSelect) {
constexpr int kElems = 2;
uint64_t v1[kElems] = {0, 1};
uint64_t v2[kElems] = {2, 3};
uint64_t s[kElems] = {0, 0xFFFF'FFFF'FFFF'FFFF};
constexpr uint64_t expected[kElems] = {2, 1};
RelaxedLaneSelectTest<uint64_t, kElems>(execution_tier, v1, v2, s, expected,
kExprI64x2RelaxedLaneSelect);
}
WASM_EXEC_TEST(F32x4RelaxedMin) {
RunF32x4BinOpTest(execution_tier, kExprF32x4RelaxedMin, Minimum);
}
WASM_EXEC_TEST(F32x4RelaxedMax) {
RunF32x4BinOpTest(execution_tier, kExprF32x4RelaxedMax, Maximum);
}
WASM_EXEC_TEST(F64x2RelaxedMin) {
RunF64x2BinOpTest(execution_tier, kExprF64x2RelaxedMin, Minimum);
}
WASM_EXEC_TEST(F64x2RelaxedMax) {
RunF64x2BinOpTest(execution_tier, kExprF64x2RelaxedMax, Maximum);
}
namespace {
// For relaxed trunc instructions, don't test out of range values.
// FloatType comes later so caller can rely on template argument deduction and
// just pass IntType.
template <typename IntType, typename FloatType>
typename std::enable_if<std::is_floating_point<FloatType>::value, bool>::type
ShouldSkipTestingConstant(FloatType x) {
return std::isnan(x) || !base::IsValueInRangeForNumericType<IntType>(x) ||
!PlatformCanRepresent(x);
}
template <typename IntType, typename FloatType>
void IntRelaxedTruncFloatTest(TestExecutionTier execution_tier,
WasmOpcode trunc_op, WasmOpcode splat_op) {
WasmRunner<int, FloatType> r(execution_tier);
IntType* g0 = r.builder().template AddGlobal<IntType>(kWasmS128);
constexpr int lanes = kSimd128Size / sizeof(FloatType);
// global[0] = trunc(splat(local[0])).
r.Build({WASM_GLOBAL_SET(
0, WASM_SIMD_UNOP(trunc_op,
WASM_SIMD_UNOP(splat_op, WASM_LOCAL_GET(0)))),
WASM_ONE});
for (FloatType x : compiler::ValueHelper::GetVector<FloatType>()) {
if (ShouldSkipTestingConstant<IntType>(x)) continue;
CHECK_EQ(1, r.Call(x));
IntType expected = base::checked_cast<IntType>(x);
for (int i = 0; i < lanes; i++) {
CHECK_EQ(expected, LANE(g0, i));
}
}
}
} // namespace
WASM_EXEC_TEST(I32x4RelaxedTruncF64x2SZero) {
IntRelaxedTruncFloatTest<int32_t, double>(
execution_tier, kExprI32x4RelaxedTruncF64x2SZero, kExprF64x2Splat);
}
WASM_EXEC_TEST(I32x4RelaxedTruncF64x2UZero) {
IntRelaxedTruncFloatTest<uint32_t, double>(
execution_tier, kExprI32x4RelaxedTruncF64x2UZero, kExprF64x2Splat);
}
WASM_EXEC_TEST(I32x4RelaxedTruncF32x4S) {
IntRelaxedTruncFloatTest<int32_t, float>(
execution_tier, kExprI32x4RelaxedTruncF32x4S, kExprF32x4Splat);
}
WASM_EXEC_TEST(I32x4RelaxedTruncF32x4U) {
IntRelaxedTruncFloatTest<uint32_t, float>(
execution_tier, kExprI32x4RelaxedTruncF32x4U, kExprF32x4Splat);
}
WASM_EXEC_TEST(I8x16RelaxedSwizzle) {
// Output is only defined for indices in the range [0,15].
WasmRunner<int32_t> r(execution_tier);
static const int kElems = kSimd128Size / sizeof(uint8_t);
uint8_t* dst = r.builder().AddGlobal<uint8_t>(kWasmS128);
uint8_t* src = r.builder().AddGlobal<uint8_t>(kWasmS128);
uint8_t* indices = r.builder().AddGlobal<uint8_t>(kWasmS128);
r.Build({WASM_GLOBAL_SET(
0, WASM_SIMD_BINOP(kExprI8x16RelaxedSwizzle, WASM_GLOBAL_GET(1),
WASM_GLOBAL_GET(2))),
WASM_ONE});
for (int i = 0; i < kElems; i++) {
LANE(src, i) = kElems - i - 1;
LANE(indices, i) = kElems - i - 1;
}
CHECK_EQ(1, r.Call());
for (int i = 0; i < kElems; i++) {
CHECK_EQ(LANE(dst, i), i);
}
}
WASM_EXEC_TEST(I16x8RelaxedQ15MulRS) {
WasmRunner<int32_t, int16_t, int16_t> r(execution_tier);
// Global to hold output.
int16_t* g = r.builder().template AddGlobal<int16_t>(kWasmS128);
// Build fn to splat test values, perform binop, and write the result.
uint8_t value1 = 0, value2 = 1;
uint8_t temp1 = r.AllocateLocal(kWasmS128);
uint8_t temp2 = r.AllocateLocal(kWasmS128);
r.Build({WASM_LOCAL_SET(temp1, WASM_SIMD_I16x8_SPLAT(WASM_LOCAL_GET(value1))),
WASM_LOCAL_SET(temp2, WASM_SIMD_I16x8_SPLAT(WASM_LOCAL_GET(value2))),
WASM_GLOBAL_SET(0, WASM_SIMD_BINOP(kExprI16x8RelaxedQ15MulRS,
WASM_LOCAL_GET(temp1),
WASM_LOCAL_GET(temp2))),
WASM_ONE});
for (int16_t x : compiler::ValueHelper::GetVector<int16_t>()) {
for (int16_t y : compiler::ValueHelper::GetVector<int16_t>()) {
// Results are dependent on the underlying hardware when both inputs are
// INT16_MIN, we could do something specific to test for x64/ARM behavior
// but predictably other supported V8 platforms will have to test specific
// behavior in that case, given that the lowering is fairly
// straighforward, and occurence of this in higher level programs is rare,
// this is okay to skip.
if (x == INT16_MIN && y == INT16_MIN) break;
r.Call(x, y);
int16_t expected = SaturateRoundingQMul(x, y);
for (int i = 0; i < 8; i++) {
CHECK_EQ(expected, LANE(g, i));
}
}
}
}
WASM_EXEC_TEST(I16x8DotI8x16I7x16S) {
WasmRunner<int32_t, int8_t, int8_t> r(execution_tier);
int16_t* g = r.builder().template AddGlobal<int16_t>(kWasmS128);
uint8_t value1 = 0, value2 = 1;
uint8_t temp1 = r.AllocateLocal(kWasmS128);
uint8_t temp2 = r.AllocateLocal(kWasmS128);
r.Build({WASM_LOCAL_SET(temp1, WASM_SIMD_I8x16_SPLAT(WASM_LOCAL_GET(value1))),
WASM_LOCAL_SET(temp2, WASM_SIMD_I8x16_SPLAT(WASM_LOCAL_GET(value2))),
WASM_GLOBAL_SET(0, WASM_SIMD_BINOP(kExprI16x8DotI8x16I7x16S,
WASM_LOCAL_GET(temp1),
WASM_LOCAL_GET(temp2))),
WASM_ONE});
for (int8_t x : compiler::ValueHelper::GetVector<int8_t>()) {
for (int8_t y : compiler::ValueHelper::GetVector<int8_t>()) {
r.Call(x, y & 0x7F);
// * 2 because we of (x*y) + (x*y) = 2*x*y
int16_t expected = base::MulWithWraparound(x * (y & 0x7F), 2);
for (int i = 0; i < 8; i++) {
CHECK_EQ(expected, LANE(g, i));
}
}
}
}
WASM_EXEC_TEST(I32x4DotI8x16I7x16AddS) {
WasmRunner<int32_t, int8_t, int8_t, int32_t> r(execution_tier);
int32_t* g = r.builder().template AddGlobal<int32_t>(kWasmS128);
uint8_t value1 = 0, value2 = 1, value3 = 2;
uint8_t temp1 = r.AllocateLocal(kWasmS128);
uint8_t temp2 = r.AllocateLocal(kWasmS128);
uint8_t temp3 = r.AllocateLocal(kWasmS128);
r.Build({WASM_LOCAL_SET(temp1, WASM_SIMD_I8x16_SPLAT(WASM_LOCAL_GET(value1))),
WASM_LOCAL_SET(temp2, WASM_SIMD_I8x16_SPLAT(WASM_LOCAL_GET(value2))),
WASM_LOCAL_SET(temp3, WASM_SIMD_I32x4_SPLAT(WASM_LOCAL_GET(value3))),
WASM_GLOBAL_SET(
0, WASM_SIMD_TERNOP(kExprI32x4DotI8x16I7x16AddS,
WASM_LOCAL_GET(temp1), WASM_LOCAL_GET(temp2),
WASM_LOCAL_GET(temp3))),
WASM_ONE});
for (int8_t x : compiler::ValueHelper::GetVector<int8_t>()) {
for (int8_t y : compiler::ValueHelper::GetVector<int8_t>()) {
for (int32_t z : compiler::ValueHelper::GetVector<int32_t>()) {
int32_t expected = base::AddWithWraparound(
base::MulWithWraparound(x * (y & 0x7F), 4), z);
r.Call(x, y & 0x7F, z);
for (int i = 0; i < 4; i++) {
CHECK_EQ(expected, LANE(g, i));
}
}
}
}
}
#ifdef V8_ENABLE_WASM_SIMD256_REVEC
TEST(RunWasm_F32x8Qfma_turbofan) {
if (!CpuFeatures::IsSupported(AVX2)) return;
EXPERIMENTAL_FLAG_SCOPE(revectorize);
WasmRunner<int32_t, float, float, float> r(TestExecutionTier::kTurbofan);
float* memory = r.builder().AddMemoryElems<float>(8);
uint8_t param1 = 0;
uint8_t param2 = 1;
uint8_t param3 = 2;
r.Build(
{WASM_SIMD_STORE_MEM(
WASM_ZERO,
WASM_SIMD_F32x4_QFMA(WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(param1)),
WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(param2)),
WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(param3)))),
WASM_SIMD_STORE_MEM_OFFSET(
16, WASM_ZERO,
WASM_SIMD_F32x4_QFMA(WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(param1)),
WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(param2)),
WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(param3)))),
WASM_ONE});
for (FMOperation<float> x : qfma_vector<float>()) {
r.Call(x.a, x.b, x.c);
float expected = ExpectFused(TestExecutionTier::kTurbofan)
? x.fused_result
: x.unfused_result;
for (int i = 0; i < 4; i++) {
float actual0 = r.builder().ReadMemory(memory + i);
float actual1 = r.builder().ReadMemory(memory + 4 + i);
CheckFloatResult(x.a, x.b, expected, actual0, true /* exact */);
CheckFloatResult(x.a, x.b, expected, actual1, true /* exact */);
}
}
}
TEST(RunWasm_F32x8Qfms_turbofan) {
if (!CpuFeatures::IsSupported(AVX2)) return;
EXPERIMENTAL_FLAG_SCOPE(revectorize);
WasmRunner<int32_t, float, float, float> r(TestExecutionTier::kTurbofan);
float* memory = r.builder().AddMemoryElems<float>(8);
uint8_t param1 = 0;
uint8_t param2 = 1;
uint8_t param3 = 2;
r.Build(
{WASM_SIMD_STORE_MEM(
WASM_ZERO,
WASM_SIMD_F32x4_QFMS(WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(param1)),
WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(param2)),
WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(param3)))),
WASM_SIMD_STORE_MEM_OFFSET(
16, WASM_ZERO,
WASM_SIMD_F32x4_QFMS(WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(param1)),
WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(param2)),
WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(param3)))),
WASM_ONE});
for (FMOperation<float> x : qfms_vector<float>()) {
r.Call(x.a, x.b, x.c);
float expected = ExpectFused(TestExecutionTier::kTurbofan)
? x.fused_result
: x.unfused_result;
for (int i = 0; i < 4; i++) {
float actual0 = r.builder().ReadMemory(memory + i);
float actual1 = r.builder().ReadMemory(memory + 4 + i);
CheckFloatResult(x.a, x.b, expected, actual0, true /* exact */);
CheckFloatResult(x.a, x.b, expected, actual1, true /* exact */);
}
}
}
TEST(RunWasm_F64x4Qfma_turbofan) {
if (!CpuFeatures::IsSupported(AVX2)) return;
EXPERIMENTAL_FLAG_SCOPE(revectorize);
WasmRunner<int32_t, double, double, double> r(TestExecutionTier::kTurbofan);
double* memory = r.builder().AddMemoryElems<double>(4);
uint8_t param1 = 0;
uint8_t param2 = 1;
uint8_t param3 = 2;
r.Build(
{WASM_SIMD_STORE_MEM(
WASM_ZERO,
WASM_SIMD_F64x2_QFMA(WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(param1)),
WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(param2)),
WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(param3)))),
WASM_SIMD_STORE_MEM_OFFSET(
16, WASM_ZERO,
WASM_SIMD_F64x2_QFMA(WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(param1)),
WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(param2)),
WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(param3)))),
WASM_ONE});
for (FMOperation<double> x : qfma_vector<double>()) {
r.Call(x.a, x.b, x.c);
double expected = ExpectFused(TestExecutionTier::kTurbofan)
? x.fused_result
: x.unfused_result;
for (int i = 0; i < 2; i++) {
double actual0 = r.builder().ReadMemory(memory + i);
double actual1 = r.builder().ReadMemory(memory + 2 + i);
CheckFloatResult(x.a, x.b, expected, actual0, true /* exact */);
CheckFloatResult(x.a, x.b, expected, actual1, true /* exact */);
}
}
}
TEST(RunWasm_F64x4Qfms_turbofan) {
if (!CpuFeatures::IsSupported(AVX2)) return;
EXPERIMENTAL_FLAG_SCOPE(revectorize);
WasmRunner<int32_t, double, double, double> r(TestExecutionTier::kTurbofan);
double* memory = r.builder().AddMemoryElems<double>(4);
uint8_t param1 = 0;
uint8_t param2 = 1;
uint8_t param3 = 2;
r.Build(
{WASM_SIMD_STORE_MEM(
WASM_ZERO,
WASM_SIMD_F64x2_QFMS(WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(param1)),
WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(param2)),
WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(param3)))),
WASM_SIMD_STORE_MEM_OFFSET(
16, WASM_ZERO,
WASM_SIMD_F64x2_QFMS(WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(param1)),
WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(param2)),
WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(param3)))),
WASM_ONE});
for (FMOperation<double> x : qfms_vector<double>()) {
r.Call(x.a, x.b, x.c);
double expected = ExpectFused(TestExecutionTier::kTurbofan)
? x.fused_result
: x.unfused_result;
for (int i = 0; i < 2; i++) {
double actual0 = r.builder().ReadMemory(memory + i);
double actual1 = r.builder().ReadMemory(memory + 2 + i);
CheckFloatResult(x.a, x.b, expected, actual0, true /* exact */);
CheckFloatResult(x.a, x.b, expected, actual1, true /* exact */);
}
}
}
template <typename T, int kElems>
void RelaxedLaneSelectRevecTest(const T l1[kElems], const T l2[kElems],
const T r1[kElems], const T r2[kElems],
const T s1[kElems], const T s2[kElems],
const T expected[2 * kElems],
WasmOpcode laneselect) {
if (!CpuFeatures::IsSupported(AVX2)) return;
EXPERIMENTAL_FLAG_SCOPE(revectorize);
const auto vector_gap = static_cast<int>(16 / sizeof(T));
WasmRunner<int32_t, int32_t, int32_t, int32_t, int32_t> r(
TestExecutionTier::kTurbofan);
T* memory = r.builder().AddMemoryElems<T>(8 * vector_gap);
uint8_t param1 = 0;
uint8_t param2 = 1;
uint8_t param3 = 2;
uint8_t param4 = 3;
uint8_t temp1 = r.AllocateLocal(kWasmS128);
uint8_t temp2 = r.AllocateLocal(kWasmS128);
constexpr uint8_t offset = 16;
r.Build(
{WASM_LOCAL_SET(
temp1,
WASM_SIMD_OPN(laneselect, WASM_SIMD_LOAD_MEM(WASM_LOCAL_GET(param1)),
WASM_SIMD_LOAD_MEM(WASM_LOCAL_GET(param2)),
WASM_SIMD_LOAD_MEM(WASM_LOCAL_GET(param3)))),
WASM_LOCAL_SET(
temp2,
WASM_SIMD_OPN(
laneselect,
WASM_SIMD_LOAD_MEM_OFFSET(offset, WASM_LOCAL_GET(param1)),
WASM_SIMD_LOAD_MEM_OFFSET(offset, WASM_LOCAL_GET(param2)),
WASM_SIMD_LOAD_MEM_OFFSET(offset, WASM_LOCAL_GET(param3)))),
WASM_SIMD_STORE_MEM(WASM_LOCAL_GET(param4), WASM_LOCAL_GET(temp1)),
WASM_SIMD_STORE_MEM_OFFSET(offset, WASM_LOCAL_GET(param4),
WASM_LOCAL_GET(temp2)),
WASM_ONE});
for (int i = 0; i < static_cast<int>(16 / sizeof(T)); i++) {
r.builder().WriteMemory(&memory[0 * vector_gap + i], l1[i]);
r.builder().WriteMemory(&memory[1 * vector_gap + i], l2[i]);
r.builder().WriteMemory(&memory[2 * vector_gap + i], r1[i]);
r.builder().WriteMemory(&memory[3 * vector_gap + i], r2[i]);
r.builder().WriteMemory(&memory[4 * vector_gap + i], s1[i]);
r.builder().WriteMemory(&memory[5 * vector_gap + i], s2[i]);
}
CHECK_EQ(1, r.Call(0, 32, 64, 96));
for (auto i = 0; i < 2 * kElems; i++) {
CHECK_EQ(expected[i], memory[6 * vector_gap + i]);
}
}
TEST(RunWasm_I64x4RelaxedLaneSelect) {
constexpr int kElems = 2;
uint64_t l1[kElems] = {0, 1};
uint64_t l2[kElems] = {2, 3};
uint64_t r1[kElems] = {4, 5};
uint64_t r2[kElems] = {6, 7};
uint64_t s1[kElems] = {0, 0xFFFF'FFFF'FFFF'FFFF};
uint64_t s2[kElems] = {0xFFFF'FFFF'FFFF'FFFF, 0};
constexpr uint64_t expected[2 * kElems] = {4, 1, 2, 7};
RelaxedLaneSelectRevecTest<uint64_t, kElems>(l1, l2, r1, r2, s1, s2, expected,
kExprI64x2RelaxedLaneSelect);
}
TEST(RunWasm_I32x8RelaxedLaneSelect) {
constexpr int kElems = 4;
uint32_t l1[kElems] = {0, 1, 2, 3};
uint32_t l2[kElems] = {8, 9, 10, 11};
uint32_t r1[kElems] = {4, 5, 6, 7};
uint32_t r2[kElems] = {12, 13, 14, 15};
uint32_t s1[kElems] = {0, 0xFFFF'FFFF, 0, 0xFFFF'FFFF};
uint32_t s2[kElems] = {0, 0xFFFF'FFFF, 0, 0xFFFF'FFFF};
constexpr uint32_t expected[2 * kElems] = {4, 1, 6, 3, 12, 9, 14, 11};
RelaxedLaneSelectRevecTest<uint32_t, kElems>(l1, l2, r1, r2, s1, s2, expected,
kExprI32x4RelaxedLaneSelect);
}
TEST(RunWasm_I16x16RelaxedLaneSelect) {
constexpr int kElems = 8;
uint16_t l1[kElems] = {0, 1, 2, 3, 4, 5, 6, 7};
uint16_t r1[kElems] = {8, 9, 10, 11, 12, 13, 14, 15};
uint16_t l2[kElems] = {16, 17, 18, 19, 20, 21, 22, 23};
uint16_t r2[kElems] = {24, 25, 26, 27, 28, 29, 30, 31};
uint16_t s1[kElems] = {0, 0xFFFF, 0, 0xFFFF, 0, 0xFFFF, 0, 0xFFFF};
uint16_t s2[kElems] = {0xFFFF, 0, 0xFFFF, 0, 0xFFFF, 0, 0xFFFF, 0};
constexpr uint16_t expected[2 * kElems] = {8, 1, 10, 3, 12, 5, 14, 7,
16, 25, 18, 27, 20, 29, 22, 31};
RelaxedLaneSelectRevecTest<uint16_t, kElems>(l1, l2, r1, r2, s1, s2, expected,
kExprI16x8RelaxedLaneSelect);
}
TEST(RunWasm_I8x32RelaxedLaneSelect) {
constexpr int kElems = 16;
uint8_t l1[kElems] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
uint8_t r1[kElems] = {16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31};
uint8_t l2[kElems] = {32, 33, 34, 35, 36, 37, 38, 39,
40, 41, 42, 43, 44, 45, 46, 47};
uint8_t r2[kElems] = {48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63};
uint8_t s1[kElems] = {0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF,
0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF};
uint8_t s2[kElems] = {0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0,
0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0};
constexpr uint8_t expected[2 * kElems] = {
16, 1, 18, 3, 20, 5, 22, 7, 24, 9, 26, 11, 28, 13, 30, 15,
32, 49, 34, 51, 36, 53, 38, 55, 40, 57, 42, 59, 44, 61, 46, 63};
RelaxedLaneSelectRevecTest<uint8_t, kElems>(l1, l2, r1, r2, s1, s2, expected,
kExprI8x16RelaxedLaneSelect);
}
TEST(RunWasm_I32x8DotI8x32I7x32AddS) {
if (!CpuFeatures::IsSupported(AVX2)) return;
EXPERIMENTAL_FLAG_SCOPE(revectorize);
WasmRunner<int32_t, int8_t, int8_t, int32_t> r(TestExecutionTier::kTurbofan);
int32_t* memory = r.builder().AddMemoryElems<int32_t>(8);
uint8_t param1 = 0;
uint8_t param2 = 1;
uint8_t param3 = 2;
r.Build({WASM_SIMD_STORE_MEM(
WASM_ZERO,
WASM_SIMD_TERNOP(kExprI32x4DotI8x16I7x16AddS,
WASM_SIMD_I8x16_SPLAT(WASM_LOCAL_GET(param1)),
WASM_SIMD_I8x16_SPLAT(WASM_LOCAL_GET(param2)),
WASM_SIMD_I32x4_SPLAT(WASM_LOCAL_GET(param3)))),
WASM_SIMD_STORE_MEM_OFFSET(
16, WASM_ZERO,
WASM_SIMD_TERNOP(kExprI32x4DotI8x16I7x16AddS,
WASM_SIMD_I8x16_SPLAT(WASM_LOCAL_GET(param1)),
WASM_SIMD_I8x16_SPLAT(WASM_LOCAL_GET(param2)),
WASM_SIMD_I32x4_SPLAT(WASM_LOCAL_GET(param3)))),
WASM_ONE});
for (int8_t x : compiler::ValueHelper::GetVector<int8_t>()) {
for (int8_t y : compiler::ValueHelper::GetVector<int8_t>()) {
for (int32_t z : compiler::ValueHelper::GetVector<int32_t>()) {
int32_t expected = base::AddWithWraparound(
base::MulWithWraparound(x * (y & 0x7F), 4), z);
r.Call(x, y & 0x7F, z);
for (auto i = 0; i < 4; i++) {
CHECK_EQ(expected, memory[i]);
CHECK_EQ(expected, memory[4 + i]);
}
}
}
}
}
TEST(RunWasm_I16x16DotI8x32I7x32S) {
if (!CpuFeatures::IsSupported(AVX2)) return;
EXPERIMENTAL_FLAG_SCOPE(revectorize);
WasmRunner<int32_t, int8_t, int8_t> r(TestExecutionTier::kTurbofan);
int16_t* memory = r.builder().AddMemoryElems<int16_t>(16);
uint8_t param1 = 0, param2 = 1;
r.Build({WASM_SIMD_STORE_MEM(
WASM_ZERO,
WASM_SIMD_BINOP(kExprI16x8DotI8x16I7x16S,
WASM_SIMD_I8x16_SPLAT(WASM_LOCAL_GET(param1)),
WASM_SIMD_I8x16_SPLAT(WASM_LOCAL_GET(param2)))),
WASM_SIMD_STORE_MEM_OFFSET(
16, WASM_ZERO,
WASM_SIMD_BINOP(kExprI16x8DotI8x16I7x16S,
WASM_SIMD_I8x16_SPLAT(WASM_LOCAL_GET(param1)),
WASM_SIMD_I8x16_SPLAT(WASM_LOCAL_GET(param2)))),
WASM_ONE});
for (int8_t x : compiler::ValueHelper::GetVector<int8_t>()) {
for (int8_t y : compiler::ValueHelper::GetVector<int8_t>()) {
r.Call(x, y & 0x7F);
// * 2 because we of (x*y) + (x*y) = 2*x*y
int16_t expected = base::MulWithWraparound(x * (y & 0x7F), 2);
CHECK_EQ(expected, memory[0]);
CHECK_EQ(expected, memory[8]);
}
}
}
TEST(RunWasmTurbofan_F32x8RelaxedMin) {
RunF32x8BinOpRevecTest(kExprF32x4RelaxedMin, Minimum,
compiler::IrOpcode::kF32x8RelaxedMin);
}
TEST(RunWasmTurbofan_F32x8RelaxedMax) {
RunF32x8BinOpRevecTest(kExprF32x4RelaxedMax, Maximum,
compiler::IrOpcode::kF32x8RelaxedMax);
}
TEST(RunWasmTurbofan_F64x4RelaxedMin) {
RunF64x4BinOpRevecTest(kExprF64x2RelaxedMin, Minimum,
compiler::IrOpcode::kF64x4RelaxedMin);
}
TEST(RunWasmTurbofan_F64x4RelaxedMax) {
RunF64x4BinOpRevecTest(kExprF64x2RelaxedMax, Maximum,
compiler::IrOpcode::kF64x4RelaxedMax);
}
template <typename IntType>
void I32x8RelaxedTruncF32x8RevecTest(WasmOpcode trunc_op,
compiler::IrOpcode::Value revec_opcode) {
if (!CpuFeatures::IsSupported(AVX2)) return;
EXPERIMENTAL_FLAG_SCOPE(revectorize);
WasmRunner<int32_t, float> r(TestExecutionTier::kTurbofan);
IntType* memory = r.builder().AddMemoryElems<IntType>(8);
uint8_t param1 = 0;
TSSimd256VerifyScope ts_scope(
r.zone(), TSSimd256VerifyScope::VerifyHaveOpcode<
compiler::turboshaft::Opcode::kSimd256Unary>);
BUILD_AND_CHECK_REVEC_NODE(
r, revec_opcode,
WASM_SIMD_STORE_MEM(
WASM_ZERO,
WASM_SIMD_UNOP(trunc_op, WASM_SIMD_UNOP(kExprF32x4Splat,
WASM_LOCAL_GET(param1)))),
WASM_SIMD_STORE_MEM_OFFSET(
16, WASM_ZERO,
WASM_SIMD_UNOP(trunc_op, WASM_SIMD_UNOP(kExprF32x4Splat,
WASM_LOCAL_GET(param1)))),
WASM_ONE);
for (float x : compiler::ValueHelper::GetVector<float>()) {
if (ShouldSkipTestingConstant<IntType>(x)) continue;
CHECK_EQ(1, r.Call(x));
IntType expected = base::checked_cast<IntType>(x);
for (int i = 0; i < 8; i++) {
CHECK_EQ(expected, memory[i]);
}
}
}
TEST(RunWasmTurbofan_I32x8RelaxedTruncF32x8U) {
I32x8RelaxedTruncF32x8RevecTest<uint32_t>(
kExprI32x4RelaxedTruncF32x4U,
compiler::IrOpcode::kI32x8RelaxedTruncF32x8U);
}
TEST(RunWasmTurbofan_I32x8RelaxedTruncF32x8S) {
I32x8RelaxedTruncF32x8RevecTest<int32_t>(
kExprI32x4RelaxedTruncF32x4S,
compiler::IrOpcode::kI32x8RelaxedTruncF32x8S);
}
#endif // V8_ENABLE_WASM_SIMD256_REVEC
} // namespace v8::internal::wasm

View File

@ -0,0 +1,65 @@
// Copyright 2017 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/cctest/cctest.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/common/wasm/wasm-macro-gen.h"
namespace v8 {
namespace internal {
namespace wasm {
WASM_EXEC_TEST(I32SExtendI8) {
WasmRunner<int32_t, int32_t> r(execution_tier);
r.Build({WASM_I32_SIGN_EXT_I8(WASM_LOCAL_GET(0))});
CHECK_EQ(0, r.Call(0));
CHECK_EQ(1, r.Call(1));
CHECK_EQ(-1, r.Call(-1));
CHECK_EQ(0x7a, r.Call(0x7a));
CHECK_EQ(-0x80, r.Call(0x80));
}
WASM_EXEC_TEST(I32SExtendI16) {
WasmRunner<int32_t, int32_t> r(execution_tier);
r.Build({WASM_I32_SIGN_EXT_I16(WASM_LOCAL_GET(0))});
CHECK_EQ(0, r.Call(0));
CHECK_EQ(1, r.Call(1));
CHECK_EQ(-1, r.Call(-1));
CHECK_EQ(0x7afa, r.Call(0x7afa));
CHECK_EQ(-0x8000, r.Call(0x8000));
}
WASM_EXEC_TEST(I64SExtendI8) {
WasmRunner<int64_t, int64_t> r(execution_tier);
r.Build({WASM_I64_SIGN_EXT_I8(WASM_LOCAL_GET(0))});
CHECK_EQ(0, r.Call(0));
CHECK_EQ(1, r.Call(1));
CHECK_EQ(-1, r.Call(-1));
CHECK_EQ(0x7a, r.Call(0x7a));
CHECK_EQ(-0x80, r.Call(0x80));
}
WASM_EXEC_TEST(I64SExtendI16) {
WasmRunner<int64_t, int64_t> r(execution_tier);
r.Build({WASM_I64_SIGN_EXT_I16(WASM_LOCAL_GET(0))});
CHECK_EQ(0, r.Call(0));
CHECK_EQ(1, r.Call(1));
CHECK_EQ(-1, r.Call(-1));
CHECK_EQ(0x7afa, r.Call(0x7afa));
CHECK_EQ(-0x8000, r.Call(0x8000));
}
WASM_EXEC_TEST(I64SExtendI32) {
WasmRunner<int64_t, int64_t> r(execution_tier);
r.Build({WASM_I64_SIGN_EXT_I32(WASM_LOCAL_GET(0))});
CHECK_EQ(0, r.Call(0));
CHECK_EQ(1, r.Call(1));
CHECK_EQ(-1, r.Call(-1));
CHECK_EQ(0x7fffffff, r.Call(0x7fffffff));
CHECK_EQ(-0x80000000LL, r.Call(0x80000000));
}
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,245 @@
// Copyright 2020 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.
//
// This file contains tests that run only on Liftoff, and each test verifies
// that the code was compiled by Liftoff. The default behavior is that each
// function is first attempted to be compiled by Liftoff, and if it fails, fall
// back to TurboFan. However we want to enforce that Liftoff is the tier that
// compiles these functions, in order to verify correctness of SIMD
// implementation in Liftoff.
#include "src/codegen/assembler-inl.h"
#include "src/wasm/wasm-opcodes.h"
#include "test/cctest/cctest.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace test_run_wasm_simd_liftoff {
TEST(S128Local) {
WasmRunner<int32_t> r(TestExecutionTier::kLiftoff);
uint8_t temp1 = r.AllocateLocal(kWasmS128);
r.Build({WASM_LOCAL_SET(temp1, WASM_LOCAL_GET(temp1)), WASM_ONE});
CHECK_EQ(1, r.Call());
}
TEST(S128Global) {
WasmRunner<int32_t> r(TestExecutionTier::kLiftoff);
int32_t* g0 = r.builder().AddGlobal<int32_t>(kWasmS128);
int32_t* g1 = r.builder().AddGlobal<int32_t>(kWasmS128);
r.Build({WASM_GLOBAL_SET(1, WASM_GLOBAL_GET(0)), WASM_ONE});
int32_t expected = 0x1234;
for (int i = 0; i < 4; i++) {
LANE(g0, i) = expected;
}
r.Call();
for (int i = 0; i < 4; i++) {
int32_t actual = LANE(g1, i);
CHECK_EQ(actual, expected);
}
}
TEST(S128Param) {
// Test how SIMD parameters in functions are processed. There is no easy way
// to specify a SIMD value when initializing a WasmRunner, so we manually
// add a new function with the right signature, and call it from main.
WasmRunner<int32_t> r(TestExecutionTier::kLiftoff);
TestSignatures sigs;
// We use a temp local to materialize a SIMD value, since at this point
// Liftoff does not support any SIMD operations.
uint8_t temp1 = r.AllocateLocal(kWasmS128);
WasmFunctionCompiler& simd_func = r.NewFunction(sigs.i_s());
simd_func.Build({WASM_ONE});
r.Build(
{WASM_CALL_FUNCTION(simd_func.function_index(), WASM_LOCAL_GET(temp1))});
CHECK_EQ(1, r.Call());
}
TEST(S128Return) {
// Test how functions returning SIMD values are processed.
WasmRunner<int32_t> r(TestExecutionTier::kLiftoff);
TestSignatures sigs;
WasmFunctionCompiler& simd_func = r.NewFunction(sigs.s_i());
uint8_t temp1 = simd_func.AllocateLocal(kWasmS128);
simd_func.Build({WASM_LOCAL_GET(temp1)});
r.Build({WASM_CALL_FUNCTION(simd_func.function_index(), WASM_ONE), kExprDrop,
WASM_ONE});
CHECK_EQ(1, r.Call());
}
TEST(REGRESS_1088273) {
// TODO(v8:9418): This is a regression test for Liftoff, translated from a
// mjsunit test. We do not have I64x2Mul lowering yet, so this will cause a
// crash on arch that don't support SIMD 128 and require lowering, thus
// explicitly skip them.
if (!CpuFeatures::SupportsWasmSimd128()) return;
WasmRunner<int32_t> r(TestExecutionTier::kLiftoff);
TestSignatures sigs;
WasmFunctionCompiler& simd_func = r.NewFunction(sigs.s_i());
uint8_t temp1 = simd_func.AllocateLocal(kWasmS128);
simd_func.Build({WASM_LOCAL_GET(temp1)});
r.Build({WASM_SIMD_SPLAT(I8x16, WASM_I32V(0x80)),
WASM_SIMD_SPLAT(I8x16, WASM_I32V(0x92)),
WASM_SIMD_I16x8_EXTRACT_LANE_U(0, WASM_SIMD_OP(kExprI64x2Mul))});
CHECK_EQ(18688, r.Call());
}
// A test to exercise logic in Liftoff's implementation of shuffle. The
// implementation in Liftoff is a bit more tricky due to shuffle requiring
// adjacent registers in ARM/ARM64.
TEST(I8x16Shuffle) {
WasmRunner<int32_t> r(TestExecutionTier::kLiftoff);
// Temps to use up registers and force non-adjacent registers for shuffle.
uint8_t local0 = r.AllocateLocal(kWasmS128);
uint8_t local1 = r.AllocateLocal(kWasmS128);
// g0 and g1 are globals that hold input values for the shuffle,
// g0 contains byte array [0, 1, ... 15], g1 contains byte array [16, 17,
// ... 31]. They should never be overwritten - write only to output.
uint8_t* g0 = r.builder().AddGlobal<uint8_t>(kWasmS128);
uint8_t* g1 = r.builder().AddGlobal<uint8_t>(kWasmS128);
for (int i = 0; i < 16; i++) {
LANE(g0, i) = i;
LANE(g1, i) = i + 16;
}
// Output global holding a kWasmS128.
uint8_t* output = r.builder().AddGlobal<uint8_t>(kWasmS128);
// i8x16_shuffle(lhs, rhs, pattern) will take the last element of rhs and
// place it into the last lane of lhs.
std::array<uint8_t, 16> pattern = {
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 31}};
// Set up locals so shuffle is called with non-adjacent registers v2 and v0.
r.Build(
{WASM_LOCAL_SET(local0, WASM_GLOBAL_GET(1)), // local0 is in v0
WASM_LOCAL_SET(local1, WASM_GLOBAL_GET(0)), // local1 is in v1
WASM_GLOBAL_GET(0), // global0 is in v2
WASM_LOCAL_GET(local0), // local0 is in v0
WASM_GLOBAL_SET(2, WASM_SIMD_I8x16_SHUFFLE_OP(kExprI8x16Shuffle, pattern,
WASM_NOP, WASM_NOP)),
WASM_ONE});
r.Call();
// The shuffle pattern only changes the last element.
for (int i = 0; i < 15; i++) {
uint8_t actual = LANE(output, i);
CHECK_EQ(i, actual);
}
CHECK_EQ(31, LANE(output, 15));
}
// Exercise logic in Liftoff's implementation of shuffle when inputs to the
// shuffle are the same register.
TEST(I8x16Shuffle_SingleOperand) {
WasmRunner<int32_t> r(TestExecutionTier::kLiftoff);
uint8_t local0 = r.AllocateLocal(kWasmS128);
uint8_t* g0 = r.builder().AddGlobal<uint8_t>(kWasmS128);
for (int i = 0; i < 16; i++) {
LANE(g0, i) = i;
}
uint8_t* output = r.builder().AddGlobal<uint8_t>(kWasmS128);
// This pattern reverses first operand. 31 should select the last lane of
// the second operand, but since the operands are the same, the effect is that
// the first operand is reversed.
std::array<uint8_t, 16> pattern = {
{31, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}};
// Set up locals so shuffle is called with non-adjacent registers v2 and v0.
r.Build(
{WASM_LOCAL_SET(local0, WASM_GLOBAL_GET(0)), WASM_LOCAL_GET(local0),
WASM_LOCAL_GET(local0),
WASM_GLOBAL_SET(1, WASM_SIMD_I8x16_SHUFFLE_OP(kExprI8x16Shuffle, pattern,
WASM_NOP, WASM_NOP)),
WASM_ONE});
r.Call();
for (int i = 0; i < 16; i++) {
// Check that the output is the reverse of input.
uint8_t actual = LANE(output, i);
CHECK_EQ(15 - i, actual);
}
}
// Exercise Liftoff's logic for zero-initializing stack slots. We were using an
// incorrect instruction for storing zeroes into the slot when the slot offset
// was too large to fit in the instruction as an immediate.
TEST(FillStackSlotsWithZero_CheckStartOffset) {
WasmRunner<int64_t> r(TestExecutionTier::kLiftoff);
// Function that takes in 32 i64 arguments, returns i64. This gets us a large
// enough starting offset from which we spill locals.
// start = 32 * 8 + 16 (instance) = 272 (cannot fit in signed int9).
const FunctionSig* sig =
r.CreateSig<int64_t, int64_t, int64_t, int64_t, int64_t, int64_t, int64_t,
int64_t, int64_t, int64_t, int64_t, int64_t, int64_t, int64_t,
int64_t, int64_t, int64_t, int64_t, int64_t, int64_t, int64_t,
int64_t, int64_t, int64_t, int64_t, int64_t, int64_t, int64_t,
int64_t, int64_t, int64_t, int64_t, int64_t>();
WasmFunctionCompiler& simd_func = r.NewFunction(sig);
// We zero 16 bytes at a time using stp, so allocate locals such that we get a
// remainder, 8 in this case, so we hit the case where we use str.
simd_func.AllocateLocal(kWasmS128);
simd_func.AllocateLocal(kWasmI64);
simd_func.Build({WASM_I64V_1(1)});
r.Build({WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_I64V_1(1),
WASM_CALL_FUNCTION0(simd_func.function_index())});
CHECK_EQ(1, r.Call());
}
} // namespace test_run_wasm_simd_liftoff
} // namespace wasm
} // namespace internal
} // namespace v8

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,456 @@
// Copyright 2020 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 "src/wasm/wasm-module-builder.h"
#include "src/wasm/wasm-objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/common/wasm/flag-utils.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
#include "test/common/wasm/wasm-module-runner.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace test_run_wasm_wrappers {
using testing::CompileAndInstantiateForTesting;
#if V8_COMPRESS_POINTERS && \
(V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_ARM64 || V8_TARGET_ARCH_IA32 || \
V8_TARGET_ARCH_ARM || V8_TARGET_ARCH_LOONG64)
namespace {
DirectHandle<WasmInstanceObject> CompileModule(Zone* zone, Isolate* isolate,
WasmModuleBuilder* builder) {
ZoneBuffer buffer(zone);
builder->WriteTo(&buffer);
testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "CompileAndRunWasmModule");
MaybeDirectHandle<WasmInstanceObject> maybe_instance =
CompileAndInstantiateForTesting(isolate, &thrower,
base::VectorOf(buffer));
CHECK_WITH_MSG(!thrower.error(), thrower.error_msg());
return maybe_instance.ToHandleChecked();
}
bool IsGeneric(Tagged<Code> wrapper) {
return wrapper->is_builtin() &&
wrapper->builtin_id() == Builtin::kJSToWasmWrapper;
}
bool IsSpecific(Tagged<Code> wrapper) {
return wrapper->kind() == CodeKind::JS_TO_WASM_FUNCTION;
}
DirectHandle<Object> SmiHandle(Isolate* isolate, int value) {
return DirectHandle<Object>(Smi::FromInt(value), isolate);
}
void SmiCall(Isolate* isolate,
DirectHandle<WasmExportedFunction> exported_function,
base::Vector<const DirectHandle<Object>> args,
int expected_result) {
DirectHandle<Object> receiver = isolate->factory()->undefined_value();
DirectHandle<Object> result =
Execution::Call(isolate, exported_function, receiver, args)
.ToHandleChecked();
CHECK(IsSmi(*result));
CHECK_EQ(expected_result, Smi::ToInt(*result));
}
void Cleanup() {
// By sending a low memory notifications, we will try hard to collect all
// garbage and will therefore also invoke all weak callbacks of actually
// unreachable persistent handles.
Isolate* isolate = CcTest::InitIsolateOnce();
reinterpret_cast<v8::Isolate*>(isolate)->LowMemoryNotification();
}
} // namespace
TEST(WrapperBudget) {
{
// This test assumes use of the generic wrapper.
FlagScope<bool> use_wasm_generic_wrapper(&v8_flags.wasm_generic_wrapper,
true);
// Initialize the environment and create a module builder.
AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
// Define the Wasm function.
TestSignatures sigs;
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_ii());
f->builder()->AddExport(base::CStrVector("main"), f);
f->EmitCode({WASM_I32_MUL(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1)), WASM_END});
// Compile the module.
DirectHandle<WasmInstanceObject> instance =
CompileModule(&zone, isolate, builder);
// Get the exported function and the function data.
DirectHandle<WasmExportedFunction> main_export =
testing::GetExportedFunction(isolate, instance, "main")
.ToHandleChecked();
DirectHandle<WasmExportedFunctionData> main_function_data(
main_export->shared()->wasm_exported_function_data(), isolate);
// Check that the generic-wrapper budget has initially a value of
// kGenericWrapperBudget.
CHECK_EQ(Smi::ToInt(main_function_data->wrapper_budget()->value()),
kGenericWrapperBudget);
static_assert(kGenericWrapperBudget > 0);
// Call the exported Wasm function.
DirectHandle<Object> params[] = {SmiHandle(isolate, 6),
SmiHandle(isolate, 7)};
SmiCall(isolate, main_export, base::VectorOf(params), 42);
// Check that the budget has now a value of (kGenericWrapperBudget - 1).
CHECK_EQ(Smi::ToInt(main_function_data->wrapper_budget()->value()),
kGenericWrapperBudget - 1);
}
Cleanup();
}
TEST(WrapperReplacement) {
{
// This test assumes use of the generic wrapper.
FlagScope<bool> use_wasm_generic_wrapper(&v8_flags.wasm_generic_wrapper,
true);
// Initialize the environment and create a module builder.
AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
// Define the Wasm function.
TestSignatures sigs;
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_i());
f->builder()->AddExport(base::CStrVector("main"), f);
f->EmitCode({WASM_LOCAL_GET(0), WASM_END});
// Compile the module.
DirectHandle<WasmInstanceObject> instance =
CompileModule(&zone, isolate, builder);
// Get the exported function and the function data.
DirectHandle<WasmExportedFunction> main_export =
testing::GetExportedFunction(isolate, instance, "main")
.ToHandleChecked();
DirectHandle<WasmExportedFunctionData> main_function_data(
main_export->shared()->wasm_exported_function_data(), isolate);
// Check that the generic-wrapper budget has initially a value of
// kGenericWrapperBudget.
CHECK_EQ(Smi::ToInt(main_function_data->wrapper_budget()->value()),
kGenericWrapperBudget);
static_assert(kGenericWrapperBudget > 0);
// Set the generic-wrapper budget to a value that allows for a few
// more calls through the generic wrapper.
const int remaining_budget =
std::min(static_cast<int>(kGenericWrapperBudget), 2);
main_function_data->wrapper_budget()->set_value(
Smi::FromInt(remaining_budget));
// Call the exported Wasm function as many times as required to almost
// exhaust the remaining budget for using the generic wrapper.
DirectHandle<Code> wrapper_before_call;
for (int i = remaining_budget; i > 0; --i) {
// Verify that the wrapper to be used is the generic one.
wrapper_before_call =
direct_handle(main_function_data->wrapper_code(isolate), isolate);
CHECK(IsGeneric(*wrapper_before_call));
// Call the function.
DirectHandle<Object> params[] = {SmiHandle(isolate, i)};
SmiCall(isolate, main_export, base::VectorOf(params), i);
// Verify that the budget has now a value of (i - 1).
CHECK_EQ(Smi::ToInt(main_function_data->wrapper_budget()->value()),
i - 1);
}
// Get the wrapper-code object after the wrapper replacement.
Tagged<Code> wrapper_after_call = main_function_data->wrapper_code(isolate);
// Verify that the budget has been exhausted.
CHECK_EQ(Smi::ToInt(main_function_data->wrapper_budget()->value()), 0);
// Verify that the wrapper-code object has changed and the wrapper is now a
// specific one.
// TODO(saelo): here we have to use full pointer comparison while not all
// Code objects have been moved into trusted space.
static_assert(!kAllCodeObjectsLiveInTrustedSpace);
CHECK(!wrapper_after_call.SafeEquals(*wrapper_before_call));
CHECK(IsSpecific(wrapper_after_call));
}
Cleanup();
}
TEST(EagerWrapperReplacement) {
{
// This test assumes use of the generic wrapper.
FlagScope<bool> use_wasm_generic_wrapper(&v8_flags.wasm_generic_wrapper,
true);
// Initialize the environment and create a module builder.
AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
// Define three Wasm functions.
// Two of these functions (add and mult) will share the same signature,
// while the other one (id) won't.
TestSignatures sigs;
WasmFunctionBuilder* add = builder->AddFunction(sigs.i_ii());
add->builder()->AddExport(base::CStrVector("add"), add);
add->EmitCode(
{WASM_I32_ADD(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1)), WASM_END});
WasmFunctionBuilder* mult = builder->AddFunction(sigs.i_ii());
mult->builder()->AddExport(base::CStrVector("mult"), mult);
mult->EmitCode(
{WASM_I32_MUL(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1)), WASM_END});
WasmFunctionBuilder* id = builder->AddFunction(sigs.i_i());
id->builder()->AddExport(base::CStrVector("id"), id);
id->EmitCode({WASM_LOCAL_GET(0), WASM_END});
// Compile the module.
DirectHandle<WasmInstanceObject> instance =
CompileModule(&zone, isolate, builder);
// Get the exported functions.
DirectHandle<WasmExportedFunction> add_export =
testing::GetExportedFunction(isolate, instance, "add")
.ToHandleChecked();
DirectHandle<WasmExportedFunction> mult_export =
testing::GetExportedFunction(isolate, instance, "mult")
.ToHandleChecked();
DirectHandle<WasmExportedFunction> id_export =
testing::GetExportedFunction(isolate, instance, "id").ToHandleChecked();
// Get the function data for all exported functions.
DirectHandle<WasmExportedFunctionData> add_function_data(
add_export->shared()->wasm_exported_function_data(), isolate);
DirectHandle<WasmExportedFunctionData> mult_function_data(
mult_export->shared()->wasm_exported_function_data(), isolate);
DirectHandle<WasmExportedFunctionData> id_function_data(
id_export->shared()->wasm_exported_function_data(), isolate);
// Set the remaining generic-wrapper budget for add to 1,
// so that the next call to it will cause the function to tier up.
add_function_data->wrapper_budget()->set_value(Smi::FromInt(1));
// Verify that the generic-wrapper budgets for all functions are correct.
CHECK_EQ(Smi::ToInt(add_function_data->wrapper_budget()->value()), 1);
CHECK_EQ(Smi::ToInt(mult_function_data->wrapper_budget()->value()),
kGenericWrapperBudget);
CHECK_EQ(Smi::ToInt(id_function_data->wrapper_budget()->value()),
kGenericWrapperBudget);
// Verify that all functions are set to use the generic wrapper.
CHECK(IsGeneric(add_function_data->wrapper_code(isolate)));
CHECK(IsGeneric(mult_function_data->wrapper_code(isolate)));
CHECK(IsGeneric(id_function_data->wrapper_code(isolate)));
// Call the add function to trigger the tier up.
{
DirectHandle<Object> params[] = {SmiHandle(isolate, 10),
SmiHandle(isolate, 11)};
SmiCall(isolate, add_export, base::VectorOf(params), 21);
// Verify that the generic-wrapper budgets for all functions are correct.
CHECK_EQ(Smi::ToInt(add_function_data->wrapper_budget()->value()), 0);
CHECK_EQ(Smi::ToInt(mult_function_data->wrapper_budget()->value()),
kGenericWrapperBudget);
CHECK_EQ(Smi::ToInt(id_function_data->wrapper_budget()->value()),
kGenericWrapperBudget);
// Verify that the tier-up of the add function replaced the wrapper
// for both the add and the mult functions, but not the id function.
CHECK(IsSpecific(add_function_data->wrapper_code(isolate)));
CHECK(IsSpecific(mult_function_data->wrapper_code(isolate)));
CHECK(IsGeneric(id_function_data->wrapper_code(isolate)));
}
// Call the mult function to verify that the compiled wrapper is used.
{
DirectHandle<Object> params[] = {SmiHandle(isolate, 6),
SmiHandle(isolate, 7)};
SmiCall(isolate, mult_export, base::VectorOf(params), 42);
// Verify that mult's budget is still intact, which means that the call
// didn't go through the generic wrapper.
CHECK_EQ(Smi::ToInt(mult_function_data->wrapper_budget()->value()),
kGenericWrapperBudget);
}
// Call the id function to verify that the generic wrapper is used.
{
DirectHandle<Object> params[] = {SmiHandle(isolate, 6)};
SmiCall(isolate, id_export, base::VectorOf(params), 6);
// Verify that id's budget decreased by 1, which means that the call
// used the generic wrapper.
CHECK_EQ(Smi::ToInt(id_function_data->wrapper_budget()->value()),
kGenericWrapperBudget - 1);
}
}
Cleanup();
}
TEST(WrapperReplacement_IndirectExport) {
{
// This test assumes use of the generic wrapper.
FlagScope<bool> use_wasm_generic_wrapper(&v8_flags.wasm_generic_wrapper,
true);
// Initialize the environment and create a module builder.
AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
// Define a Wasm function, but do not add it to the exports.
TestSignatures sigs;
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_i());
f->EmitCode({WASM_LOCAL_GET(0), WASM_END});
uint32_t function_index = f->func_index();
// Export a table of indirect functions.
const uint32_t table_size = 2;
const uint32_t table_index =
builder->AddTable(kWasmFuncRef, table_size, table_size);
builder->AddExport(base::CStrVector("exported_table"), kExternalTable, 0);
// Point from the exported table to the Wasm function.
builder->SetIndirectFunction(
table_index, 0, function_index,
WasmModuleBuilder::WasmElemSegment::kRelativeToImports);
// Compile the module.
DirectHandle<WasmInstanceObject> instance =
CompileModule(&zone, isolate, builder);
// Get the exported table.
DirectHandle<WasmTableObject> table(
Cast<WasmTableObject>(
instance->trusted_data(isolate)->tables()->get(table_index)),
isolate);
// Get the Wasm function through the exported table.
DirectHandle<WasmFuncRef> func_ref =
Cast<WasmFuncRef>(WasmTableObject::Get(isolate, table, function_index));
DirectHandle<WasmInternalFunction> internal_function{
func_ref->internal(isolate), isolate};
DirectHandle<WasmExportedFunction> indirect_function =
Cast<WasmExportedFunction>(
WasmInternalFunction::GetOrCreateExternal(internal_function));
// Get the function data.
DirectHandle<WasmExportedFunctionData> indirect_function_data(
indirect_function->shared()->wasm_exported_function_data(), isolate);
// Verify that the generic-wrapper budget has initially a value of
// kGenericWrapperBudget and the wrapper to be used for calls to the
// indirect function is the generic one.
CHECK(IsGeneric(indirect_function_data->wrapper_code(isolate)));
CHECK(Smi::ToInt(indirect_function_data->wrapper_budget()->value()) ==
kGenericWrapperBudget);
// Set the remaining generic-wrapper budget for the indirect function to 1,
// so that the next call to it will cause the function to tier up.
indirect_function_data->wrapper_budget()->set_value(Smi::FromInt(1));
// Call the Wasm function.
DirectHandle<Object> params[] = {SmiHandle(isolate, 6)};
SmiCall(isolate, indirect_function, base::VectorOf(params), 6);
// Verify that the budget is now exhausted and the generic wrapper has been
// replaced by a specific one.
CHECK_EQ(Smi::ToInt(indirect_function_data->wrapper_budget()->value()), 0);
CHECK(IsSpecific(indirect_function_data->wrapper_code(isolate)));
}
Cleanup();
}
TEST(JSToWasmWrapperGarbageCollection) {
Isolate* isolate = CcTest::InitIsolateOnce();
auto NumCompiledJSToWasmWrappers = [isolate]() {
int num_wrappers = 0;
Tagged<WeakFixedArray> wrappers = isolate->heap()->js_to_wasm_wrappers();
for (int i = 0, e = wrappers->length(); i < e; ++i) {
// Entries are either weak code wrappers, cleared entries, or undefined.
Tagged<MaybeObject> maybe_wrapper = wrappers->get(i);
if (maybe_wrapper.IsCleared()) continue;
CHECK(maybe_wrapper.IsWeak());
CHECK(IsCodeWrapper(maybe_wrapper.GetHeapObjectAssumeWeak()));
Tagged<Code> code =
Cast<CodeWrapper>(maybe_wrapper.GetHeapObjectAssumeWeak())
->code(isolate);
CHECK_EQ(CodeKind::JS_TO_WASM_FUNCTION, code->kind());
++num_wrappers;
}
return num_wrappers;
};
{
// Initialize the environment and create a module builder.
AccountingAllocator allocator;
Zone zone{&allocator, ZONE_NAME};
HandleScope scope{isolate};
WasmModuleBuilder builder{&zone};
// Define an exported Wasm function.
TestSignatures sigs;
WasmFunctionBuilder* f = builder.AddFunction(sigs.i_v());
builder.AddExport(base::CStrVector("main"), f);
f->EmitCode({WASM_ONE, WASM_END});
// Before compilation there should be no compiled wrappers.
CHECK_EQ(0, NumCompiledJSToWasmWrappers());
// Compile the module.
DirectHandle<WasmInstanceObject> instance =
CompileModule(&zone, isolate, &builder);
// If the generic wrapper is disabled, this should have compiled a wrapper.
CHECK_EQ(v8_flags.wasm_generic_wrapper ? 0 : 1,
NumCompiledJSToWasmWrappers());
// Get the exported function and the function data.
DirectHandle<WasmExportedFunction> main_function =
testing::GetExportedFunction(isolate, instance, "main")
.ToHandleChecked();
DirectHandle<WasmExportedFunctionData> main_function_data(
main_function->shared()->wasm_exported_function_data(), isolate);
// Set the remaining generic-wrapper budget for add to 1,
// so that the next call to it will cause the function to tier up.
main_function_data->wrapper_budget()->set_value(Smi::FromInt(1));
// Call the Wasm function.
SmiCall(isolate, main_function, {}, 1);
// There should be exactly one compiled wrapper now.
CHECK_EQ(1, NumCompiledJSToWasmWrappers());
}
// After GC all compiled wrappers must be cleared again.
{
// Disable stack scanning in case CSS is being used to prevent references
// that leaked to the C++ stack from keeping the wrapper alive.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(
isolate->heap());
Cleanup();
}
CHECK_EQ(0, NumCompiledJSToWasmWrappers());
}
#endif
} // namespace test_run_wasm_wrappers
} // namespace wasm
} // namespace internal
} // namespace v8

3920
deps/v8/test/cctest/wasm/test-run-wasm.cc vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,589 @@
// Copyright 2016 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 "src/codegen/assembler-inl.h"
#include "src/debug/debug-interface.h"
#include "src/execution/frames-inl.h"
#include "src/objects/property-descriptor.h"
#include "src/utils/utils.h"
#include "src/wasm/wasm-debug.h"
#include "src/wasm/wasm-objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/common/value-helper.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace {
debug::Location TranslateLocation(WasmRunnerBase* runner,
const debug::Location& loc) {
// Convert locations from {func_index, offset_in_func} to
// {0, offset_in_module}.
int func_index = loc.GetLineNumber();
int func_offset = runner->builder().GetFunctionAt(func_index)->code.offset();
int offset = loc.GetColumnNumber() + func_offset;
return {0, offset};
}
void CheckLocations(
WasmRunnerBase* runner, NativeModule* native_module, debug::Location start,
debug::Location end,
std::initializer_list<debug::Location> expected_locations_init) {
std::vector<debug::BreakLocation> locations;
std::vector<debug::Location> expected_locations;
for (auto loc : expected_locations_init) {
expected_locations.push_back(TranslateLocation(runner, loc));
}
bool success = WasmScript::GetPossibleBreakpoints(
native_module, TranslateLocation(runner, start),
TranslateLocation(runner, end), &locations);
CHECK(success);
printf("got %d locations: ", static_cast<int>(locations.size()));
for (size_t i = 0, e = locations.size(); i != e; ++i) {
printf("%s<%d,%d>", i == 0 ? "" : ", ", locations[i].GetLineNumber(),
locations[i].GetColumnNumber());
}
printf("\n");
CHECK_EQ(expected_locations.size(), locations.size());
for (size_t i = 0, e = locations.size(); i != e; ++i) {
CHECK_EQ(expected_locations[i].GetLineNumber(),
locations[i].GetLineNumber());
CHECK_EQ(expected_locations[i].GetColumnNumber(),
locations[i].GetColumnNumber());
}
}
void CheckLocationsFail(WasmRunnerBase* runner, NativeModule* native_module,
debug::Location start, debug::Location end) {
std::vector<debug::BreakLocation> locations;
bool success = WasmScript::GetPossibleBreakpoints(
native_module, TranslateLocation(runner, start),
TranslateLocation(runner, end), &locations);
CHECK(!success);
}
class BreakHandler : public debug::DebugDelegate {
public:
enum Action {
Continue = StepAction::LastStepAction + 1,
StepOver = StepAction::StepOver,
StepInto = StepAction::StepInto,
StepOut = StepAction::StepOut
};
struct BreakPoint {
int position;
Action action;
std::function<void(void)> pre_action;
BreakPoint(int position, Action action)
: position(position), action(action), pre_action([]() {}) {}
BreakPoint(int position, Action action,
std::function<void(void)> pre_action)
: position(position), action(action), pre_action(pre_action) {}
};
explicit BreakHandler(Isolate* isolate,
std::initializer_list<BreakPoint> expected_breaks)
: isolate_(isolate), expected_breaks_(expected_breaks) {
v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_), this);
}
~BreakHandler() override {
// Check that all expected breakpoints have been hit.
CHECK_EQ(count_, expected_breaks_.size());
v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_),
nullptr);
}
int count() const { return count_; }
private:
Isolate* isolate_;
int count_ = 0;
std::vector<BreakPoint> expected_breaks_;
void BreakProgramRequested(v8::Local<v8::Context> paused_context,
const std::vector<int>&,
v8::debug::BreakReasons break_reasons) override {
printf("Break #%d\n", count_);
CHECK_GT(expected_breaks_.size(), count_);
// Check the current position.
DebuggableStackFrameIterator frame_it(isolate_);
auto summ = FrameSummary::GetTop(frame_it.frame()).AsWasm();
CHECK_EQ(expected_breaks_[count_].position, summ.code_offset());
expected_breaks_[count_].pre_action();
Action next_action = expected_breaks_[count_].action;
switch (next_action) {
case Continue:
break;
case StepOver:
case StepInto:
case StepOut:
isolate_->debug()->PrepareStep(static_cast<StepAction>(next_action));
break;
default:
UNREACHABLE();
}
++count_;
}
};
Handle<BreakPoint> SetBreakpoint(WasmRunnerBase* runner, int function_index,
int byte_offset,
int expected_set_byte_offset = -1) {
runner->SwitchToDebug();
int func_offset =
runner->builder().GetFunctionAt(function_index)->code.offset();
int code_offset = func_offset + byte_offset;
if (expected_set_byte_offset == -1) expected_set_byte_offset = byte_offset;
DirectHandle<WasmInstanceObject> instance =
runner->builder().instance_object();
DirectHandle<Script> script(instance->module_object()->script(),
runner->main_isolate());
static int break_index = 0;
Handle<BreakPoint> break_point =
runner->main_isolate()->factory()->NewBreakPoint(
break_index++, runner->main_isolate()->factory()->empty_string());
CHECK(WasmScript::SetBreakPoint(script, &code_offset, break_point));
return break_point;
}
void ClearBreakpoint(WasmRunnerBase* runner, int function_index,
int byte_offset, DirectHandle<BreakPoint> break_point) {
int func_offset =
runner->builder().GetFunctionAt(function_index)->code.offset();
int code_offset = func_offset + byte_offset;
DirectHandle<WasmInstanceObject> instance =
runner->builder().instance_object();
DirectHandle<Script> script(instance->module_object()->script(),
runner->main_isolate());
CHECK(WasmScript::ClearBreakPoint(script, code_offset, break_point));
}
// Wrapper with operator<<.
struct WasmValWrapper {
WasmValue val;
bool operator==(const WasmValWrapper& other) const {
return val == other.val;
}
};
// Only needed in debug builds. Avoid unused warning otherwise.
#ifdef DEBUG
std::ostream& operator<<(std::ostream& out, const WasmValWrapper& wrapper) {
switch (wrapper.val.type().kind()) {
case kI32:
out << "i32: " << wrapper.val.to<int32_t>();
break;
case kI64:
out << "i64: " << wrapper.val.to<int64_t>();
break;
case kF32:
out << "f32: " << wrapper.val.to<float>();
break;
case kF64:
out << "f64: " << wrapper.val.to<double>();
break;
default:
UNIMPLEMENTED();
}
return out;
}
#endif
class CollectValuesBreakHandler : public debug::DebugDelegate {
public:
struct BreakpointValues {
std::vector<WasmValue> locals;
std::vector<WasmValue> stack;
};
explicit CollectValuesBreakHandler(
Isolate* isolate, std::initializer_list<BreakpointValues> expected_values)
: isolate_(isolate), expected_values_(expected_values) {
v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_), this);
}
~CollectValuesBreakHandler() override {
v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_),
nullptr);
}
private:
Isolate* isolate_;
int count_ = 0;
std::vector<BreakpointValues> expected_values_;
void BreakProgramRequested(v8::Local<v8::Context> paused_context,
const std::vector<int>&,
v8::debug::BreakReasons break_reasons) override {
printf("Break #%d\n", count_);
CHECK_GT(expected_values_.size(), count_);
auto& expected = expected_values_[count_];
++count_;
HandleScope handles(isolate_);
DebuggableStackFrameIterator frame_it(isolate_);
WasmFrame* frame = WasmFrame::cast(frame_it.frame());
DebugInfo* debug_info = frame->native_module()->GetDebugInfo();
int num_locals = debug_info->GetNumLocals(frame->pc(), isolate_);
CHECK_EQ(expected.locals.size(), num_locals);
for (int i = 0; i < num_locals; ++i) {
WasmValue local_value = debug_info->GetLocalValue(
i, frame->pc(), frame->fp(), frame->callee_fp(), isolate_);
CHECK_EQ(WasmValWrapper{expected.locals[i]}, WasmValWrapper{local_value});
}
int stack_depth = debug_info->GetStackDepth(frame->pc(), isolate_);
CHECK_EQ(expected.stack.size(), stack_depth);
for (int i = 0; i < stack_depth; ++i) {
WasmValue stack_value = debug_info->GetStackValue(
i, frame->pc(), frame->fp(), frame->callee_fp(), isolate_);
CHECK_EQ(WasmValWrapper{expected.stack[i]}, WasmValWrapper{stack_value});
}
isolate_->debug()->PrepareStep(StepAction::StepInto);
}
};
// Special template to explicitly cast to WasmValue.
template <typename Arg>
WasmValue MakeWasmVal(Arg arg) {
return WasmValue(arg);
}
// Translate long to i64 (ambiguous otherwise).
template <>
WasmValue MakeWasmVal(long arg) { // NOLINT: allow long parameter
return WasmValue(static_cast<int64_t>(arg));
}
template <typename... Args>
std::vector<WasmValue> wasmVec(Args... args) {
std::array<WasmValue, sizeof...(args)> arr{{MakeWasmVal(args)...}};
return std::vector<WasmValue>{arr.begin(), arr.end()};
}
int GetIntReturnValue(MaybeDirectHandle<Object> retval) {
CHECK(!retval.is_null());
int result;
CHECK(Object::ToInt32(*retval.ToHandleChecked(), &result));
return result;
}
} // namespace
WASM_COMPILED_EXEC_TEST(WasmCollectPossibleBreakpoints) {
WasmRunner<int> runner(execution_tier);
runner.Build({WASM_NOP, WASM_I32_ADD(WASM_ZERO, WASM_ONE)});
Tagged<WasmInstanceObject> instance = *runner.builder().instance_object();
NativeModule* native_module = instance->module_object()->native_module();
std::vector<debug::Location> locations;
// Check all locations for function 0.
CheckLocations(&runner, native_module, {0, 0}, {0, 10},
{{0, 1}, {0, 2}, {0, 4}, {0, 6}, {0, 7}});
// Check a range ending at an instruction.
CheckLocations(&runner, native_module, {0, 2}, {0, 4}, {{0, 2}});
// Check a range ending one behind an instruction.
CheckLocations(&runner, native_module, {0, 2}, {0, 5}, {{0, 2}, {0, 4}});
// Check a range starting at an instruction.
CheckLocations(&runner, native_module, {0, 7}, {0, 8}, {{0, 7}});
// Check from an instruction to beginning of next function.
CheckLocations(&runner, native_module, {0, 7}, {0, 10}, {{0, 7}});
// Check from end of one function (no valid instruction position) to beginning
// of next function. Must be empty, but not fail.
CheckLocations(&runner, native_module, {0, 8}, {0, 10}, {});
// Check from one after the end of the function. Must fail.
CheckLocationsFail(&runner, native_module, {0, 9}, {0, 10});
}
WASM_COMPILED_EXEC_TEST(WasmSimpleBreak) {
WasmRunner<int> runner(execution_tier);
Isolate* isolate = runner.main_isolate();
runner.Build({WASM_NOP, WASM_I32_ADD(WASM_I32V_1(11), WASM_I32V_1(3))});
DirectHandle<JSFunction> main_fun_wrapper =
runner.builder().WrapCode(runner.function_index());
SetBreakpoint(&runner, runner.function_index(), 4, 4);
BreakHandler count_breaks(isolate, {{4, BreakHandler::Continue}});
DirectHandle<Object> global(isolate->context()->global_object(), isolate);
MaybeDirectHandle<Object> retval =
Execution::Call(isolate, main_fun_wrapper, global, {});
CHECK_EQ(14, GetIntReturnValue(retval));
}
WASM_COMPILED_EXEC_TEST(WasmNonBreakablePosition) {
WasmRunner<int> runner(execution_tier);
Isolate* isolate = runner.main_isolate();
runner.Build({WASM_RETURN(WASM_I32V_2(1024))});
DirectHandle<JSFunction> main_fun_wrapper =
runner.builder().WrapCode(runner.function_index());
SetBreakpoint(&runner, runner.function_index(), 2, 4);
BreakHandler count_breaks(isolate, {{4, BreakHandler::Continue}});
DirectHandle<Object> global(isolate->context()->global_object(), isolate);
MaybeDirectHandle<Object> retval =
Execution::Call(isolate, main_fun_wrapper, global, {});
CHECK_EQ(1024, GetIntReturnValue(retval));
}
WASM_COMPILED_EXEC_TEST(WasmSimpleStepping) {
WasmRunner<int> runner(execution_tier);
runner.Build({WASM_I32_ADD(WASM_I32V_1(11), WASM_I32V_1(3))});
Isolate* isolate = runner.main_isolate();
DirectHandle<JSFunction> main_fun_wrapper =
runner.builder().WrapCode(runner.function_index());
// Set breakpoint at the first I32Const.
SetBreakpoint(&runner, runner.function_index(), 1, 1);
BreakHandler count_breaks(isolate,
{
{1, BreakHandler::StepOver}, // I32Const
{3, BreakHandler::StepOver}, // I32Const
{5, BreakHandler::Continue} // I32Add
});
DirectHandle<Object> global(isolate->context()->global_object(), isolate);
MaybeDirectHandle<Object> retval =
Execution::Call(isolate, main_fun_wrapper, global, {});
CHECK_EQ(14, GetIntReturnValue(retval));
}
WASM_COMPILED_EXEC_TEST(WasmStepInAndOut) {
WasmRunner<int, int> runner(execution_tier);
runner.SwitchToDebug();
WasmFunctionCompiler& f2 = runner.NewFunction<void>();
f2.AllocateLocal(kWasmI32);
// Call f2 via indirect call, because a direct call requires f2 to exist when
// we compile main, but we need to compile main first so that the order of
// functions in the code section matches the function indexes.
// return arg0
runner.Build({WASM_RETURN(WASM_LOCAL_GET(0))});
// for (int i = 0; i < 10; ++i) { f2(i); }
f2.Build({WASM_LOOP(
WASM_BR_IF(0,
WASM_BINOP(kExprI32GeU, WASM_LOCAL_GET(0), WASM_I32V_1(10))),
WASM_LOCAL_SET(0, WASM_BINOP(kExprI32Sub, WASM_LOCAL_GET(0), WASM_ONE)),
WASM_CALL_FUNCTION(runner.function_index(), WASM_LOCAL_GET(0)), WASM_DROP,
WASM_BR(1))});
Isolate* isolate = runner.main_isolate();
DirectHandle<JSFunction> main_fun_wrapper =
runner.builder().WrapCode(f2.function_index());
// Set first breakpoint on the LocalGet (offset 19) before the Call.
SetBreakpoint(&runner, f2.function_index(), 19, 19);
BreakHandler count_breaks(isolate,
{
{19, BreakHandler::StepInto}, // LocalGet
{21, BreakHandler::StepInto}, // Call
{1, BreakHandler::StepOut}, // in f2
{23, BreakHandler::Continue} // After Call
});
DirectHandle<Object> global(isolate->context()->global_object(), isolate);
CHECK(!Execution::Call(isolate, main_fun_wrapper, global, {}).is_null());
}
WASM_COMPILED_EXEC_TEST(WasmGetLocalsAndStack) {
WasmRunner<void, int> runner(execution_tier);
runner.AllocateLocal(kWasmI64);
runner.AllocateLocal(kWasmF32);
runner.AllocateLocal(kWasmF64);
runner.Build(
{// set [1] to 17
WASM_LOCAL_SET(1, WASM_I64V_1(17)),
// set [2] to <arg0> = 7
WASM_LOCAL_SET(2, WASM_F32_SCONVERT_I32(WASM_LOCAL_GET(0))),
// set [3] to <arg1>/2 = 8.5
WASM_LOCAL_SET(3, WASM_F64_DIV(WASM_F64_SCONVERT_I64(WASM_LOCAL_GET(1)),
WASM_F64(2)))});
Isolate* isolate = runner.main_isolate();
DirectHandle<JSFunction> main_fun_wrapper =
runner.builder().WrapCode(runner.function_index());
// Set breakpoint at the first instruction (7 bytes for local decls: num
// entries + 3x<count, type>).
SetBreakpoint(&runner, runner.function_index(), 7, 7);
CollectValuesBreakHandler break_handler(
isolate,
{
// params + locals stack
{wasmVec(7, 0L, 0.f, 0.), wasmVec()}, // 0: i64.const[17]
{wasmVec(7, 0L, 0.f, 0.), wasmVec(17L)}, // 1: set_local[1]
{wasmVec(7, 17L, 0.f, 0.), wasmVec()}, // 2: get_local[0]
{wasmVec(7, 17L, 0.f, 0.), wasmVec(7)}, // 3: f32.convert_s
{wasmVec(7, 17L, 0.f, 0.), wasmVec(7.f)}, // 4: set_local[2]
{wasmVec(7, 17L, 7.f, 0.), wasmVec()}, // 5: get_local[1]
{wasmVec(7, 17L, 7.f, 0.), wasmVec(17L)}, // 6: f64.convert_s
{wasmVec(7, 17L, 7.f, 0.), wasmVec(17.)}, // 7: f64.const[2]
{wasmVec(7, 17L, 7.f, 0.), wasmVec(17., 2.)}, // 8: f64.div
{wasmVec(7, 17L, 7.f, 0.), wasmVec(8.5)}, // 9: set_local[3]
{wasmVec(7, 17L, 7.f, 8.5), wasmVec()}, // 10: end
});
DirectHandle<Object> global(isolate->context()->global_object(), isolate);
DirectHandle<Object> args[]{direct_handle(Smi::FromInt(7), isolate)};
CHECK(
!Execution::Call(isolate, main_fun_wrapper, global, base::VectorOf(args))
.is_null());
}
WASM_COMPILED_EXEC_TEST(WasmRemoveBreakPoint) {
WasmRunner<int> runner(execution_tier);
Isolate* isolate = runner.main_isolate();
runner.Build(
{WASM_NOP, WASM_NOP, WASM_NOP, WASM_NOP, WASM_NOP, WASM_I32V_1(14)});
DirectHandle<JSFunction> main_fun_wrapper =
runner.builder().WrapCode(runner.function_index());
SetBreakpoint(&runner, runner.function_index(), 1, 1);
SetBreakpoint(&runner, runner.function_index(), 2, 2);
Handle<BreakPoint> to_delete =
SetBreakpoint(&runner, runner.function_index(), 3, 3);
SetBreakpoint(&runner, runner.function_index(), 4, 4);
BreakHandler count_breaks(isolate, {{1, BreakHandler::Continue},
{2, BreakHandler::Continue,
[&runner, &to_delete]() {
ClearBreakpoint(
&runner, runner.function_index(),
3, to_delete);
}},
{4, BreakHandler::Continue}});
DirectHandle<Object> global(isolate->context()->global_object(), isolate);
MaybeDirectHandle<Object> retval =
Execution::Call(isolate, main_fun_wrapper, global, {});
CHECK_EQ(14, GetIntReturnValue(retval));
}
WASM_COMPILED_EXEC_TEST(WasmRemoveLastBreakPoint) {
WasmRunner<int> runner(execution_tier);
Isolate* isolate = runner.main_isolate();
runner.Build(
{WASM_NOP, WASM_NOP, WASM_NOP, WASM_NOP, WASM_NOP, WASM_I32V_1(14)});
DirectHandle<JSFunction> main_fun_wrapper =
runner.builder().WrapCode(runner.function_index());
SetBreakpoint(&runner, runner.function_index(), 1, 1);
SetBreakpoint(&runner, runner.function_index(), 2, 2);
Handle<BreakPoint> to_delete =
SetBreakpoint(&runner, runner.function_index(), 3, 3);
BreakHandler count_breaks(
isolate, {{1, BreakHandler::Continue},
{2, BreakHandler::Continue, [&runner, &to_delete]() {
ClearBreakpoint(&runner, runner.function_index(), 3,
to_delete);
}}});
DirectHandle<Object> global(isolate->context()->global_object(), isolate);
MaybeDirectHandle<Object> retval =
Execution::Call(isolate, main_fun_wrapper, global, {});
CHECK_EQ(14, GetIntReturnValue(retval));
}
WASM_COMPILED_EXEC_TEST(WasmRemoveAllBreakPoint) {
WasmRunner<int> runner(execution_tier);
Isolate* isolate = runner.main_isolate();
runner.Build(
{WASM_NOP, WASM_NOP, WASM_NOP, WASM_NOP, WASM_NOP, WASM_I32V_1(14)});
DirectHandle<JSFunction> main_fun_wrapper =
runner.builder().WrapCode(runner.function_index());
Handle<BreakPoint> bp1 =
SetBreakpoint(&runner, runner.function_index(), 1, 1);
Handle<BreakPoint> bp2 =
SetBreakpoint(&runner, runner.function_index(), 2, 2);
Handle<BreakPoint> bp3 =
SetBreakpoint(&runner, runner.function_index(), 3, 3);
BreakHandler count_breaks(
isolate, {{1, BreakHandler::Continue, [&runner, &bp1, &bp2, &bp3]() {
ClearBreakpoint(&runner, runner.function_index(), 1, bp1);
ClearBreakpoint(&runner, runner.function_index(), 3, bp3);
ClearBreakpoint(&runner, runner.function_index(), 2, bp2);
}}});
DirectHandle<Object> global(isolate->context()->global_object(), isolate);
MaybeDirectHandle<Object> retval =
Execution::Call(isolate, main_fun_wrapper, global, {});
CHECK_EQ(14, GetIntReturnValue(retval));
}
WASM_COMPILED_EXEC_TEST(WasmBreakInPostMVP) {
// This test checks that we don't fail if experimental / post-MVP opcodes are
// being used. There was a bug where we were trying to update the "detected"
// features set, but we were passing a nullptr when compiling with
// breakpoints.
WasmRunner<int> runner(execution_tier);
Isolate* isolate = runner.main_isolate();
// [] -> [i32, i32]
ValueType sig_types[] = {kWasmI32, kWasmI32};
FunctionSig sig{2, 0, sig_types};
ModuleTypeIndex sig_idx = runner.builder().AddSignature(&sig);
constexpr int kReturn = 13;
constexpr int kIgnored = 23;
runner.Build(
{WASM_BLOCK_X(sig_idx, WASM_I32V_1(kReturn), WASM_I32V_1(kIgnored)),
WASM_DROP});
DirectHandle<JSFunction> main_fun_wrapper =
runner.builder().WrapCode(runner.function_index());
SetBreakpoint(&runner, runner.function_index(), 3, 3);
BreakHandler count_breaks(isolate, {{3, BreakHandler::Continue}});
DirectHandle<Object> global(isolate->context()->global_object(), isolate);
MaybeDirectHandle<Object> retval =
Execution::Call(isolate, main_fun_wrapper, global, {});
CHECK_EQ(kReturn, GetIntReturnValue(retval));
}
WASM_COMPILED_EXEC_TEST(Regress10889) {
FLAG_SCOPE(print_wasm_code);
WasmRunner<int> runner(execution_tier);
runner.Build({WASM_I32V_1(0)});
SetBreakpoint(&runner, runner.function_index(), 1, 1);
}
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,125 @@
// Copyright 2017 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.
// Tests effects of (CSP) "unsafe-eval" and "wasm-eval" callback functions.
//
// Note: These tests are in a separate test file because the tests dynamically
// change the isolate in terms of allow_wasm_code_gen_callback.
#include "src/api/api-inl.h"
#include "src/wasm/wasm-module-builder.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-objects.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-utils.h"
#include "test/common/wasm/wasm-module-runner.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace {
// Possible values for callback pointers.
enum TestValue {
kTestUsingNull, // no callback.
kTestUsingFalse, // callback returning false.
kTestUsingTrue, // callbacl returning true.
};
constexpr int kNumTestValues = 3;
const char* TestValueName[kNumTestValues] = {"null", "false", "true"};
// Defined to simplify iterating over TestValues;
const TestValue AllTestValues[kNumTestValues] = {
kTestUsingNull, kTestUsingFalse, kTestUsingTrue};
// This list holds the results of setting allow_wasm_code_gen_callback using
// TestValue's. The value in the list is true if code gen is
// allowed, and false otherwise.
const bool ExpectedResults[kNumTestValues] = {true, false, true};
bool TrueCallback(Local<v8::Context>, Local<v8::String>) { return true; }
bool FalseCallback(Local<v8::Context>, Local<v8::String>) { return false; }
using CallbackFn = bool (*)(Local<v8::Context>, Local<v8::String>);
// Defines the Callback to use for the corresponding TestValue.
CallbackFn Callback[kNumTestValues] = {nullptr, FalseCallback, TrueCallback};
void BuildTrivialModule(Zone* zone, ZoneBuffer* buffer) {
WasmModuleBuilder* builder = zone->New<WasmModuleBuilder>(zone);
builder->WriteTo(buffer);
}
bool TestModule(Isolate* isolate, v8::MemorySpan<const uint8_t> wire_bytes) {
HandleScope scope(isolate);
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
v8::Local<v8::Context> context = Utils::ToLocal(isolate->native_context());
// Get the "WebAssembly.Module" function.
auto get_property = [context, v8_isolate](
v8::Local<v8::Object> obj,
const char* property_name) -> v8::Local<v8::Object> {
auto name = v8::String::NewFromUtf8(v8_isolate, property_name,
NewStringType::kInternalized)
.ToLocalChecked();
return obj->Get(context, name).ToLocalChecked().As<v8::Object>();
};
auto wasm_class = get_property(context->Global(), "WebAssembly");
auto module_class = get_property(wasm_class, "Module");
// Create an arraybuffer with the wire bytes.
v8::Local<v8::ArrayBuffer> buf =
v8::ArrayBuffer::New(v8_isolate, wire_bytes.size());
memcpy(static_cast<uint8_t*>(buf->GetBackingStore()->Data()),
wire_bytes.data(), wire_bytes.size());
// Now call the "WebAssembly.Module" function with the array buffer. Return
// true if this succeeded, false otherwise.
v8::TryCatch try_catch(v8_isolate);
v8::Local<v8::Value> args[] = {buf};
MaybeLocal<Value> module_object =
module_class->CallAsConstructor(context, arraysize(args), args);
CHECK_EQ(try_catch.HasCaught(), module_object.IsEmpty());
return !module_object.IsEmpty();
}
} // namespace
TEST(PropertiesOfCodegenCallbacks) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
ZoneBuffer buffer(&zone);
BuildTrivialModule(&zone, &buffer);
v8::MemorySpan<const uint8_t> wire_bytes = {buffer.begin(), buffer.size()};
Isolate* isolate = CcTest::InitIsolateOnce();
v8::Isolate* v8_isolate = CcTest::isolate();
HandleScope scope(isolate);
for (TestValue wasm_codegen : AllTestValues) {
fprintf(stderr, "Test wasm_codegen = %s\n", TestValueName[wasm_codegen]);
v8_isolate->SetAllowWasmCodeGenerationCallback(Callback[wasm_codegen]);
bool found = TestModule(isolate, wire_bytes);
bool expected = ExpectedResults[wasm_codegen];
CHECK_EQ(expected, found);
heap::InvokeMemoryReducingMajorGCs(isolate->heap());
}
}
TEST(WasmModuleObjectCompileFailure) {
const uint8_t wire_bytes_arr[] = {0xDE, 0xAD, 0xBE, 0xEF};
v8::MemorySpan<const uint8_t> wire_bytes = {wire_bytes_arr,
arraysize(wire_bytes_arr)};
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
CHECK(!TestModule(isolate, wire_bytes));
}
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,180 @@
// Copyright 2018 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 "src/compiler/wasm-compiler.h"
#include "src/wasm/function-compiler.h"
#include "src/wasm/module-compiler.h"
#include "src/wasm/wasm-code-manager.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-import-wrapper-cache.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects.h"
#include "test/cctest/cctest.h"
#include "test/common/flag-utils.h"
#include "test/common/wasm/test-signatures.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace test_wasm_import_wrapper_cache {
std::shared_ptr<NativeModule> NewModule(Isolate* isolate) {
auto module = std::make_shared<WasmModule>(kWasmOrigin);
size_t kCodeSizeEstimate = 0;
auto native_module = GetWasmEngine()->NewNativeModule(
isolate, WasmEnabledFeatures::All(), WasmDetectedFeatures{},
CompileTimeImports{}, std::move(module), kCodeSizeEstimate);
native_module->SetWireBytes({});
return native_module;
}
TEST(CacheHit) {
FlagScope<bool> cleanup_immediately(&v8_flags.stress_wasm_code_gc, true);
Isolate* isolate = CcTest::InitIsolateOnce();
auto module = NewModule(isolate);
TestSignatures sigs;
auto kind = ImportCallKind::kJSFunctionArityMatch;
auto sig = sigs.i_i();
CanonicalTypeIndex type_index =
GetTypeCanonicalizer()->AddRecursiveGroup(sig);
int expected_arity = static_cast<int>(sig->parameter_count());
auto* canonical_sig =
GetTypeCanonicalizer()->LookupFunctionSignature(type_index);
{
WasmCodeRefScope wasm_code_ref_scope;
WasmCode* c1 =
CompileImportWrapperForTest(isolate, module.get(), kind, canonical_sig,
type_index, expected_arity, kNoSuspend);
CHECK_NOT_NULL(c1);
CHECK_EQ(WasmCode::Kind::kWasmToJsWrapper, c1->kind());
WasmCode* c2 = GetWasmImportWrapperCache()->MaybeGet(
kind, type_index, expected_arity, kNoSuspend);
CHECK_NOT_NULL(c2);
CHECK_EQ(c1, c2);
}
// Ending the lifetime of the {WasmCodeRefScope} should drop the refcount
// of the wrapper to zero, causing its cleanup at the next Wasm Code GC
// (requested via interrupt).
isolate->stack_guard()->HandleInterrupts();
CHECK_NULL(GetWasmImportWrapperCache()->MaybeGet(kind, type_index,
expected_arity, kNoSuspend));
}
TEST(CacheMissSig) {
Isolate* isolate = CcTest::InitIsolateOnce();
auto module = NewModule(isolate);
TestSignatures sigs;
WasmCodeRefScope wasm_code_ref_scope;
auto kind = ImportCallKind::kJSFunctionArityMatch;
auto* sig1 = sigs.i_i();
int expected_arity1 = static_cast<int>(sig1->parameter_count());
CanonicalTypeIndex type_index1 =
GetTypeCanonicalizer()->AddRecursiveGroup(sig1);
auto* canonical_sig1 =
GetTypeCanonicalizer()->LookupFunctionSignature(type_index1);
auto sig2 = sigs.i_ii();
int expected_arity2 = static_cast<int>(sig2->parameter_count());
CanonicalTypeIndex type_index2 =
GetTypeCanonicalizer()->AddRecursiveGroup(sig2);
WasmCode* c1 =
CompileImportWrapperForTest(isolate, module.get(), kind, canonical_sig1,
type_index1, expected_arity1, kNoSuspend);
CHECK_NOT_NULL(c1);
CHECK_EQ(WasmCode::Kind::kWasmToJsWrapper, c1->kind());
WasmCode* c2 = GetWasmImportWrapperCache()->MaybeGet(
kind, type_index2, expected_arity2, kNoSuspend);
CHECK_NULL(c2);
}
TEST(CacheMissKind) {
Isolate* isolate = CcTest::InitIsolateOnce();
auto module = NewModule(isolate);
TestSignatures sigs;
WasmCodeRefScope wasm_code_ref_scope;
auto kind1 = ImportCallKind::kJSFunctionArityMatch;
auto kind2 = ImportCallKind::kJSFunctionArityMismatch;
auto sig = sigs.i_i();
int expected_arity = static_cast<int>(sig->parameter_count());
CanonicalTypeIndex type_index =
GetTypeCanonicalizer()->AddRecursiveGroup(sig);
auto* canonical_sig =
GetTypeCanonicalizer()->LookupFunctionSignature(type_index);
WasmCode* c1 =
CompileImportWrapperForTest(isolate, module.get(), kind1, canonical_sig,
type_index, expected_arity, kNoSuspend);
CHECK_NOT_NULL(c1);
CHECK_EQ(WasmCode::Kind::kWasmToJsWrapper, c1->kind());
WasmCode* c2 = GetWasmImportWrapperCache()->MaybeGet(
kind2, type_index, expected_arity, kNoSuspend);
CHECK_NULL(c2);
}
TEST(CacheHitMissSig) {
Isolate* isolate = CcTest::InitIsolateOnce();
auto module = NewModule(isolate);
TestSignatures sigs;
WasmCodeRefScope wasm_code_ref_scope;
auto kind = ImportCallKind::kJSFunctionArityMatch;
auto sig1 = sigs.i_i();
int expected_arity1 = static_cast<int>(sig1->parameter_count());
CanonicalTypeIndex type_index1 =
GetTypeCanonicalizer()->AddRecursiveGroup(sig1);
auto* canonical_sig1 =
GetTypeCanonicalizer()->LookupFunctionSignature(type_index1);
auto sig2 = sigs.i_ii();
int expected_arity2 = static_cast<int>(sig2->parameter_count());
CanonicalTypeIndex type_index2 =
GetTypeCanonicalizer()->AddRecursiveGroup(sig2);
auto* canonical_sig2 =
GetTypeCanonicalizer()->LookupFunctionSignature(type_index2);
WasmCode* c1 =
CompileImportWrapperForTest(isolate, module.get(), kind, canonical_sig1,
type_index1, expected_arity1, kNoSuspend);
CHECK_NOT_NULL(c1);
CHECK_EQ(WasmCode::Kind::kWasmToJsWrapper, c1->kind());
WasmCode* c2 = GetWasmImportWrapperCache()->MaybeGet(
kind, type_index2, expected_arity2, kNoSuspend);
CHECK_NULL(c2);
c2 = CompileImportWrapperForTest(isolate, module.get(), kind, canonical_sig2,
type_index2, expected_arity2, kNoSuspend);
CHECK_NE(c1, c2);
WasmCode* c3 = GetWasmImportWrapperCache()->MaybeGet(
kind, type_index1, expected_arity1, kNoSuspend);
CHECK_NOT_NULL(c3);
CHECK_EQ(c1, c3);
WasmCode* c4 = GetWasmImportWrapperCache()->MaybeGet(
kind, type_index2, expected_arity2, kNoSuspend);
CHECK_NOT_NULL(c4);
CHECK_EQ(c2, c4);
}
} // namespace test_wasm_import_wrapper_cache
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,348 @@
// Copyright 2020 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 <memory>
#include "include/libplatform/libplatform.h"
#include "include/v8-metrics.h"
#include "include/v8-platform.h"
#include "src/api/api-inl.h"
#include "src/base/platform/time.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-module-builder.h"
#include "test/cctest/cctest.h"
#include "test/common/wasm/flag-utils.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
#include "test/common/wasm/wasm-module-runner.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace {
class MockPlatform final : public TestPlatform {
public:
MockPlatform()
: no_memory_reducer_(&v8_flags.memory_reducer, false),
task_runner_(std::make_shared<MockTaskRunner>()) {}
~MockPlatform() override {
for (auto* job_handle : job_handles_) job_handle->ResetPlatform();
}
std::unique_ptr<v8::JobHandle> CreateJobImpl(
v8::TaskPriority priority, std::unique_ptr<v8::JobTask> job_task,
const v8::SourceLocation& location) override {
auto orig_job_handle = v8::platform::NewDefaultJobHandle(
this, priority, std::move(job_task), 1);
auto job_handle =
std::make_unique<MockJobHandle>(std::move(orig_job_handle), this);
job_handles_.insert(job_handle.get());
return job_handle;
}
std::shared_ptr<TaskRunner> GetForegroundTaskRunner(
v8::Isolate* isolate, v8::TaskPriority) override {
return task_runner_;
}
void PostTaskOnWorkerThreadImpl(v8::TaskPriority priority,
std::unique_ptr<v8::Task> task,
const v8::SourceLocation& location) override {
task_runner_->PostTask(std::move(task));
}
bool IdleTasksEnabled(v8::Isolate* isolate) override { return false; }
void ExecuteTasks() {
task_runner_->ExecuteTasks();
}
private:
class MockTaskRunner final : public TaskRunner {
public:
void PostTaskImpl(std::unique_ptr<v8::Task> task,
const SourceLocation& location) override {
base::MutexGuard lock_scope(&tasks_lock_);
tasks_.push(std::move(task));
}
void PostNonNestableTaskImpl(std::unique_ptr<Task> task,
const SourceLocation& location) override {
PostTask(std::move(task));
}
void PostDelayedTaskImpl(std::unique_ptr<Task> task,
double delay_in_seconds,
const SourceLocation& location) override {
PostTask(std::move(task));
}
void PostNonNestableDelayedTaskImpl(
std::unique_ptr<Task> task, double delay_in_seconds,
const SourceLocation& location) override {
PostTask(std::move(task));
}
void PostIdleTaskImpl(std::unique_ptr<IdleTask> task,
const SourceLocation& location) override {
UNREACHABLE();
}
bool IdleTasksEnabled() override { return false; }
bool NonNestableTasksEnabled() const override { return true; }
bool NonNestableDelayedTasksEnabled() const override { return true; }
void ExecuteTasks() {
std::queue<std::unique_ptr<v8::Task>> tasks;
while (true) {
{
base::MutexGuard lock_scope(&tasks_lock_);
tasks.swap(tasks_);
}
if (tasks.empty()) break;
while (!tasks.empty()) {
std::unique_ptr<Task> task = std::move(tasks.front());
tasks.pop();
task->Run();
}
}
}
private:
base::Mutex tasks_lock_;
// We do not execute tasks concurrently, so we only need one list of tasks.
std::queue<std::unique_ptr<v8::Task>> tasks_;
};
class MockJobHandle : public JobHandle {
public:
explicit MockJobHandle(std::unique_ptr<JobHandle> orig_handle,
MockPlatform* platform)
: orig_handle_(std::move(orig_handle)), platform_(platform) {}
~MockJobHandle() {
if (platform_) platform_->job_handles_.erase(this);
}
void ResetPlatform() { platform_ = nullptr; }
void NotifyConcurrencyIncrease() override {
orig_handle_->NotifyConcurrencyIncrease();
}
void Join() override { orig_handle_->Join(); }
void Cancel() override { orig_handle_->Cancel(); }
void CancelAndDetach() override { orig_handle_->CancelAndDetach(); }
bool IsValid() override { return orig_handle_->IsValid(); }
bool IsActive() override { return orig_handle_->IsActive(); }
private:
std::unique_ptr<JobHandle> orig_handle_;
MockPlatform* platform_;
};
FlagScope<bool> no_memory_reducer_;
std::shared_ptr<MockTaskRunner> task_runner_;
std::unordered_set<MockJobHandle*> job_handles_;
};
enum class CompilationStatus {
kPending,
kFinished,
kFailed,
};
class TestInstantiateResolver : public InstantiationResultResolver {
public:
TestInstantiateResolver(Isolate* isolate, CompilationStatus* status,
std::string* error_message)
: isolate_(isolate), status_(status), error_message_(error_message) {}
void OnInstantiationSucceeded(
i::DirectHandle<i::WasmInstanceObject> instance) override {
*status_ = CompilationStatus::kFinished;
}
void OnInstantiationFailed(i::DirectHandle<i::JSAny> error_reason) override {
*status_ = CompilationStatus::kFailed;
DirectHandle<String> str =
Object::ToString(isolate_, error_reason).ToHandleChecked();
error_message_->assign(str->ToCString().get());
}
private:
Isolate* isolate_;
CompilationStatus* const status_;
std::string* const error_message_;
};
class TestCompileResolver : public CompilationResultResolver {
public:
TestCompileResolver(CompilationStatus* status, std::string* error_message,
Isolate* isolate,
std::shared_ptr<NativeModule>* native_module)
: status_(status),
error_message_(error_message),
isolate_(isolate),
native_module_(native_module) {}
void OnCompilationSucceeded(
i::DirectHandle<i::WasmModuleObject> module) override {
if (!module.is_null()) {
*native_module_ = module->shared_native_module();
GetWasmEngine()->AsyncInstantiate(
isolate_,
std::make_unique<TestInstantiateResolver>(isolate_, status_,
error_message_),
module, MaybeDirectHandle<JSReceiver>());
}
}
void OnCompilationFailed(i::DirectHandle<i::JSAny> error_reason) override {
*status_ = CompilationStatus::kFailed;
DirectHandle<String> str =
Object::ToString(CcTest::i_isolate(), error_reason).ToHandleChecked();
error_message_->assign(str->ToCString().get());
}
private:
CompilationStatus* const status_;
std::string* const error_message_;
Isolate* isolate_;
std::shared_ptr<NativeModule>* const native_module_;
};
} // namespace
#define RUN_COMPILE(name) \
v8::HandleScope handle_scope(CcTest::isolate()); \
v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate()); \
v8::Context::Scope context_scope(context); \
Isolate* i_isolate = CcTest::i_isolate(); \
testing::SetupIsolateForWasmModule(i_isolate); \
RunCompile_##name(&platform, i_isolate);
#define COMPILE_TEST(name) \
void RunCompile_##name(MockPlatform*, i::Isolate*); \
TEST_WITH_PLATFORM(Sync##name, MockPlatform) { \
i::FlagScope<bool> sync_scope(&i::v8_flags.wasm_async_compilation, false); \
RUN_COMPILE(name); \
} \
\
TEST_WITH_PLATFORM(Async##name, MockPlatform) { RUN_COMPILE(name); } \
\
TEST_WITH_PLATFORM(Streaming##name, MockPlatform) { \
i::FlagScope<bool> streaming_scope(&i::v8_flags.wasm_test_streaming, \
true); \
RUN_COMPILE(name); \
} \
void RunCompile_##name(MockPlatform* platform, i::Isolate* isolate)
class MetricsRecorder : public v8::metrics::Recorder {
public:
std::vector<v8::metrics::WasmModuleDecoded> module_decoded_;
std::vector<v8::metrics::WasmModuleCompiled> module_compiled_;
std::vector<v8::metrics::WasmModuleInstantiated> module_instantiated_;
void AddMainThreadEvent(const v8::metrics::WasmModuleDecoded& event,
v8::metrics::Recorder::ContextId id) override {
CHECK(!id.IsEmpty());
module_decoded_.emplace_back(event);
}
void AddMainThreadEvent(const v8::metrics::WasmModuleCompiled& event,
v8::metrics::Recorder::ContextId id) override {
CHECK(!id.IsEmpty());
module_compiled_.emplace_back(event);
}
void AddMainThreadEvent(const v8::metrics::WasmModuleInstantiated& event,
v8::metrics::Recorder::ContextId id) override {
CHECK(!id.IsEmpty());
module_instantiated_.emplace_back(event);
}
};
COMPILE_TEST(TestEventMetrics) {
if (v8_flags.memory_balancer) return;
FlagScope<bool> no_wasm_dynamic_tiering(&v8_flags.wasm_dynamic_tiering,
false);
std::shared_ptr<MetricsRecorder> recorder =
std::make_shared<MetricsRecorder>();
reinterpret_cast<v8::Isolate*>(isolate)->SetMetricsRecorder(recorder);
if (v8::base::ThreadTicks::IsSupported()) {
v8::base::ThreadTicks::WaitUntilInitialized();
}
TestSignatures sigs;
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
f->builder()->AddExport(base::CStrVector("main"), f);
f->EmitCode({WASM_I32V_2(0), WASM_END});
ZoneBuffer buffer(&zone);
builder->WriteTo(&buffer);
auto enabled_features = WasmEnabledFeatures::FromIsolate(isolate);
CompilationStatus status = CompilationStatus::kPending;
std::string error_message;
std::shared_ptr<NativeModule> native_module;
base::OwnedVector<const uint8_t> bytes = base::OwnedCopyOf(buffer);
GetWasmEngine()->AsyncCompile(
isolate, enabled_features, CompileTimeImports{},
std::make_shared<TestCompileResolver>(&status, &error_message, isolate,
&native_module),
std::move(bytes), "CompileAndInstantiateWasmModuleForTesting");
// Finish compilation tasks.
while (status == CompilationStatus::kPending) {
platform->ExecuteTasks();
}
platform->ExecuteTasks(); // Complete pending tasks beyond compilation.
CHECK_EQ(CompilationStatus::kFinished, status);
CHECK_EQ(1, recorder->module_decoded_.size());
CHECK(recorder->module_decoded_.back().success);
CHECK_EQ(i::v8_flags.wasm_async_compilation,
recorder->module_decoded_.back().async);
CHECK_EQ(i::v8_flags.wasm_test_streaming,
recorder->module_decoded_.back().streamed);
CHECK_EQ(buffer.size(),
recorder->module_decoded_.back().module_size_in_bytes);
CHECK_EQ(1, recorder->module_decoded_.back().function_count);
CHECK_LE(0, recorder->module_decoded_.back().wall_clock_duration_in_us);
CHECK_EQ(1, recorder->module_compiled_.size());
CHECK(recorder->module_compiled_.back().success);
CHECK_EQ(i::v8_flags.wasm_async_compilation,
recorder->module_compiled_.back().async);
CHECK_EQ(i::v8_flags.wasm_test_streaming,
recorder->module_compiled_.back().streamed);
CHECK(!recorder->module_compiled_.back().cached);
CHECK(!recorder->module_compiled_.back().deserialized);
CHECK_EQ(v8_flags.wasm_lazy_compilation,
recorder->module_compiled_.back().lazy);
CHECK_LT(0, recorder->module_compiled_.back().code_size_in_bytes);
// We currently cannot ensure that no code is attributed to Liftoff after the
// WasmModuleCompiled event has been emitted. We therefore only assume the
// liftoff_code_size() to be an upper limit for the reported size.
CHECK_GE(native_module->liftoff_code_size(),
recorder->module_compiled_.back().code_size_in_bytes);
CHECK_GE(native_module->generated_code_size(),
recorder->module_compiled_.back().code_size_in_bytes);
CHECK_LE(0, recorder->module_compiled_.back().wall_clock_duration_in_us);
CHECK_EQ(1, recorder->module_instantiated_.size());
CHECK(recorder->module_instantiated_.back().success);
// We currently don't support true async instantiation.
CHECK(!recorder->module_instantiated_.back().async);
CHECK_EQ(0, recorder->module_instantiated_.back().imported_function_count);
CHECK_LE(0, recorder->module_instantiated_.back().wall_clock_duration_in_us);
}
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,804 @@
// Copyright 2015 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 <stdlib.h>
#include <string.h>
#include "include/v8-wasm.h"
#include "src/api/api-inl.h"
#include "src/objects/objects-inl.h"
#include "src/snapshot/code-serializer.h"
#include "src/utils/version.h"
#include "src/wasm/module-decoder.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-module-builder.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-opcodes.h"
#include "src/wasm/wasm-serialization.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-utils.h"
#include "test/common/wasm/flag-utils.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
#include "test/common/wasm/wasm-module-runner.h"
namespace v8::internal::wasm {
// Approximate gtest TEST_F style, in case we adopt gtest.
class WasmSerializationTest {
public:
WasmSerializationTest() : zone_(&allocator_, ZONE_NAME) {
// Don't call here if we move to gtest.
SetUp();
}
static constexpr const char* kFunctionName = "increment";
static void BuildWireBytes(Zone* zone, ZoneBuffer* buffer) {
WasmModuleBuilder* builder = zone->New<WasmModuleBuilder>(zone);
TestSignatures sigs;
// Generate 3 functions, and export the last one with the name "increment".
WasmFunctionBuilder* f;
for (int i = 0; i < 3; ++i) {
f = builder->AddFunction(sigs.i_i());
f->EmitCode({WASM_LOCAL_GET(0), kExprI32Const, 1, kExprI32Add, kExprEnd});
}
builder->AddExport(base::CStrVector(kFunctionName), f);
builder->WriteTo(buffer);
}
void ClearSerializedData() { serialized_bytes_ = {}; }
void InvalidateVersion() {
uint32_t* slot = reinterpret_cast<uint32_t*>(
const_cast<uint8_t*>(serialized_bytes_.data()) +
WasmSerializer::kVersionHashOffset);
*slot = Version::Hash() + 1;
}
void InvalidateWireBytes() {
memset(const_cast<uint8_t*>(wire_bytes_.data()), 0, wire_bytes_.size() / 2);
}
void PartlyDropTieringBudget() {
serialized_bytes_ = {serialized_bytes_.data(),
serialized_bytes_.size() - 1};
}
MaybeDirectHandle<WasmModuleObject> Deserialize(
base::Vector<const char> source_url = {}) {
return DeserializeNativeModule(
CcTest::i_isolate(), base::VectorOf(serialized_bytes_),
base::VectorOf(wire_bytes_), compile_imports_, source_url);
}
void DeserializeAndRun() {
ErrorThrower thrower(CcTest::i_isolate(), "");
DirectHandle<WasmModuleObject> module_object;
CHECK(Deserialize().ToHandle(&module_object));
{
DisallowGarbageCollection assume_no_gc;
base::Vector<const uint8_t> deserialized_module_wire_bytes =
module_object->native_module()->wire_bytes();
CHECK_EQ(deserialized_module_wire_bytes.size(), wire_bytes_.size());
CHECK_EQ(memcmp(deserialized_module_wire_bytes.begin(),
wire_bytes_.data(), wire_bytes_.size()),
0);
}
DirectHandle<WasmInstanceObject> instance =
GetWasmEngine()
->SyncInstantiate(CcTest::i_isolate(), &thrower, module_object,
DirectHandle<JSReceiver>::null(),
MaybeDirectHandle<JSArrayBuffer>())
.ToHandleChecked();
DirectHandle<Object> params[] = {
direct_handle(Smi::FromInt(41), CcTest::i_isolate())};
int32_t result = testing::CallWasmFunctionForTesting(
CcTest::i_isolate(), instance, kFunctionName,
base::ArrayVector(params));
CHECK_EQ(42, result);
}
void CollectGarbage() {
// Try hard to collect all garbage and will therefore also invoke all weak
// callbacks of actually unreachable persistent handles.
heap::InvokeMemoryReducingMajorGCs(CcTest::heap());
}
v8::MemorySpan<const uint8_t> wire_bytes() const { return wire_bytes_; }
CompileTimeImports MakeCompileTimeImports() { return CompileTimeImports{}; }
private:
Zone* zone() { return &zone_; }
void SetUp() {
CcTest::InitIsolateOnce();
ZoneBuffer buffer(&zone_);
WasmSerializationTest::BuildWireBytes(zone(), &buffer);
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator =
CcTest::i_isolate()->array_buffer_allocator();
v8::Isolate* serialization_v8_isolate = v8::Isolate::New(create_params);
Isolate* serialization_isolate =
reinterpret_cast<Isolate*>(serialization_v8_isolate);
ErrorThrower thrower(serialization_isolate, "");
// Keep a weak pointer so we can check that the native module dies after
// serialization (when the isolate is disposed).
std::weak_ptr<NativeModule> weak_native_module;
{
v8::Isolate::Scope isolate_scope(serialization_v8_isolate);
HandleScope scope(serialization_isolate);
v8::Local<v8::Context> serialization_context =
v8::Context::New(serialization_v8_isolate);
serialization_context->Enter();
auto enabled_features =
WasmEnabledFeatures::FromIsolate(serialization_isolate);
MaybeDirectHandle<WasmModuleObject> maybe_module_object =
GetWasmEngine()->SyncCompile(serialization_isolate, enabled_features,
MakeCompileTimeImports(), &thrower,
base::OwnedCopyOf(buffer));
DirectHandle<WasmModuleObject> module_object =
maybe_module_object.ToHandleChecked();
weak_native_module = module_object->shared_native_module();
// Check that the native module exists at this point.
CHECK(weak_native_module.lock());
v8::Local<v8::Object> v8_module_obj =
v8::Utils::ToLocal(Cast<JSObject>(module_object));
CHECK(v8_module_obj->IsWasmModuleObject());
v8::Local<v8::WasmModuleObject> v8_module_object =
v8_module_obj.As<v8::WasmModuleObject>();
v8::CompiledWasmModule compiled_module =
v8_module_object->GetCompiledModule();
v8::MemorySpan<const uint8_t> uncompiled_bytes =
compiled_module.GetWireBytesRef();
uint8_t* bytes_copy =
zone()->AllocateArray<uint8_t>(uncompiled_bytes.size());
memcpy(bytes_copy, uncompiled_bytes.data(), uncompiled_bytes.size());
wire_bytes_ = {bytes_copy, uncompiled_bytes.size()};
// Run the code until tier-up (of the single function) was observed.
DirectHandle<WasmInstanceObject> instance =
GetWasmEngine()
->SyncInstantiate(serialization_isolate, &thrower, module_object,
{}, {})
.ToHandleChecked();
CHECK_EQ(0, data_.size);
while (data_.size == 0) {
testing::CallWasmFunctionForTesting(serialization_isolate, instance,
kFunctionName, {});
data_ = compiled_module.Serialize();
}
CHECK_LT(0, data_.size);
}
// Dispose of serialization isolate to destroy the reference to the
// NativeModule, which removes it from the module cache in the wasm engine
// and forces de-serialization in the new isolate.
serialization_v8_isolate->Dispose();
// Busy-wait for the NativeModule to really die. Background threads might
// temporarily keep it alive (happens very rarely, see
// https://crbug.com/v8/10148).
while (weak_native_module.lock()) {
}
serialized_bytes_ = {data_.buffer.get(), data_.size};
v8::HandleScope new_scope(CcTest::isolate());
v8::Local<v8::Context> deserialization_context =
v8::Context::New(CcTest::isolate());
deserialization_context->Enter();
}
v8::internal::AccountingAllocator allocator_;
Zone zone_;
// TODO(14179): Add tests for de/serializing modules with compile-time
// imports.
CompileTimeImports compile_imports_;
v8::OwnedBuffer data_;
v8::MemorySpan<const uint8_t> wire_bytes_ = {nullptr, 0};
v8::MemorySpan<const uint8_t> serialized_bytes_ = {nullptr, 0};
FlagScope<int> tier_up_quickly_{&v8_flags.wasm_tiering_budget, 1000};
};
TEST(DeserializeValidModule) {
WasmSerializationTest test;
{
HandleScope scope(CcTest::i_isolate());
test.DeserializeAndRun();
}
test.CollectGarbage();
}
TEST(DeserializeWithSourceUrl) {
WasmSerializationTest test;
{
HandleScope scope(CcTest::i_isolate());
const std::string url = "http://example.com/example.wasm";
DirectHandle<WasmModuleObject> module_object;
CHECK(test.Deserialize(base::VectorOf(url)).ToHandle(&module_object));
Tagged<String> url_str = Cast<String>(module_object->script()->name());
CHECK_EQ(url, url_str->ToCString().get());
}
test.CollectGarbage();
}
TEST(DeserializeMismatchingVersion) {
WasmSerializationTest test;
{
HandleScope scope(CcTest::i_isolate());
test.InvalidateVersion();
CHECK(test.Deserialize().is_null());
}
test.CollectGarbage();
}
TEST(DeserializeNoSerializedData) {
WasmSerializationTest test;
{
HandleScope scope(CcTest::i_isolate());
test.ClearSerializedData();
CHECK(test.Deserialize().is_null());
}
test.CollectGarbage();
}
TEST(DeserializeWireBytesAndSerializedDataInvalid) {
WasmSerializationTest test;
{
HandleScope scope(CcTest::i_isolate());
test.InvalidateVersion();
test.InvalidateWireBytes();
CHECK(test.Deserialize().is_null());
}
test.CollectGarbage();
}
bool False(v8::Local<v8::Context> context, v8::Local<v8::String> source) {
return false;
}
TEST(BlockWasmCodeGenAtDeserialization) {
WasmSerializationTest test;
{
HandleScope scope(CcTest::i_isolate());
CcTest::isolate()->SetAllowWasmCodeGenerationCallback(False);
CHECK(test.Deserialize().is_null());
}
test.CollectGarbage();
}
UNINITIALIZED_TEST(CompiledWasmModulesTransfer) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
ZoneBuffer buffer(&zone);
WasmSerializationTest::BuildWireBytes(&zone, &buffer);
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* from_isolate = v8::Isolate::New(create_params);
std::vector<v8::CompiledWasmModule> store;
std::shared_ptr<NativeModule> original_native_module;
{
v8::Isolate::Scope isolate_scope(from_isolate);
v8::HandleScope scope(from_isolate);
LocalContext env(from_isolate);
Isolate* from_i_isolate = reinterpret_cast<Isolate*>(from_isolate);
testing::SetupIsolateForWasmModule(from_i_isolate);
ErrorThrower thrower(from_i_isolate, "TestCompiledWasmModulesTransfer");
auto enabled_features = WasmEnabledFeatures::FromIsolate(from_i_isolate);
MaybeDirectHandle<WasmModuleObject> maybe_module_object =
GetWasmEngine()->SyncCompile(from_i_isolate, enabled_features,
CompileTimeImports{}, &thrower,
base::OwnedCopyOf(buffer));
DirectHandle<WasmModuleObject> module_object =
maybe_module_object.ToHandleChecked();
v8::Local<v8::WasmModuleObject> v8_module =
v8::Local<v8::WasmModuleObject>::Cast(
v8::Utils::ToLocal(Cast<JSObject>(module_object)));
store.push_back(v8_module->GetCompiledModule());
original_native_module = module_object->shared_native_module();
}
{
v8::Isolate* to_isolate = v8::Isolate::New(create_params);
{
v8::Isolate::Scope isolate_scope(to_isolate);
v8::HandleScope scope(to_isolate);
LocalContext env(to_isolate);
v8::MaybeLocal<v8::WasmModuleObject> transferred_module =
v8::WasmModuleObject::FromCompiledModule(to_isolate, store[0]);
CHECK(!transferred_module.IsEmpty());
DirectHandle<WasmModuleObject> module_object = Cast<WasmModuleObject>(
v8::Utils::OpenDirectHandle(*transferred_module.ToLocalChecked()));
std::shared_ptr<NativeModule> transferred_native_module =
module_object->shared_native_module();
CHECK_EQ(original_native_module, transferred_native_module);
}
to_isolate->Dispose();
}
original_native_module.reset();
from_isolate->Dispose();
}
TEST(TierDownAfterDeserialization) {
WasmSerializationTest test;
Isolate* isolate = CcTest::i_isolate();
HandleScope scope(isolate);
DirectHandle<WasmModuleObject> module_object;
CHECK(test.Deserialize().ToHandle(&module_object));
auto* native_module = module_object->native_module();
CHECK_EQ(3, native_module->module()->functions.size());
WasmCodeRefScope code_ref_scope;
// The deserialized code must be TurboFan (we wait for tier-up before
// serializing).
auto* turbofan_code = native_module->GetCode(2);
CHECK_NOT_NULL(turbofan_code);
CHECK_EQ(ExecutionTier::kTurbofan, turbofan_code->tier());
GetWasmEngine()->EnterDebuggingForIsolate(isolate);
// Entering debugging should delete all code, so that debug code gets compiled
// lazily.
CHECK_NULL(native_module->GetCode(0));
}
TEST(SerializeLiftoffModuleFails) {
// Make sure that no function is tiered up to TurboFan.
if (!v8_flags.liftoff) return;
FlagScope<bool> no_tier_up(&v8_flags.wasm_tier_up, false);
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, "test_zone");
CcTest::InitIsolateOnce();
Isolate* isolate = CcTest::i_isolate();
HandleScope scope(isolate);
ZoneBuffer wire_bytes_buffer(&zone);
WasmSerializationTest::BuildWireBytes(&zone, &wire_bytes_buffer);
ErrorThrower thrower(isolate, "Test");
MaybeDirectHandle<WasmModuleObject> maybe_module_object =
GetWasmEngine()->SyncCompile(isolate, WasmEnabledFeatures::All(),
CompileTimeImports{}, &thrower,
base::OwnedCopyOf(wire_bytes_buffer));
DirectHandle<WasmModuleObject> module_object =
maybe_module_object.ToHandleChecked();
NativeModule* native_module = module_object->native_module();
WasmSerializer wasm_serializer(native_module);
size_t buffer_size = wasm_serializer.GetSerializedNativeModuleSize();
std::unique_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
// Serialization is expected to fail if there is no TurboFan function to
// serialize.
CHECK(!wasm_serializer.SerializeNativeModule({buffer.get(), buffer_size}));
}
TEST(SerializeTieringBudget) {
WasmSerializationTest test;
Isolate* isolate = CcTest::i_isolate();
v8::OwnedBuffer serialized_bytes;
uint32_t mock_budget[3]{1, 2, 3};
{
HandleScope scope(isolate);
DirectHandle<WasmModuleObject> module_object;
CHECK(test.Deserialize().ToHandle(&module_object));
auto* native_module = module_object->native_module();
memcpy(native_module->tiering_budget_array(), mock_budget,
arraysize(mock_budget) * sizeof(uint32_t));
v8::Local<v8::Object> v8_module_obj =
v8::Utils::ToLocal(Cast<JSObject>(module_object));
CHECK(v8_module_obj->IsWasmModuleObject());
v8::Local<v8::WasmModuleObject> v8_module_object =
v8_module_obj.As<v8::WasmModuleObject>();
serialized_bytes = v8_module_object->GetCompiledModule().Serialize();
// Change one entry in the tiering budget after serialization to make sure
// the module gets deserialized and not just loaded from the module cache.
native_module->tiering_budget_array()[0]++;
}
// We need to invoke GC without stack, otherwise some objects may survive.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(
isolate->heap());
test.CollectGarbage();
HandleScope scope(isolate);
DirectHandle<WasmModuleObject> module_object;
CompileTimeImports compile_imports = test.MakeCompileTimeImports();
CHECK(
DeserializeNativeModule(
isolate,
base::VectorOf(serialized_bytes.buffer.get(), serialized_bytes.size),
base::VectorOf(test.wire_bytes()), compile_imports, {})
.ToHandle(&module_object));
auto* native_module = module_object->native_module();
for (size_t i = 0; i < arraysize(mock_budget); ++i) {
CHECK_EQ(mock_budget[i], native_module->tiering_budget_array()[i]);
}
}
TEST(DeserializeTieringBudgetPartlyMissing) {
WasmSerializationTest test;
{
HandleScope scope(CcTest::i_isolate());
test.PartlyDropTieringBudget();
CHECK(test.Deserialize().is_null());
}
test.CollectGarbage();
}
TEST(SerializationFailsOnChangedFlags) {
WasmSerializationTest test;
{
HandleScope scope(CcTest::i_isolate());
FlagScope<bool> no_bounds_checks(&v8_flags.wasm_bounds_checks, false);
CHECK(test.Deserialize().is_null());
FlagScope<bool> bounds_checks(&v8_flags.wasm_bounds_checks, true);
CHECK(!test.Deserialize().is_null());
}
}
TEST(SerializationFailsOnChangedFeatures) {
WasmSerializationTest test;
{
HandleScope scope(CcTest::i_isolate());
CcTest::isolate()->SetWasmImportedStringsEnabledCallback(
[](auto) { return true; });
CHECK(test.Deserialize().is_null());
CcTest::isolate()->SetWasmImportedStringsEnabledCallback(
[](auto) { return false; });
CHECK(!test.Deserialize().is_null());
}
}
TEST(DeserializeIndirectCallWithDifferentCanonicalId) {
// This test compiles and serializes a module with an indirect call, then
// resets the type canonicalizer, compiles another module, and then
// deserializes the original module. This ensures that a different canonical
// signature ID is used for the indirect call.
// We then call the deserialized module to check that the right canonical
// signature ID is being used.
// Compile with Turbofan right away.
FlagScope<bool> no_liftoff{&v8_flags.liftoff, false};
FlagScope<bool> no_lazy_compilation{&v8_flags.wasm_lazy_compilation, false};
FlagScope<bool> expose_gc{&v8_flags.expose_gc, true};
i::Isolate* i_isolate = CcTest::InitIsolateOnce();
v8::Isolate* v8_isolate = CcTest::isolate();
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
HandleScope handle_scope(i_isolate);
// Build a small module with an indirect call.
ZoneBuffer zone_buffer(&zone);
{
WasmModuleBuilder builder{&zone};
TestSignatures sigs;
// Add the "call_indirect" function which calls table0[0].
ModuleTypeIndex sig_id = builder.AddSignature(sigs.i_i(), true);
WasmFunctionBuilder* f = builder.AddFunction(sig_id);
f->EmitCode({// (i) => i != 0 ? f(i-1) : 42
WASM_IF_ELSE_I(
// cond:
WASM_LOCAL_GET(0),
// if_true:
WASM_CALL_INDIRECT(
SIG_INDEX(sig_id.index),
WASM_I32_SUB(WASM_LOCAL_GET(0), WASM_ONE), WASM_ZERO),
// if_false:
WASM_I32V_1(42)),
WASM_END});
builder.AddExport(base::CStrVector("call_indirect"), f);
// Add a function table.
uint32_t table_id = builder.AddTable(kWasmFuncRef, 1);
builder.SetIndirectFunction(
table_id, 0, f->func_index(),
WasmModuleBuilder::WasmElemSegment::kRelativeToImports);
// Write the final module into {buffer}.
builder.WriteTo(&zone_buffer);
}
// Compile the module and serialize it.
// Keep a weak pointer so we can check that the original native module died.
auto enabled_features = WasmEnabledFeatures::FromIsolate(i_isolate);
std::weak_ptr<NativeModule> weak_native_module;
v8::OwnedBuffer serialized_module;
CanonicalTypeIndex canonical_sig_id_before_serialization;
{
ErrorThrower thrower(i_isolate, "");
{
v8::Isolate::Scope isolate_scope(v8_isolate);
HandleScope scope(i_isolate);
v8::Local<v8::Context> serialization_context =
v8::Context::New(v8_isolate);
serialization_context->Enter();
DirectHandle<WasmModuleObject> module_object =
GetWasmEngine()
->SyncCompile(i_isolate, enabled_features, CompileTimeImports{},
&thrower, base::OwnedCopyOf(zone_buffer))
.ToHandleChecked();
weak_native_module = module_object->shared_native_module();
// Retrieve the canonicalized signature ID.
const std::vector<CanonicalTypeIndex>& canonical_type_ids =
module_object->native_module()
->module()
->isorecursive_canonical_type_ids;
CHECK_EQ(1, canonical_type_ids.size());
canonical_sig_id_before_serialization = canonical_type_ids[0];
// Check that the embedded constant in the code is right.
WasmCodeRefScope code_ref_scope;
WasmCode* code = module_object->native_module()->GetCode(0);
RelocIterator reloc_it{
code->instructions(), code->reloc_info(), code->constant_pool(),
RelocInfo::ModeMask(RelocInfo::WASM_CANONICAL_SIG_ID)};
CHECK(!reloc_it.done());
CHECK_EQ(canonical_sig_id_before_serialization.index,
reloc_it.rinfo()->wasm_canonical_sig_id());
reloc_it.next();
CHECK(reloc_it.done());
// Convert to API objects and serialize.
v8::Local<v8::WasmModuleObject> v8_module_object =
v8::Utils::ToLocal(module_object);
serialized_module = v8_module_object->GetCompiledModule().Serialize();
}
CHECK_LT(0, serialized_module.size);
// Run GC until the NativeModule died. Add a manual timeout of 60 seconds to
// get a better error message than just a test timeout if this fails.
const auto start_time = std::chrono::steady_clock::now();
const auto end_time = start_time + std::chrono::seconds(60);
while (weak_native_module.lock()) {
// We need to invoke GC without stack, otherwise the native module may
// survive.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(
i_isolate->heap());
v8_isolate->RequestGarbageCollectionForTesting(
v8::Isolate::kFullGarbageCollection);
if (std::chrono::steady_clock::now() > end_time) {
FATAL("NativeModule did not die within 60 seconds");
}
}
}
// Clear canonicalized types, then compile another module which adds a
// canonical type at the same index we used in the previous module.
GetTypeCanonicalizer()->EmptyStorageForTesting();
{
ZoneBuffer buffer(&zone);
WasmModuleBuilder builder{&zone};
TestSignatures sigs;
ModuleTypeIndex sig_id = builder.AddSignature(sigs.v_v(), true);
WasmFunctionBuilder* f = builder.AddFunction(sig_id);
f->EmitByte(kExprEnd);
builder.WriteTo(&buffer);
ErrorThrower thrower(i_isolate, "");
GetWasmEngine()
->SyncCompile(i_isolate, enabled_features, CompileTimeImports{},
&thrower, base::OwnedCopyOf(buffer))
.ToHandleChecked();
}
// Now deserialize the previous module.
CanonicalTypeIndex canonical_sig_id_after_deserialization{
canonical_sig_id_before_serialization.index + 1};
{
v8::Local<v8::Context> deserialization_context =
v8::Context::New(CcTest::isolate());
deserialization_context->Enter();
ErrorThrower thrower(CcTest::i_isolate(), "");
base::Vector<const char> kNoSourceUrl;
DirectHandle<WasmModuleObject> module_object =
DeserializeNativeModule(CcTest::i_isolate(),
base::VectorOf(serialized_module.buffer.get(),
serialized_module.size),
base::VectorOf(zone_buffer),
CompileTimeImports{}, kNoSourceUrl)
.ToHandleChecked();
// Check that the signature ID got canonicalized to index 1.
const std::vector<CanonicalTypeIndex>& canonical_type_ids =
module_object->native_module()
->module()
->isorecursive_canonical_type_ids;
CHECK_EQ(1, canonical_type_ids.size());
CHECK_EQ(canonical_sig_id_after_deserialization, canonical_type_ids[0]);
// Check that the embedded constant in the code is right.
WasmCodeRefScope code_ref_scope;
WasmCode* code = module_object->native_module()->GetCode(0);
RelocIterator reloc_it{
code->instructions(), code->reloc_info(), code->constant_pool(),
RelocInfo::ModeMask(RelocInfo::WASM_CANONICAL_SIG_ID)};
CHECK(!reloc_it.done());
CHECK_EQ(canonical_sig_id_after_deserialization.index,
reloc_it.rinfo()->wasm_canonical_sig_id());
reloc_it.next();
CHECK(reloc_it.done());
// Now call the function.
DirectHandle<WasmInstanceObject> instance =
GetWasmEngine()
->SyncInstantiate(CcTest::i_isolate(), &thrower, module_object,
DirectHandle<JSReceiver>::null(),
MaybeDirectHandle<JSArrayBuffer>())
.ToHandleChecked();
DirectHandle<Object> params[] = {direct_handle(Smi::FromInt(1), i_isolate)};
int32_t result = testing::CallWasmFunctionForTesting(
i_isolate, instance, "call_indirect", base::ArrayVector(params));
CHECK_EQ(42, result);
}
}
// Regression test for https://crbug.com/372840600 /
// https://crbug.com/369793713 / https://crbug.com/369869947.
TEST(SerializeDetectedFeatures) {
// This test compiles and serializes a module which uses a use-counter-tracked
// feature (tail calls). We check that the set of detected features is
// preserved across serialization and deserialization. Otherwise we would
// fail a DCHECK in lazy compilation later.
FlagScope<int> tier_up_quickly{&v8_flags.wasm_tiering_budget, 10};
FlagScope<bool> expose_gc{&v8_flags.expose_gc, true};
i::Isolate* i_isolate = CcTest::InitIsolateOnce();
v8::Isolate* v8_isolate = CcTest::isolate();
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
HandleScope handle_scope(i_isolate);
// Build a small module with a tail call.
ZoneBuffer buffer(&zone);
{
WasmModuleBuilder builder{&zone};
// Add a function which is tail-called by another one.
ModuleTypeIndex sig_i_v = builder.AddSignature(TestSignatures::i_v(), true);
WasmFunctionBuilder* a = builder.AddFunction(sig_i_v);
a->EmitCode({WASM_I32V_1(11), WASM_END});
builder.AddExport(base::CStrVector("a"), a);
// Add the function which tail-calls the first one.
WasmFunctionBuilder* b = builder.AddFunction(sig_i_v);
b->EmitCode({WASM_RETURN_CALL_FUNCTION0(a->func_index()), WASM_END});
builder.AddExport(base::CStrVector("b"), b);
// Write the final module into {buffer}.
builder.WriteTo(&buffer);
}
// Compile and initialize the module and serialize it.
// Keep a weak pointer so we can check that the original native module died.
auto enabled_features = WasmEnabledFeatures::FromIsolate(i_isolate);
std::weak_ptr<NativeModule> weak_native_module;
v8::OwnedBuffer serialized_module;
{
ErrorThrower thrower(i_isolate, "");
{
v8::Isolate::Scope isolate_scope(v8_isolate);
HandleScope scope(i_isolate);
v8::Local<v8::Context> serialization_context =
v8::Context::New(v8_isolate);
serialization_context->Enter();
DirectHandle<WasmModuleObject> module_object =
GetWasmEngine()
->SyncCompile(i_isolate, enabled_features, CompileTimeImports{},
&thrower, base::OwnedCopyOf(buffer))
.ToHandleChecked();
// Check that "return_call" is in the set of detected features.
CHECK_EQ(WasmDetectedFeatures{{WasmDetectedFeature::return_call}},
module_object->native_module()
->compilation_state()
->detected_features());
weak_native_module = module_object->shared_native_module();
// Now call the tail-calling function "b". This triggers lazy compilation,
// which should not DCHECK because of a new detected feature.
DirectHandle<WasmInstanceObject> instance =
GetWasmEngine()
->SyncInstantiate(CcTest::i_isolate(), &thrower, module_object,
DirectHandle<JSReceiver>::null(),
MaybeDirectHandle<JSArrayBuffer>())
.ToHandleChecked();
v8::Local<v8::WasmModuleObject> v8_module_object =
v8::Utils::ToLocal(module_object);
// Call function "a" until serialization succeeds (once we have TF code).
const auto start_time = std::chrono::steady_clock::now();
const auto end_time = start_time + std::chrono::seconds(60);
while (true) {
int32_t result =
testing::CallWasmFunctionForTesting(i_isolate, instance, "a", {});
CHECK_EQ(11, result);
serialized_module = v8_module_object->GetCompiledModule().Serialize();
if (serialized_module.size != 0) break;
v8_isolate->RequestGarbageCollectionForTesting(
v8::Isolate::kFullGarbageCollection);
if (std::chrono::steady_clock::now() > end_time) {
FATAL("Tier-up didn't complete within 60 seconds");
}
}
}
CHECK_LT(0, serialized_module.size);
// Run GC until the NativeModule died. Add a manual timeout of 60 seconds to
// get a better error message than just a test timeout if this fails.
const auto start_time = std::chrono::steady_clock::now();
const auto end_time = start_time + std::chrono::seconds(60);
while (weak_native_module.lock()) {
// We need to invoke GC without stack, otherwise the native module may
// survive.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(
i_isolate->heap());
v8_isolate->RequestGarbageCollectionForTesting(
v8::Isolate::kFullGarbageCollection);
if (std::chrono::steady_clock::now() > end_time) {
FATAL("NativeModule did not die within 60 seconds");
}
}
}
// Now deserialize the module and check the detected features again.
{
v8::Local<v8::Context> deserialization_context =
v8::Context::New(CcTest::isolate());
deserialization_context->Enter();
ErrorThrower thrower(CcTest::i_isolate(), "");
base::Vector<const char> kNoSourceUrl;
DirectHandle<WasmModuleObject> module_object =
DeserializeNativeModule(CcTest::i_isolate(),
base::VectorOf(serialized_module.buffer.get(),
serialized_module.size),
base::VectorOf(buffer), CompileTimeImports{},
kNoSourceUrl)
.ToHandleChecked();
CHECK_EQ(WasmDetectedFeatures{{WasmDetectedFeature::return_call}},
module_object->native_module()
->compilation_state()
->detected_features());
// Now call the tail-calling function "b". This triggers lazy compilation,
// which should not DCHECK because of a new detected feature.
DirectHandle<WasmInstanceObject> instance =
GetWasmEngine()
->SyncInstantiate(CcTest::i_isolate(), &thrower, module_object,
DirectHandle<JSReceiver>::null(),
MaybeDirectHandle<JSArrayBuffer>())
.ToHandleChecked();
int32_t result =
testing::CallWasmFunctionForTesting(i_isolate, instance, "b", {});
CHECK_EQ(11, result);
}
}
} // namespace v8::internal::wasm

View File

@ -0,0 +1,319 @@
// Copyright 2018 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 <memory>
#include "src/execution/microtask-queue.h"
#include "src/objects/objects-inl.h"
#include "src/wasm/function-compiler.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-module-builder.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
#include "test/common/wasm/wasm-module-runner.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace test_wasm_shared_engine {
// Helper type definition representing a WebAssembly module shared between
// multiple Isolates with implicit reference counting.
using SharedModule = std::shared_ptr<NativeModule>;
// Helper class representing an Isolate that uses the process-wide (shared) wasm
// engine.
class SharedEngineIsolate {
public:
SharedEngineIsolate() : isolate_(v8::Isolate::Allocate()) {
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate::Initialize(isolate_, create_params);
v8_isolate()->Enter();
v8::HandleScope handle_scope(v8_isolate());
v8::Context::New(v8_isolate())->Enter();
testing::SetupIsolateForWasmModule(isolate());
zone_.reset(new Zone(isolate()->allocator(), ZONE_NAME));
}
~SharedEngineIsolate() {
v8_isolate()->Exit();
zone_.reset();
isolate_->Dispose();
}
Zone* zone() const { return zone_.get(); }
v8::Isolate* v8_isolate() { return isolate_; }
Isolate* isolate() { return reinterpret_cast<Isolate*>(isolate_); }
DirectHandle<WasmInstanceObject> CompileAndInstantiate(ZoneBuffer* buffer) {
ErrorThrower thrower(isolate(), "CompileAndInstantiate");
MaybeDirectHandle<WasmInstanceObject> instance =
testing::CompileAndInstantiateForTesting(isolate(), &thrower,
base::VectorOf(*buffer));
return instance.ToHandleChecked();
}
DirectHandle<WasmInstanceObject> ImportInstance(SharedModule shared_module) {
DirectHandle<WasmModuleObject> module_object =
GetWasmEngine()->ImportNativeModule(isolate(), shared_module, {});
ErrorThrower thrower(isolate(), "ImportInstance");
MaybeDirectHandle<WasmInstanceObject> instance =
GetWasmEngine()->SyncInstantiate(isolate(), &thrower, module_object, {},
{});
return instance.ToHandleChecked();
}
SharedModule ExportInstance(DirectHandle<WasmInstanceObject> instance) {
return instance->module_object()->shared_native_module();
}
int32_t Run(DirectHandle<WasmInstanceObject> instance) {
return testing::CallWasmFunctionForTesting(isolate(), instance, "main", {});
}
private:
v8::Isolate* isolate_;
std::unique_ptr<Zone> zone_;
};
// Helper class representing a Thread running its own instance of an Isolate
// with a shared WebAssembly engine available at construction time.
class SharedEngineThread : public v8::base::Thread {
public:
explicit SharedEngineThread(
std::function<void(SharedEngineIsolate*)> callback)
: Thread(Options("SharedEngineThread")), callback_(callback) {}
void Run() override {
SharedEngineIsolate isolate;
callback_(&isolate);
}
private:
std::function<void(SharedEngineIsolate*)> callback_;
};
namespace {
ZoneBuffer* BuildReturnConstantModule(Zone* zone, int constant) {
TestSignatures sigs;
ZoneBuffer* buffer = zone->New<ZoneBuffer>(zone);
WasmModuleBuilder* builder = zone->New<WasmModuleBuilder>(zone);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
f->builder()->AddExport(base::CStrVector("main"), f);
f->EmitCode({WASM_I32V_2(constant), WASM_END});
builder->WriteTo(buffer);
return buffer;
}
class MockInstantiationResolver : public InstantiationResultResolver {
public:
explicit MockInstantiationResolver(IndirectHandle<Object>* out_instance)
: out_instance_(out_instance) {}
void OnInstantiationSucceeded(
DirectHandle<WasmInstanceObject> result) override {
*out_instance_->location() = result->ptr();
}
void OnInstantiationFailed(DirectHandle<JSAny> error_reason) override {
UNREACHABLE();
}
private:
IndirectHandle<Object>* out_instance_;
};
class MockCompilationResolver : public CompilationResultResolver {
public:
MockCompilationResolver(SharedEngineIsolate* isolate,
IndirectHandle<Object>* out_instance)
: isolate_(isolate), out_instance_(out_instance) {}
void OnCompilationSucceeded(DirectHandle<WasmModuleObject> result) override {
GetWasmEngine()->AsyncInstantiate(
isolate_->isolate(),
std::make_unique<MockInstantiationResolver>(out_instance_), result, {});
}
void OnCompilationFailed(DirectHandle<JSAny> error_reason) override {
UNREACHABLE();
}
private:
SharedEngineIsolate* isolate_;
IndirectHandle<Object>* out_instance_;
};
void PumpMessageLoop(SharedEngineIsolate* isolate) {
v8::platform::PumpMessageLoop(i::V8::GetCurrentPlatform(),
isolate->v8_isolate(),
platform::MessageLoopBehavior::kWaitForWork);
isolate->isolate()->default_microtask_queue()->RunMicrotasks(
isolate->isolate());
}
DirectHandle<WasmInstanceObject> CompileAndInstantiateAsync(
SharedEngineIsolate* isolate, ZoneBuffer* buffer) {
IndirectHandle<Object> maybe_instance(Smi::zero(), isolate->isolate());
auto enabled_features = WasmEnabledFeatures::FromIsolate(isolate->isolate());
constexpr const char* kAPIMethodName = "Test.CompileAndInstantiateAsync";
GetWasmEngine()->AsyncCompile(
isolate->isolate(), enabled_features, CompileTimeImports{},
std::make_unique<MockCompilationResolver>(isolate, &maybe_instance),
base::OwnedCopyOf(*buffer), kAPIMethodName);
while (!IsWasmInstanceObject(*maybe_instance)) PumpMessageLoop(isolate);
DirectHandle<WasmInstanceObject> instance =
Cast<WasmInstanceObject>(maybe_instance);
return instance;
}
} // namespace
TEST(SharedEngineRunSeparated) {
{
SharedEngineIsolate isolate;
HandleScope scope(isolate.isolate());
ZoneBuffer* buffer = BuildReturnConstantModule(isolate.zone(), 23);
DirectHandle<WasmInstanceObject> instance =
isolate.CompileAndInstantiate(buffer);
CHECK_EQ(23, isolate.Run(instance));
}
{
SharedEngineIsolate isolate;
HandleScope scope(isolate.isolate());
ZoneBuffer* buffer = BuildReturnConstantModule(isolate.zone(), 42);
DirectHandle<WasmInstanceObject> instance =
isolate.CompileAndInstantiate(buffer);
CHECK_EQ(42, isolate.Run(instance));
}
}
TEST(SharedEngineRunImported) {
SharedModule module;
{
SharedEngineIsolate isolate;
HandleScope scope(isolate.isolate());
ZoneBuffer* buffer = BuildReturnConstantModule(isolate.zone(), 23);
DirectHandle<WasmInstanceObject> instance =
isolate.CompileAndInstantiate(buffer);
module = isolate.ExportInstance(instance);
CHECK_EQ(23, isolate.Run(instance));
}
{
SharedEngineIsolate isolate;
HandleScope scope(isolate.isolate());
DirectHandle<WasmInstanceObject> instance = isolate.ImportInstance(module);
CHECK_EQ(23, isolate.Run(instance));
}
}
TEST(SharedEngineRunThreadedBuildingSync) {
SharedEngineThread thread1([](SharedEngineIsolate* isolate) {
HandleScope scope(isolate->isolate());
ZoneBuffer* buffer = BuildReturnConstantModule(isolate->zone(), 23);
DirectHandle<WasmInstanceObject> instance =
isolate->CompileAndInstantiate(buffer);
CHECK_EQ(23, isolate->Run(instance));
});
SharedEngineThread thread2([](SharedEngineIsolate* isolate) {
HandleScope scope(isolate->isolate());
ZoneBuffer* buffer = BuildReturnConstantModule(isolate->zone(), 42);
DirectHandle<WasmInstanceObject> instance =
isolate->CompileAndInstantiate(buffer);
CHECK_EQ(42, isolate->Run(instance));
});
CHECK(thread1.Start());
CHECK(thread2.Start());
thread1.Join();
thread2.Join();
}
TEST(SharedEngineRunThreadedBuildingAsync) {
SharedEngineThread thread1([](SharedEngineIsolate* isolate) {
HandleScope scope(isolate->isolate());
ZoneBuffer* buffer = BuildReturnConstantModule(isolate->zone(), 23);
DirectHandle<WasmInstanceObject> instance =
CompileAndInstantiateAsync(isolate, buffer);
CHECK_EQ(23, isolate->Run(instance));
});
SharedEngineThread thread2([](SharedEngineIsolate* isolate) {
HandleScope scope(isolate->isolate());
ZoneBuffer* buffer = BuildReturnConstantModule(isolate->zone(), 42);
DirectHandle<WasmInstanceObject> instance =
CompileAndInstantiateAsync(isolate, buffer);
CHECK_EQ(42, isolate->Run(instance));
});
CHECK(thread1.Start());
CHECK(thread2.Start());
thread1.Join();
thread2.Join();
}
TEST(SharedEngineRunThreadedExecution) {
SharedModule module;
{
SharedEngineIsolate isolate;
HandleScope scope(isolate.isolate());
ZoneBuffer* buffer = BuildReturnConstantModule(isolate.zone(), 23);
DirectHandle<WasmInstanceObject> instance =
isolate.CompileAndInstantiate(buffer);
module = isolate.ExportInstance(instance);
}
SharedEngineThread thread1([module](SharedEngineIsolate* isolate) {
HandleScope scope(isolate->isolate());
DirectHandle<WasmInstanceObject> instance = isolate->ImportInstance(module);
CHECK_EQ(23, isolate->Run(instance));
});
SharedEngineThread thread2([module](SharedEngineIsolate* isolate) {
HandleScope scope(isolate->isolate());
DirectHandle<WasmInstanceObject> instance = isolate->ImportInstance(module);
CHECK_EQ(23, isolate->Run(instance));
});
CHECK(thread1.Start());
CHECK(thread2.Start());
thread1.Join();
thread2.Join();
}
TEST(SharedEngineRunThreadedTierUp) {
SharedModule module;
{
SharedEngineIsolate isolate;
HandleScope scope(isolate.isolate());
ZoneBuffer* buffer = BuildReturnConstantModule(isolate.zone(), 23);
DirectHandle<WasmInstanceObject> instance =
isolate.CompileAndInstantiate(buffer);
module = isolate.ExportInstance(instance);
}
constexpr int kNumberOfThreads = 5;
std::list<SharedEngineThread> threads;
for (int i = 0; i < kNumberOfThreads; ++i) {
threads.emplace_back([module](SharedEngineIsolate* isolate) {
constexpr int kNumberOfIterations = 100;
HandleScope scope(isolate->isolate());
DirectHandle<WasmInstanceObject> instance =
isolate->ImportInstance(module);
for (int j = 0; j < kNumberOfIterations; ++j) {
CHECK_EQ(23, isolate->Run(instance));
}
});
}
threads.emplace_back([module](SharedEngineIsolate* isolate) {
HandleScope scope(isolate->isolate());
DirectHandle<WasmInstanceObject> instance = isolate->ImportInstance(module);
WasmDetectedFeatures detected;
WasmCompilationUnit::CompileWasmFunction(
isolate->isolate()->counters(), module.get(), &detected,
&module->module()->functions[0], ExecutionTier::kTurbofan);
CHECK_EQ(23, isolate->Run(instance));
});
for (auto& thread : threads) CHECK(thread.Start());
for (auto& thread : threads) thread.Join();
}
} // namespace test_wasm_shared_engine
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,293 @@
// Copyright 2016 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 "include/v8-function.h"
#include "src/api/api-inl.h"
#include "src/codegen/assembler-inl.h"
#include "src/objects/call-site-info-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/common/value-helper.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace test_wasm_stack {
using v8::Local;
using v8::Utils;
namespace {
#define CHECK_CSTREQ(exp, found) \
do { \
const char* exp_ = (exp); \
const char* found_ = (found); \
DCHECK_NOT_NULL(exp); \
if (V8_UNLIKELY(found_ == nullptr || strcmp(exp_, found_) != 0)) { \
FATAL("Check failed: (%s) != (%s) ('%s' vs '%s').", #exp, #found, exp_, \
found_ ? found_ : "<null>"); \
} \
} while (false)
void PrintStackTrace(v8::Isolate* isolate, v8::Local<v8::StackTrace> stack) {
printf("Stack Trace (length %d):\n", stack->GetFrameCount());
for (int i = 0, e = stack->GetFrameCount(); i != e; ++i) {
v8::Local<v8::StackFrame> frame = stack->GetFrame(isolate, i);
v8::Local<v8::String> script = frame->GetScriptName();
v8::Local<v8::String> func = frame->GetFunctionName();
printf(
"[%d] (%s) %s:%d:%d\n", i,
script.IsEmpty() ? "<null>" : *v8::String::Utf8Value(isolate, script),
func.IsEmpty() ? "<null>" : *v8::String::Utf8Value(isolate, func),
frame->GetLineNumber(), frame->GetColumn());
}
}
struct ExceptionInfo {
const char* func_name;
int line_nr; // 1-based
int column; // 1-based
};
template <int N>
void CheckExceptionInfos(v8::internal::Isolate* i_isolate,
DirectHandle<Object> exc,
const ExceptionInfo (&excInfos)[N]) {
// Check that it's indeed an Error object.
CHECK(IsJSError(*exc));
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(i_isolate);
// Extract stack frame from the exception.
Local<v8::Value> localExc = Utils::ToLocal(exc);
v8::Local<v8::StackTrace> stack = v8::Exception::GetStackTrace(localExc);
PrintStackTrace(v8_isolate, stack);
CHECK(!stack.IsEmpty());
CHECK_EQ(N, stack->GetFrameCount());
for (int frameNr = 0; frameNr < N; ++frameNr) {
v8::Local<v8::StackFrame> frame = stack->GetFrame(v8_isolate, frameNr);
v8::String::Utf8Value funName(v8_isolate, frame->GetFunctionName());
CHECK_CSTREQ(excInfos[frameNr].func_name, *funName);
// Line and column are 1-based in v8::StackFrame, just as in ExceptionInfo.
CHECK_EQ(excInfos[frameNr].line_nr, frame->GetLineNumber());
CHECK_EQ(excInfos[frameNr].column, frame->GetColumn());
v8::Local<v8::String> scriptSource = frame->GetScriptSource();
if (frame->IsWasm()) {
CHECK(scriptSource.IsEmpty());
} else {
CHECK(scriptSource->IsString());
}
}
CheckComputeLocation(i_isolate, exc, excInfos[0],
stack->GetFrame(v8_isolate, 0));
}
void CheckComputeLocation(v8::internal::Isolate* i_isolate,
DirectHandle<Object> exc,
const ExceptionInfo& topLocation,
const v8::Local<v8::StackFrame> stackFrame) {
MessageLocation loc;
CHECK(i_isolate->ComputeLocationFromSimpleStackTrace(&loc, exc));
printf("loc start: %d, end: %d\n", loc.start_pos(), loc.end_pos());
DirectHandle<JSMessageObject> message =
i_isolate->CreateMessage(exc, nullptr);
JSMessageObject::EnsureSourcePositionsAvailable(i_isolate, message);
printf("msg start: %d, end: %d, line: %d, col: %d\n",
message->GetStartPosition(), message->GetEndPosition(),
message->GetLineNumber(), message->GetColumnNumber());
CHECK_EQ(loc.start_pos(), message->GetStartPosition());
CHECK_EQ(loc.end_pos(), message->GetEndPosition());
// In the message, the line is 1-based, but the column is 0-based.
CHECK_EQ(topLocation.line_nr, message->GetLineNumber());
CHECK_LE(1, topLocation.column);
// TODO(szuend): Remove or re-enable the following check once it is decided
// whether Script::PositionInfo.column should be the offset
// relative to the module or relative to the function.
// CHECK_EQ(topLocation.column - 1, message->GetColumnNumber());
Tagged<String> scriptSource = message->GetSource();
CHECK(IsString(scriptSource));
if (stackFrame->IsWasm()) {
CHECK_EQ(scriptSource->length(), 0);
} else {
CHECK_GT(scriptSource->length(), 0);
}
}
#undef CHECK_CSTREQ
} // namespace
// Call from JS to wasm to JS and throw an Error from JS.
WASM_COMPILED_EXEC_TEST(CollectDetailedWasmStack_ExplicitThrowFromJs) {
TestSignatures sigs;
HandleScope scope(CcTest::InitIsolateOnce());
const char* source =
"(function js() {\n function a() {\n throw new Error(); };\n a(); })";
DirectHandle<JSFunction> js_function =
Cast<JSFunction>(v8::Utils::OpenDirectHandle(
*v8::Local<v8::Function>::Cast(CompileRun(source))));
ManuallyImportedJSFunction import = {sigs.v_v(), js_function};
uint32_t js_throwing_index = 0;
WasmRunner<void> r(execution_tier, kWasmOrigin, &import);
// Add a nop such that we don't always get position 1.
r.Build({WASM_NOP, WASM_CALL_FUNCTION0(js_throwing_index)});
uint32_t wasm_index_1 = r.function()->func_index;
WasmFunctionCompiler& f2 = r.NewFunction<void>("call_main");
f2.Build({WASM_CALL_FUNCTION0(wasm_index_1)});
uint32_t wasm_index_2 = f2.function_index();
DirectHandle<JSFunction> js_wasm_wrapper = r.builder().WrapCode(wasm_index_2);
DirectHandle<JSFunction> js_trampoline = Cast<JSFunction>(
v8::Utils::OpenDirectHandle(*v8::Local<v8::Function>::Cast(
CompileRun("(function callFn(fn) { fn(); })"))));
Isolate* isolate = js_wasm_wrapper->GetIsolate();
isolate->SetCaptureStackTraceForUncaughtExceptions(true, 10,
v8::StackTrace::kOverview);
DirectHandle<Object> global(isolate->context()->global_object(), isolate);
MaybeDirectHandle<Object> maybe_exc;
DirectHandle<Object> args[] = {js_wasm_wrapper};
MaybeDirectHandle<Object> returnObjMaybe =
Execution::TryCall(isolate, js_trampoline, global, base::VectorOf(args),
Execution::MessageHandling::kReport, &maybe_exc);
CHECK(returnObjMaybe.is_null());
ExceptionInfo expected_exceptions[] = {
{"a", 3, 8}, // -
{"js", 4, 2}, // -
{"$main", 1, 8}, // -
{"$call_main", 1, 21}, // -
{"callFn", 1, 24} // -
};
CheckExceptionInfos(isolate, maybe_exc.ToHandleChecked(),
expected_exceptions);
}
// Trigger a trap in wasm, stack should contain a source url.
WASM_COMPILED_EXEC_TEST(CollectDetailedWasmStack_WasmUrl) {
// Create a WasmRunner with stack checks and traps enabled.
WasmRunner<int> r(execution_tier, kWasmOrigin, nullptr, "main");
std::vector<uint8_t> trap_code(1, kExprUnreachable);
r.Build(trap_code.data(), trap_code.data() + trap_code.size());
WasmFunctionCompiler& f = r.NewFunction<int>("call_main");
f.Build({WASM_CALL_FUNCTION0(0)});
uint32_t wasm_index = f.function_index();
DirectHandle<JSFunction> js_wasm_wrapper = r.builder().WrapCode(wasm_index);
DirectHandle<JSFunction> js_trampoline = Cast<JSFunction>(
v8::Utils::OpenDirectHandle(*v8::Local<v8::Function>::Cast(
CompileRun("(function callFn(fn) { fn(); })"))));
Isolate* isolate = js_wasm_wrapper->GetIsolate();
isolate->SetCaptureStackTraceForUncaughtExceptions(true, 10,
v8::StackTrace::kOverview);
// Set the wasm script source url.
const char* url = "http://example.com/example.wasm";
const DirectHandle<String> source_url =
isolate->factory()->InternalizeUtf8String(url);
r.builder().instance_object()->module_object()->script()->set_source_url(
*source_url);
// Run the js wrapper.
DirectHandle<Object> global(isolate->context()->global_object(), isolate);
MaybeDirectHandle<Object> maybe_exc;
DirectHandle<Object> args[] = {js_wasm_wrapper};
MaybeDirectHandle<Object> maybe_return_obj =
Execution::TryCall(isolate, js_trampoline, global, base::VectorOf(args),
Execution::MessageHandling::kReport, &maybe_exc);
CHECK(maybe_return_obj.is_null());
DirectHandle<Object> exception = maybe_exc.ToHandleChecked();
// Extract stack trace from the exception.
DirectHandle<FixedArray> stack_trace_object =
isolate->GetSimpleStackTrace(Cast<JSReceiver>(exception));
CHECK_NE(0, stack_trace_object->length());
DirectHandle<CallSiteInfo> stack_frame(
Cast<CallSiteInfo>(stack_trace_object->get(0)), isolate);
MaybeDirectHandle<String> maybe_stack_trace_str =
SerializeCallSiteInfo(isolate, stack_frame);
CHECK(!maybe_stack_trace_str.is_null());
DirectHandle<String> stack_trace_str =
maybe_stack_trace_str.ToHandleChecked();
// Check if the source_url is part of the stack trace.
CHECK_NE(std::string(stack_trace_str->ToCString().get()).find(url),
std::string::npos);
}
// Trigger a trap in wasm, stack should be JS -> wasm -> wasm.
WASM_COMPILED_EXEC_TEST(CollectDetailedWasmStack_WasmError) {
for (int pos_shift = 0; pos_shift < 3; ++pos_shift) {
// Test a position with 1, 2 or 3 bytes needed to represent it.
int unreachable_pos = 1 << (8 * pos_shift);
// Create a WasmRunner with stack checks and traps enabled.
WasmRunner<int> r(execution_tier, kWasmOrigin, nullptr, "main");
std::vector<uint8_t> trap_code(unreachable_pos + 1, kExprNop);
trap_code[unreachable_pos] = kExprUnreachable;
r.Build(trap_code.data(), trap_code.data() + trap_code.size());
uint32_t wasm_index_1 = r.function()->func_index;
WasmFunctionCompiler& f2 = r.NewFunction<int>("call_main");
f2.Build({WASM_CALL_FUNCTION0(0)});
uint32_t wasm_index_2 = f2.function_index();
DirectHandle<JSFunction> js_wasm_wrapper =
r.builder().WrapCode(wasm_index_2);
DirectHandle<JSFunction> js_trampoline = Cast<JSFunction>(
v8::Utils::OpenDirectHandle(*v8::Local<v8::Function>::Cast(
CompileRun("(function callFn(fn) { fn(); })"))));
Isolate* isolate = js_wasm_wrapper->GetIsolate();
isolate->SetCaptureStackTraceForUncaughtExceptions(
true, 10, v8::StackTrace::kOverview);
DirectHandle<Object> global(isolate->context()->global_object(), isolate);
MaybeDirectHandle<Object> maybe_exc;
DirectHandle<Object> args[] = {js_wasm_wrapper};
MaybeDirectHandle<Object> maybe_return_obj =
Execution::TryCall(isolate, js_trampoline, global, base::VectorOf(args),
Execution::MessageHandling::kReport, &maybe_exc);
CHECK(maybe_return_obj.is_null());
DirectHandle<Object> exception = maybe_exc.ToHandleChecked();
static constexpr int kMainLocalsLength = 1;
const int main_offset =
r.builder().GetFunctionAt(wasm_index_1)->code.offset();
const int call_main_offset =
r.builder().GetFunctionAt(wasm_index_2)->code.offset();
// Column is 1-based, so add 1 for the expected wasm output. Line number
// is always 1.
const int expected_main_pos =
unreachable_pos + main_offset + kMainLocalsLength + 1;
const int expected_call_main_pos = call_main_offset + kMainLocalsLength + 1;
ExceptionInfo expected_exceptions[] = {
{"$main", 1, expected_main_pos}, // -
{"$call_main", 1, expected_call_main_pos}, // -
{"callFn", 1, 24} //-
};
CheckExceptionInfos(isolate, exception, expected_exceptions);
}
}
} // namespace test_wasm_stack
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,186 @@
// Copyright 2022 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 "src/strings/unicode.h"
#include "test/cctest/cctest.h"
#include "third_party/utf8-decoder/generalized-utf8-decoder.h"
#include "third_party/utf8-decoder/utf8-decoder.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace test_wasm_strings {
struct Utf8Decoder {
Utf8DfaDecoder::State state = Utf8DfaDecoder::kAccept;
uint32_t codepoint = 0;
void Decode(uint8_t byte) {
DCHECK(!failure());
Utf8DfaDecoder::Decode(byte, &state, &codepoint);
}
bool success() const { return state == Utf8DfaDecoder::kAccept; }
bool failure() const { return state == Utf8DfaDecoder::kReject; }
bool incomplete() const { return !success() && !failure(); }
};
struct GeneralizedUtf8Decoder {
GeneralizedUtf8DfaDecoder::State state = GeneralizedUtf8DfaDecoder::kAccept;
uint32_t codepoint = 0;
void Decode(uint8_t byte) {
DCHECK(!failure());
GeneralizedUtf8DfaDecoder::Decode(byte, &state, &codepoint);
}
bool success() const { return state == GeneralizedUtf8DfaDecoder::kAccept; }
bool failure() const { return state == GeneralizedUtf8DfaDecoder::kReject; }
bool incomplete() const { return !success() && !failure(); }
};
struct DecodingOracle {
Utf8Decoder utf8;
GeneralizedUtf8Decoder generalized_utf8;
void Decode(uint8_t byte) {
utf8.Decode(byte);
generalized_utf8.Decode(byte);
}
void CheckSame() const {
CHECK_EQ(utf8.success(), generalized_utf8.success());
CHECK_EQ(utf8.failure(), generalized_utf8.failure());
if (utf8.success()) CHECK(utf8.codepoint == generalized_utf8.codepoint);
}
bool success() const {
CheckSame();
return utf8.success();
}
bool failure() const {
CheckSame();
return utf8.failure();
}
bool incomplete() const {
CheckSame();
return utf8.incomplete();
}
};
TEST(GeneralizedUTF8Decode) {
// Exhaustive check that the generalized UTF-8 decoder matches the strict
// UTF-8 encoder, except for surrogates. Each production should end the
// decoders accepting or rejecting the production.
for (uint32_t byte1 = 0; byte1 <= 0xFF; byte1++) {
DecodingOracle decoder1;
decoder1.Decode(byte1);
if (byte1 <= 0x7F) {
// First byte in [0x00, 0x7F]: one-byte.
CHECK(decoder1.success());
} else if (byte1 <= 0xC1) {
// First byte in [0x80, 0xC1]: invalid.
CHECK(decoder1.failure());
} else if (byte1 <= 0xDF) {
// First byte in [0xC2, 0xDF]: two-byte.
CHECK(decoder1.incomplete());
// Second byte completes the sequence. Only [0x80, 0xBF] is valid.
for (uint32_t byte2 = 0x00; byte2 <= 0xFF; byte2++) {
DecodingOracle decoder2 = decoder1;
decoder2.Decode(byte2);
if (0x80 <= byte2 && byte2 <= 0xBF) {
CHECK(decoder2.success());
} else {
CHECK(decoder2.failure());
}
}
} else if (byte1 <= 0xEF) {
// First byte in [0xE0, 0xEF]: three-byte sequence.
CHECK(decoder1.incomplete());
uint32_t min = byte1 == 0xE0 ? 0xA0 : 0x80;
for (uint32_t byte2 = 0x00; byte2 <= 0xFF; byte2++) {
DecodingOracle decoder2 = decoder1;
decoder2.Decode(byte2);
if (min <= byte2 && byte2 <= 0xBF) {
// Second byte in [min, 0xBF]: continuation.
bool is_surrogate = byte1 == 0xED && byte2 >= 0xA0;
if (is_surrogate) {
// Here's where we expect the two decoders to differ: generalized
// UTF-8 will get a surrogate and strict UTF-8 errors.
CHECK(decoder2.utf8.failure());
CHECK(decoder2.generalized_utf8.incomplete());
} else {
CHECK(decoder2.incomplete());
}
// Third byte completes the sequence. Only [0x80, 0xBF] is valid.
for (uint32_t byte3 = 0x00; byte3 <= 0xFF; byte3++) {
DecodingOracle decoder3 = decoder2;
if (is_surrogate) {
decoder3.generalized_utf8.Decode(byte3);
if (0x80 <= byte3 && byte3 <= 0xBF) {
CHECK(decoder3.generalized_utf8.success());
uint32_t codepoint = decoder3.generalized_utf8.codepoint;
CHECK(unibrow::Utf16::IsLeadSurrogate(codepoint) ||
unibrow::Utf16::IsTrailSurrogate(codepoint));
} else {
CHECK(decoder3.generalized_utf8.failure());
}
} else {
decoder3.Decode(byte3);
if (0x80 <= byte3 && byte3 <= 0xBF) {
CHECK(decoder3.success());
} else {
CHECK(decoder3.failure());
}
}
}
} else {
// Second byte not in range: failure.
CHECK(decoder2.failure());
}
}
} else if (byte1 <= 0xF4) {
// First byte in [0xF0, 0xF4]: four-byte sequence.
CHECK(decoder1.incomplete());
uint32_t min = byte1 == 0xF0 ? 0x90 : 0x80;
uint32_t max = byte1 == 0xF4 ? 0x8F : 0xBF;
for (uint32_t byte2 = 0x00; byte2 <= 0xFF; byte2++) {
DecodingOracle decoder2 = decoder1;
decoder2.Decode(byte2);
if (min <= byte2 && byte2 <= max) {
// Second byte in [min, max]: continuation.
CHECK(decoder2.incomplete());
for (uint32_t byte3 = 0x00; byte3 <= 0xFF; byte3++) {
DecodingOracle decoder3 = decoder2;
decoder3.Decode(byte3);
if (0x80 <= byte3 && byte3 <= 0xBF) {
// Third byte in [0x80, BF]: continuation.
CHECK(decoder3.incomplete());
for (uint32_t byte4 = 0x00; byte4 <= 0xFF; byte4++) {
DecodingOracle decoder4 = decoder3;
decoder4.Decode(byte4);
// Fourth byte4 completes the sequence.
if (0x80 <= byte4 && byte4 <= 0xBF) {
CHECK(decoder4.success());
} else {
CHECK(decoder4.failure());
}
}
} else {
CHECK(decoder3.failure());
}
}
} else {
CHECK(decoder2.failure());
}
}
} else {
// First byte in [0xF5, 0xFF]: failure.
CHECK(decoder1.failure());
}
}
}
} // namespace test_wasm_strings
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,147 @@
// Copyright 2016 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 "include/v8-function.h"
#include "src/api/api-inl.h"
#include "src/codegen/assembler-inl.h"
#include "src/objects/call-site-info-inl.h"
#include "src/trap-handler/trap-handler.h"
#include "test/cctest/cctest.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/common/value-helper.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace test_wasm_trap_position {
using v8::Local;
using v8::Utils;
namespace {
#define CHECK_CSTREQ(exp, found) \
do { \
const char* exp_ = (exp); \
const char* found_ = (found); \
DCHECK_NOT_NULL(exp); \
if (V8_UNLIKELY(found_ == nullptr || strcmp(exp_, found_) != 0)) { \
FATAL("Check failed: (%s) != (%s) ('%s' vs '%s').", #exp, #found, exp_, \
found_ ? found_ : "<null>"); \
} \
} while (false)
struct ExceptionInfo {
const char* func_name;
int line_nr;
int column;
};
template <int N>
void CheckExceptionInfos(v8::internal::Isolate* isolate,
DirectHandle<Object> exc,
const ExceptionInfo (&excInfos)[N]) {
// Check that it's indeed an Error object.
CHECK(IsJSError(*exc));
Print(*exc);
// Extract stack frame from the exception.
auto stack = isolate->GetSimpleStackTrace(Cast<JSObject>(exc));
CHECK_EQ(N, stack->length());
for (int i = 0; i < N; ++i) {
DirectHandle<CallSiteInfo> info(Cast<CallSiteInfo>(stack->get(i)), isolate);
auto func_name =
Cast<String>(CallSiteInfo::GetFunctionName(info))->ToCString();
CHECK_CSTREQ(excInfos[i].func_name, func_name.get());
CHECK_EQ(excInfos[i].line_nr, CallSiteInfo::GetLineNumber(info));
CHECK_EQ(excInfos[i].column, CallSiteInfo::GetColumnNumber(info));
}
}
#undef CHECK_CSTREQ
} // namespace
// Trigger a trap for executing unreachable.
WASM_COMPILED_EXEC_TEST(Unreachable) {
// Create a WasmRunner with stack checks and traps enabled.
WasmRunner<void> r(execution_tier, kWasmOrigin, nullptr, "main");
r.Build({WASM_UNREACHABLE});
uint32_t wasm_index = r.function()->func_index;
DirectHandle<JSFunction> js_wasm_wrapper = r.builder().WrapCode(wasm_index);
DirectHandle<JSFunction> js_trampoline =
Cast<JSFunction>(v8::Utils::OpenHandle(*v8::Local<v8::Function>::Cast(
CompileRun("(function callFn(fn) { fn(); })"))));
Isolate* isolate = js_wasm_wrapper->GetIsolate();
isolate->SetCaptureStackTraceForUncaughtExceptions(true, 10,
v8::StackTrace::kOverview);
DirectHandle<Object> global(isolate->context()->global_object(), isolate);
MaybeDirectHandle<Object> maybe_exc;
DirectHandle<Object> args[] = {js_wasm_wrapper};
MaybeDirectHandle<Object> returnObjMaybe =
Execution::TryCall(isolate, js_trampoline, global, base::VectorOf(args),
Execution::MessageHandling::kReport, &maybe_exc);
CHECK(returnObjMaybe.is_null());
ExceptionInfo expected_exceptions[] = {
{"main", 1, 7}, // --
{"callFn", 1, 24} // --
};
CheckExceptionInfos(isolate, maybe_exc.ToHandleChecked(),
expected_exceptions);
}
// Trigger a trap for loading from out-of-bounds.
WASM_COMPILED_EXEC_TEST(IllegalLoad) {
WasmRunner<void> r(execution_tier, kWasmOrigin, nullptr, "main");
r.builder().AddMemory(0L);
r.Build({WASM_IF(
WASM_ONE, WASM_SEQ(WASM_LOAD_MEM(MachineType::Int32(), WASM_I32V_1(-3)),
WASM_DROP))});
uint32_t wasm_index_1 = r.function()->func_index;
WasmFunctionCompiler& f2 = r.NewFunction<void>("call_main");
// Insert a NOP such that the position of the call is not one.
f2.Build({WASM_NOP, WASM_CALL_FUNCTION0(wasm_index_1)});
uint32_t wasm_index_2 = f2.function_index();
DirectHandle<JSFunction> js_wasm_wrapper = r.builder().WrapCode(wasm_index_2);
DirectHandle<JSFunction> js_trampoline =
Cast<JSFunction>(v8::Utils::OpenHandle(*v8::Local<v8::Function>::Cast(
CompileRun("(function callFn(fn) { fn(); })"))));
Isolate* isolate = js_wasm_wrapper->GetIsolate();
isolate->SetCaptureStackTraceForUncaughtExceptions(true, 10,
v8::StackTrace::kOverview);
DirectHandle<Object> global(isolate->context()->global_object(), isolate);
MaybeDirectHandle<Object> maybe_exc;
DirectHandle<Object> args[] = {js_wasm_wrapper};
MaybeDirectHandle<Object> returnObjMaybe =
Execution::TryCall(isolate, js_trampoline, global, base::VectorOf(args),
Execution::MessageHandling::kReport, &maybe_exc);
CHECK(returnObjMaybe.is_null());
ExceptionInfo expected_exceptions[] = {
{"main", 1, 13}, // --
{"call_main", 1, 30}, // --
{"callFn", 1, 24} // --
};
CheckExceptionInfos(isolate, maybe_exc.ToHandleChecked(),
expected_exceptions);
}
} // namespace test_wasm_trap_position
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,69 @@
// Copyright 2018 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.
#ifndef WASM_ATOMICOP_UTILS_H
#define WASM_ATOMICOP_UTILS_H
#include "test/cctest/cctest.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/common/value-helper.h"
namespace v8 {
namespace internal {
namespace wasm {
#define WASM_ATOMIC_OPERATION_LIST(V) \
V(Add) \
V(Sub) \
V(And) \
V(Or) \
V(Xor) \
V(Exchange)
using Uint64BinOp = uint64_t (*)(uint64_t, uint64_t);
using Uint32BinOp = uint32_t (*)(uint32_t, uint32_t);
using Uint16BinOp = uint16_t (*)(uint16_t, uint16_t);
using Uint8BinOp = uint8_t (*)(uint8_t, uint8_t);
template <typename T>
T Add(T a, T b) {
return a + b;
}
template <typename T>
T Sub(T a, T b) {
return a - b;
}
template <typename T>
T And(T a, T b) {
return a & b;
}
template <typename T>
T Or(T a, T b) {
return a | b;
}
template <typename T>
T Xor(T a, T b) {
return a ^ b;
}
template <typename T>
T Exchange(T a, T b) {
return b;
}
template <typename T>
T CompareExchange(T initial, T a, T b) {
if (initial == a) return b;
return a;
}
} // namespace wasm
} // namespace internal
} // namespace v8
#endif

View File

@ -0,0 +1,601 @@
// Copyright 2017 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/cctest/wasm/wasm-run-utils.h"
#include <optional>
#include "src/codegen/assembler-inl.h"
#include "src/compiler/pipeline.h"
#include "src/diagnostics/code-tracer.h"
#include "src/heap/heap-inl.h"
#include "src/wasm/baseline/liftoff-compiler.h"
#include "src/wasm/code-space-access.h"
#include "src/wasm/compilation-environment-inl.h"
#include "src/wasm/leb-helper.h"
#include "src/wasm/module-compiler.h"
#include "src/wasm/module-instantiate.h"
#include "src/wasm/wasm-code-pointer-table-inl.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-import-wrapper-cache.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-opcodes.h"
#include "src/wasm/wasm-subtyping.h"
namespace v8 {
namespace internal {
namespace wasm {
// Helper Functions.
bool IsSameNan(uint16_t expected, uint16_t actual) {
// Sign is non-deterministic.
uint16_t expected_bits = expected & ~0x8000;
uint16_t actual_bits = actual & ~0x8000;
return (expected_bits == actual_bits);
}
bool IsSameNan(float expected, float actual) {
// Sign is non-deterministic.
uint32_t expected_bits = base::bit_cast<uint32_t>(expected) & ~0x80000000;
uint32_t actual_bits = base::bit_cast<uint32_t>(actual) & ~0x80000000;
// Some implementations convert signaling NaNs to quiet NaNs.
return (expected_bits == actual_bits) ||
((expected_bits | 0x00400000) == actual_bits);
}
bool IsSameNan(double expected, double actual) {
// Sign is non-deterministic.
uint64_t expected_bits =
base::bit_cast<uint64_t>(expected) & ~0x8000000000000000;
uint64_t actual_bits = base::bit_cast<uint64_t>(actual) & ~0x8000000000000000;
// Some implementations convert signaling NaNs to quiet NaNs.
return (expected_bits == actual_bits) ||
((expected_bits | 0x0008000000000000) == actual_bits);
}
TestingModuleBuilder::TestingModuleBuilder(
Zone* zone, ModuleOrigin origin, ManuallyImportedJSFunction* maybe_import,
TestExecutionTier tier, Isolate* isolate)
: test_module_(std::make_shared<WasmModule>(origin)),
isolate_(isolate ? isolate : CcTest::InitIsolateOnce()),
enabled_features_(WasmEnabledFeatures::FromIsolate(isolate_)),
execution_tier_(tier) {
// In this test setup, the NativeModule gets allocated before functions get
// added. The tiering budget array, which gets allocated in the NativeModule
// constructor, therefore does not have slots for functions that get added
// later. By disabling dynamic tiering, the tiering budget does not get
// accessed by generated code.
v8_flags.wasm_dynamic_tiering = false;
WasmJs::Install(isolate_);
test_module_->untagged_globals_buffer_size = kMaxGlobalsSize;
// The GlobalsData must be located inside the sandbox, so allocate it from the
// ArrayBuffer allocator.
globals_data_ = reinterpret_cast<uint8_t*>(
CcTest::array_buffer_allocator()->Allocate(kMaxGlobalsSize));
uint32_t maybe_import_index = 0;
if (maybe_import) {
// Manually add an imported function before any other functions.
// This must happen before the instance object is created, since the
// instance object allocates import entries.
maybe_import_index = AddFunction(maybe_import->sig, nullptr, kImport);
DCHECK_EQ(0, maybe_import_index);
}
instance_object_ = InitInstanceObject();
trusted_instance_data_ =
direct_handle(instance_object_->trusted_data(isolate_), isolate_);
DirectHandle<FixedArray> tables(isolate_->factory()->NewFixedArray(0));
trusted_instance_data_->set_tables(*tables);
if (maybe_import) {
WasmCodeRefScope code_ref_scope;
// Manually compile an import wrapper and insert it into the instance.
CanonicalTypeIndex sig_index =
GetTypeCanonicalizer()->AddRecursiveGroup(maybe_import->sig);
const wasm::CanonicalSig* sig =
GetTypeCanonicalizer()->LookupFunctionSignature(sig_index);
ResolvedWasmImport resolved({}, -1, maybe_import->js_function, sig,
sig_index, WellKnownImport::kUninstantiated);
ImportCallKind kind = resolved.kind();
DirectHandle<JSReceiver> callable = resolved.callable();
WasmCode* import_wrapper = GetWasmImportWrapperCache()->MaybeGet(
kind, sig_index, static_cast<int>(sig->parameter_count()), kNoSuspend);
if (import_wrapper == nullptr) {
import_wrapper = CompileImportWrapperForTest(
isolate_, native_module_, kind, sig, sig_index,
static_cast<int>(sig->parameter_count()), kNoSuspend);
}
ImportedFunctionEntry(trusted_instance_data_, maybe_import_index)
.SetCompiledWasmToJs(isolate_, callable, import_wrapper,
resolved.suspend(), sig, sig_index);
}
}
TestingModuleBuilder::~TestingModuleBuilder() {
// When the native module dies and is erased from the cache, it is expected to
// have either valid bytes or no bytes at all.
native_module_->SetWireBytes({});
CcTest::array_buffer_allocator()->Free(globals_data_, kMaxGlobalsSize);
}
uint8_t* TestingModuleBuilder::AddMemory(uint32_t size, SharedFlag shared,
AddressType address_type,
std::optional<size_t> max_size) {
// The TestingModuleBuilder only supports one memory currently.
CHECK_EQ(0, test_module_->memories.size());
CHECK_NULL(mem0_start_);
CHECK_EQ(0, mem0_size_);
CHECK_EQ(0, trusted_instance_data_->memory_objects()->length());
uint32_t initial_pages = RoundUp(size, kWasmPageSize) / kWasmPageSize;
uint32_t maximum_pages =
max_size.has_value()
? static_cast<uint32_t>(RoundUp(max_size.value(), kWasmPageSize) /
kWasmPageSize)
: initial_pages;
test_module_->memories.resize(1);
WasmMemory* memory = &test_module_->memories[0];
memory->initial_pages = initial_pages;
memory->maximum_pages = maximum_pages;
memory->address_type = address_type;
UpdateComputedInformation(memory, test_module_->origin);
// Create the WasmMemoryObject.
DirectHandle<WasmMemoryObject> memory_object =
WasmMemoryObject::New(isolate_, initial_pages, maximum_pages, shared,
address_type)
.ToHandleChecked();
DirectHandle<FixedArray> memory_objects =
isolate_->factory()->NewFixedArray(1);
memory_objects->set(0, *memory_object);
trusted_instance_data_->set_memory_objects(*memory_objects);
// Create the memory_bases_and_sizes array.
DirectHandle<TrustedFixedAddressArray> memory_bases_and_sizes =
TrustedFixedAddressArray::New(isolate_, 2);
uint8_t* mem_start = reinterpret_cast<uint8_t*>(
memory_object->array_buffer()->backing_store());
memory_bases_and_sizes->set_sandboxed_pointer(
0, reinterpret_cast<Address>(mem_start));
memory_bases_and_sizes->set(1, size);
trusted_instance_data_->set_memory_bases_and_sizes(*memory_bases_and_sizes);
mem0_start_ = mem_start;
mem0_size_ = size;
CHECK(size == 0 || mem0_start_);
// TODO(14616): Add shared_trusted_instance_data_.
WasmMemoryObject::UseInInstance(isolate_, memory_object,
trusted_instance_data_,
trusted_instance_data_, 0);
// TODO(wasm): Delete the following line when test-run-wasm will use a
// multiple of kPageSize as memory size. At the moment, the effect of these
// two lines is used to shrink the memory for testing purposes.
trusted_instance_data_->SetRawMemory(0, mem0_start_, mem0_size_);
return mem0_start_;
}
uint32_t TestingModuleBuilder::AddFunction(const FunctionSig* sig,
const char* name,
FunctionType type) {
if (test_module_->functions.size() == 0) {
// TODO(titzer): Reserving space here to avoid the underlying WasmFunction
// structs from moving.
test_module_->functions.reserve(kMaxFunctions);
DCHECK_NULL(test_module_->validated_functions);
test_module_->validated_functions =
std::make_unique<std::atomic<uint8_t>[]>((kMaxFunctions + 7) / 8);
if (is_asmjs_module(test_module_.get())) {
// All asm.js functions are valid by design.
std::fill_n(test_module_->validated_functions.get(),
(kMaxFunctions + 7) / 8, 0xff);
}
test_module_->type_feedback.well_known_imports.Initialize(kMaxFunctions);
}
uint32_t index = static_cast<uint32_t>(test_module_->functions.size());
test_module_->functions.push_back({sig, // sig
index, // func_index
ModuleTypeIndex{0}, // sig_index
{0, 0}, // code
false, // imported
false, // exported
false}); // declared
if (type == kImport) {
DCHECK_EQ(0, test_module_->num_declared_functions);
++test_module_->num_imported_functions;
test_module_->functions.back().imported = true;
} else {
++test_module_->num_declared_functions;
}
DCHECK_EQ(test_module_->functions.size(),
test_module_->num_imported_functions +
test_module_->num_declared_functions);
if (name) {
base::Vector<const uint8_t> name_vec =
base::Vector<const uint8_t>::cast(base::CStrVector(name));
test_module_->lazily_generated_names.AddForTesting(
index, {AddBytes(name_vec), static_cast<uint32_t>(name_vec.length())});
}
DCHECK_LT(index, kMaxFunctions); // limited for testing.
if (!trusted_instance_data_.is_null()) {
DirectHandle<FixedArray> func_refs =
isolate_->factory()->NewFixedArrayWithZeroes(
static_cast<int>(test_module_->functions.size()));
trusted_instance_data_->set_func_refs(*func_refs);
}
return index;
}
void TestingModuleBuilder::InitializeWrapperCache() {
TypeCanonicalizer::PrepareForCanonicalTypeId(
isolate_, test_module_->MaxCanonicalTypeIndex());
DirectHandle<FixedArray> maps = isolate_->factory()->NewFixedArray(
static_cast<int>(test_module_->types.size()));
for (uint32_t index = 0; index < test_module_->types.size(); index++) {
// TODO(14616): Support shared types.
CreateMapForType(isolate_, test_module_.get(), ModuleTypeIndex{index},
maps);
}
trusted_instance_data_->set_managed_object_maps(*maps);
}
DirectHandle<JSFunction> TestingModuleBuilder::WrapCode(uint32_t index) {
InitializeWrapperCache();
DirectHandle<WasmFuncRef> func_ref =
WasmTrustedInstanceData::GetOrCreateFuncRef(
isolate_, trusted_instance_data_, index);
DirectHandle<WasmInternalFunction> internal{func_ref->internal(isolate_),
isolate_};
return WasmInternalFunction::GetOrCreateExternal(internal);
}
void TestingModuleBuilder::AddIndirectFunctionTable(
const uint16_t* function_indexes, uint32_t table_size,
ValueType table_type) {
uint32_t table_index = static_cast<uint32_t>(test_module_->tables.size());
test_module_->tables.emplace_back();
WasmTable& table = test_module_->tables.back();
table.initial_size = table_size;
table.maximum_size = table_size;
table.has_maximum_size = true;
table.type = table_type;
DirectHandle<HeapObject> value =
table.type.use_wasm_null()
? Cast<HeapObject>(isolate_->factory()->wasm_null())
: Cast<HeapObject>(isolate_->factory()->null_value());
CanonicalValueType canonical_type = test_module_->canonical_type(table.type);
DirectHandle<WasmDispatchTable> dispatch_table;
DirectHandle<WasmTableObject> table_obj = WasmTableObject::New(
isolate_,
direct_handle(instance_object_->trusted_data(isolate_), isolate_),
table.type, canonical_type, table.initial_size, table.has_maximum_size,
table.maximum_size, value,
// TODO(clemensb): Make this configurable.
wasm::AddressType::kI32, &dispatch_table);
WasmDispatchTable::AddUse(isolate_, dispatch_table, trusted_instance_data_,
table_index);
{
// Store the shortcut to the dispatch table.
DirectHandle<ProtectedFixedArray> old_dispatch_tables{
trusted_instance_data_->dispatch_tables(), isolate_};
DCHECK_EQ(table_index, old_dispatch_tables->length());
DirectHandle<ProtectedFixedArray> new_dispatch_tables =
isolate_->factory()->NewProtectedFixedArray(table_index + 1);
for (int i = 0; i < old_dispatch_tables->length(); ++i) {
new_dispatch_tables->set(i, old_dispatch_tables->get(i));
}
new_dispatch_tables->set(table_index, *dispatch_table);
if (table_index == 0) {
trusted_instance_data_->set_dispatch_table0(*dispatch_table);
}
trusted_instance_data_->set_dispatch_tables(*new_dispatch_tables);
}
if (function_indexes) {
WasmCodeRefScope code_ref_scope;
for (uint32_t i = 0; i < table_size; ++i) {
uint32_t function_index = function_indexes[i];
WasmFunction& function = test_module_->functions[function_index];
CanonicalTypeIndex sig_id =
test_module_->canonical_sig_id(function.sig_index);
FunctionTargetAndImplicitArg entry(isolate_, trusted_instance_data_,
function.func_index);
if (function_index < test_module_->num_imported_functions &&
trusted_instance_data_->dispatch_table_for_imports()->IsAWrapper(
function_index)) {
uint64_t signature_hash = SignatureHasher::Hash(function.sig);
trusted_instance_data_->dispatch_table(table_index)
->SetForWrapper(
i, *entry.implicit_arg(),
wasm::GetProcessWideWasmCodePointerTable()->GetEntrypoint(
entry.call_target(), signature_hash),
sig_id, signature_hash,
#if V8_ENABLE_DRUMBRAKE
function.func_index,
#endif // !V8_ENABLE_DRUMBRAKE
wasm::GetWasmImportWrapperCache()->FindWrapper(
entry.call_target()),
WasmDispatchTable::kNewEntry);
} else {
trusted_instance_data_->dispatch_table(table_index)
->SetForNonWrapper(i, *entry.implicit_arg(), entry.call_target(),
sig_id,
#if V8_ENABLE_DRUMBRAKE
function.func_index,
#endif // !V8_ENABLE_DRUMBRAKE
WasmDispatchTable::kNewEntry);
}
WasmTableObject::SetFunctionTablePlaceholder(
isolate_, table_obj, i, trusted_instance_data_, function_indexes[i]);
}
}
DirectHandle<FixedArray> old_tables(trusted_instance_data_->tables(),
isolate_);
DirectHandle<FixedArray> new_tables =
isolate_->factory()->CopyFixedArrayAndGrow(old_tables, 1);
new_tables->set(old_tables->length(), *table_obj);
trusted_instance_data_->set_tables(*new_tables);
}
uint32_t TestingModuleBuilder::AddBytes(base::Vector<const uint8_t> bytes) {
base::Vector<const uint8_t> old_bytes = native_module_->wire_bytes();
uint32_t old_size = static_cast<uint32_t>(old_bytes.size());
// Avoid placing strings at offset 0, this might be interpreted as "not
// set", e.g. for function names.
uint32_t bytes_offset = old_size ? old_size : 1;
size_t new_size = bytes_offset + bytes.size();
base::OwnedVector<uint8_t> new_bytes =
base::OwnedVector<uint8_t>::New(new_size);
if (old_size > 0) {
memcpy(new_bytes.begin(), old_bytes.begin(), old_size);
} else {
// Set the unused byte. It is never decoded, but the bytes are used as the
// key in the native module cache.
new_bytes[0] = 0;
}
memcpy(new_bytes.begin() + bytes_offset, bytes.begin(), bytes.length());
native_module_->SetWireBytes(std::move(new_bytes));
return bytes_offset;
}
uint32_t TestingModuleBuilder::AddException(const FunctionSig* sig) {
DCHECK_EQ(0, sig->return_count());
uint32_t index = static_cast<uint32_t>(test_module_->tags.size());
test_module_->tags.emplace_back(sig, AddSignature(sig));
DirectHandle<WasmExceptionTag> tag = WasmExceptionTag::New(isolate_, index);
DirectHandle<FixedArray> table(trusted_instance_data_->tags_table(),
isolate_);
table = isolate_->factory()->CopyFixedArrayAndGrow(table, 1);
trusted_instance_data_->set_tags_table(*table);
table->set(index, *tag);
return index;
}
uint32_t TestingModuleBuilder::AddPassiveDataSegment(
base::Vector<const uint8_t> bytes) {
uint32_t index = static_cast<uint32_t>(test_module_->data_segments.size());
DCHECK_EQ(index, test_module_->data_segments.size());
DCHECK_EQ(index, data_segment_starts_.size());
DCHECK_EQ(index, data_segment_sizes_.size());
// Add a passive data segment. This isn't used by function compilation, but
// but it keeps the index in sync. The data segment's source will not be
// correct, since we don't store data in the module wire bytes.
test_module_->data_segments.push_back(WasmDataSegment::PassiveForTesting());
// The num_declared_data_segments (from the DataCount section) is used
// to validate the segment index, during function compilation.
test_module_->num_declared_data_segments = index + 1;
Address old_data_address =
reinterpret_cast<Address>(data_segment_data_.data());
size_t old_data_size = data_segment_data_.size();
data_segment_data_.resize(old_data_size + bytes.length());
Address new_data_address =
reinterpret_cast<Address>(data_segment_data_.data());
memcpy(data_segment_data_.data() + old_data_size, bytes.begin(),
bytes.length());
// The data_segment_data_ offset may have moved, so update all the starts.
for (Address& start : data_segment_starts_) {
start += new_data_address - old_data_address;
}
data_segment_starts_.push_back(new_data_address + old_data_size);
data_segment_sizes_.push_back(bytes.length());
// The vector pointers may have moved, so update the instance object.
uint32_t size = static_cast<uint32_t>(data_segment_sizes_.size());
DirectHandle<FixedAddressArray> data_segment_starts =
FixedAddressArray::New(isolate_, size);
MemCopy(data_segment_starts->begin(), data_segment_starts_.data(),
size * sizeof(Address));
trusted_instance_data_->set_data_segment_starts(*data_segment_starts);
DirectHandle<FixedUInt32Array> data_segment_sizes =
FixedUInt32Array::New(isolate_, size);
MemCopy(data_segment_sizes->begin(), data_segment_sizes_.data(),
size * sizeof(uint32_t));
trusted_instance_data_->set_data_segment_sizes(*data_segment_sizes);
return index;
}
const WasmGlobal* TestingModuleBuilder::AddGlobal(ValueType type) {
uint8_t size = type.value_kind_size();
global_offset = (global_offset + size - 1) & ~(size - 1); // align
test_module_->globals.push_back(
{type, true, {}, {global_offset}, false, false, false});
global_offset += size;
// limit number of globals.
CHECK_LT(global_offset, kMaxGlobalsSize);
return &test_module_->globals.back();
}
DirectHandle<WasmInstanceObject> TestingModuleBuilder::InitInstanceObject() {
// Compute the estimate based on {kMaxFunctions} because we might still add
// functions later. Assume 1k of code per function.
int estimated_code_section_length = kMaxFunctions * 1024;
// Pretend to have `kMaxFunctions` already when allocating the `NativeModule`.
DCHECK_EQ(0, test_module_->num_declared_functions);
test_module_->num_declared_functions = kMaxFunctions;
size_t code_size_estimate =
wasm::WasmCodeManager::EstimateNativeModuleCodeSize(
kMaxFunctions, estimated_code_section_length);
auto native_module = GetWasmEngine()->NewNativeModule(
isolate_, enabled_features_, WasmDetectedFeatures{}, CompileTimeImports{},
test_module_, code_size_estimate);
// Reset the declared functions; functions will be added later in the test.
test_module_->num_declared_functions = 0;
native_module->SetWireBytes(base::OwnedVector<const uint8_t>());
native_module->compilation_state()->set_compilation_id(0);
constexpr base::Vector<const char> kNoSourceUrl{"", 0};
DirectHandle<Script> script =
GetWasmEngine()->GetOrCreateScript(isolate_, native_module, kNoSourceUrl);
// Asm.js modules are expected to have "normal" scripts, not Wasm scripts.
if (is_asmjs_module(native_module->module())) {
script->set_type(Script::Type::kNormal);
script->set_infos(ReadOnlyRoots{isolate_}.empty_weak_fixed_array());
}
DirectHandle<WasmModuleObject> module_object =
WasmModuleObject::New(isolate_, std::move(native_module), script);
native_module_ = module_object->native_module();
DirectHandle<WasmTrustedInstanceData> trusted_data =
WasmTrustedInstanceData::New(isolate_, module_object, false);
// TODO(42204563): Avoid crashing if the instance object is not available.
CHECK(trusted_data->has_instance_object());
DirectHandle<WasmInstanceObject> instance_object(
trusted_data->instance_object(), isolate_);
trusted_data->set_tags_table(ReadOnlyRoots{isolate_}.empty_fixed_array());
trusted_data->set_globals_start(globals_data_);
DirectHandle<FixedArray> feedback_vector =
isolate_->factory()->NewFixedArrayWithZeroes(kMaxFunctions);
trusted_data->set_feedback_vectors(*feedback_vector);
return instance_object;
}
// This struct is just a type tag for Zone::NewArray<T>(size_t) call.
struct WasmFunctionCompilerBuffer {};
void WasmFunctionCompiler::Build(base::Vector<const uint8_t> bytes) {
size_t locals_size = local_decls_.Size();
size_t total_size = bytes.size() + locals_size + 1;
uint8_t* buffer =
zone_->AllocateArray<uint8_t, WasmFunctionCompilerBuffer>(total_size);
// Prepend the local decls to the code.
local_decls_.Emit(buffer);
// Emit the code.
memcpy(buffer + locals_size, bytes.begin(), bytes.size());
// Append an extra end opcode.
buffer[total_size - 1] = kExprEnd;
bytes = base::VectorOf(buffer, total_size);
function_->code = {builder_->AddBytes(bytes),
static_cast<uint32_t>(bytes.size())};
NativeModule* native_module =
builder_->trusted_instance_data()->native_module();
base::Vector<const uint8_t> wire_bytes = native_module->wire_bytes();
CompilationEnv env = CompilationEnv::ForModule(native_module);
base::ScopedVector<uint8_t> func_wire_bytes(function_->code.length());
memcpy(func_wire_bytes.begin(), wire_bytes.begin() + function_->code.offset(),
func_wire_bytes.length());
constexpr bool kIsShared = false; // TODO(14616): Extend this.
FunctionBody func_body{function_->sig, function_->code.offset(),
func_wire_bytes.begin(), func_wire_bytes.end(),
kIsShared};
ForDebugging for_debugging =
native_module->IsInDebugState() ? kForDebugging : kNotForDebugging;
WasmDetectedFeatures unused_detected_features;
// Validate Wasm modules; asm.js is assumed to be always valid.
if (env.module->origin == kWasmOrigin) {
DecodeResult validation_result =
ValidateFunctionBody(zone_, env.enabled_features, env.module,
&unused_detected_features, func_body);
if (validation_result.failed()) {
FATAL("Validation failed: %s",
validation_result.error().message().c_str());
}
env.module->set_function_validated(function_->func_index);
}
if (v8_flags.wasm_jitless) return;
std::optional<WasmCompilationResult> result;
if (builder_->test_execution_tier() ==
TestExecutionTier::kLiftoffForFuzzing) {
result.emplace(
ExecuteLiftoffCompilation(&env, func_body,
LiftoffOptions{}
.set_func_index(function_->func_index)
.set_for_debugging(kForDebugging)
.set_max_steps(builder_->max_steps_ptr())
.set_detect_nondeterminism(true)));
} else {
WasmCompilationUnit unit(function_->func_index, builder_->execution_tier(),
for_debugging);
result.emplace(unit.ExecuteCompilation(
&env, native_module->compilation_state()->GetWireBytesStorage().get(),
nullptr, &unused_detected_features));
}
CHECK(result->succeeded());
WasmCode* code =
native_module->PublishCode(native_module->AddCompiledCode(*result));
DCHECK_NOT_NULL(code);
DisallowGarbageCollection no_gc;
Tagged<Script> script =
builder_->instance_object()->module_object()->script();
std::unique_ptr<char[]> source_url =
Cast<String>(script->name())->ToCString();
if (WasmCode::ShouldBeLogged(isolate())) {
code->LogCode(isolate(), source_url.get(), script->id());
}
}
WasmFunctionCompiler::WasmFunctionCompiler(Zone* zone, const FunctionSig* sig,
TestingModuleBuilder* builder,
const char* name)
: zone_(zone), builder_(builder), local_decls_(zone, sig) {
// Get a new function from the testing module.
int index = builder->AddFunction(sig, name, TestingModuleBuilder::kWasm);
function_ = builder_->GetFunctionAt(index);
}
WasmFunctionCompiler::~WasmFunctionCompiler() = default;
FunctionSig* WasmRunnerBase::CreateSig(MachineType return_type,
base::Vector<MachineType> param_types) {
int return_count = return_type.IsNone() ? 0 : 1;
int param_count = param_types.length();
Zone& zone = builder_.SignatureZone();
// Allocate storage array in zone.
ValueType* sig_types =
zone.AllocateArray<ValueType>(return_count + param_count);
// Convert machine types to local types, and check that there are no
// MachineType::None()'s in the parameters.
int idx = 0;
if (return_count) sig_types[idx++] = ValueType::For(return_type);
for (MachineType param : param_types) {
CHECK_NE(MachineType::None(), param);
sig_types[idx++] = ValueType::For(param);
}
return zone.New<FunctionSig>(return_count, param_count, sig_types);
}
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,642 @@
// Copyright 2016 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.
#ifndef WASM_RUN_UTILS_H
#define WASM_RUN_UTILS_H
#include <setjmp.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <array>
#include <memory>
#include "src/base/utils/random-number-generator.h"
#include "src/compiler/compiler-source-position-table.h"
#include "src/compiler/int64-lowering.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/node.h"
#include "src/compiler/wasm-compiler.h"
#include "src/trap-handler/trap-handler.h"
#include "src/wasm/canonical-types.h"
#include "src/wasm/function-body-decoder.h"
#include "src/wasm/local-decl-encoder.h"
#include "src/wasm/wasm-code-manager.h"
#include "src/wasm/wasm-external-refs.h"
#include "src/wasm/wasm-js.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-objects.h"
#include "src/wasm/wasm-opcodes.h"
#include "src/wasm/wasm-tier.h"
#include "src/zone/accounting-allocator.h"
#include "src/zone/zone.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/graph-and-builders.h"
#include "test/common/call-tester.h"
#include "test/common/value-helper.h"
#include "test/common/wasm/flag-utils.h"
#if V8_ENABLE_DRUMBRAKE
#include "src/wasm/interpreter/wasm-interpreter.h"
#endif // V8_ENABLE_DRUMBRAKE
namespace v8::internal::wasm {
enum class TestExecutionTier : int8_t {
#if V8_ENABLE_DRUMBRAKE
kInterpreter = static_cast<int8_t>(ExecutionTier::kInterpreter),
#endif // V8_ENABLE_DRUMBRAKE
kLiftoff = static_cast<int8_t>(ExecutionTier::kLiftoff),
kTurbofan = static_cast<int8_t>(ExecutionTier::kTurbofan),
kLiftoffForFuzzing
};
static_assert(
std::is_same<std::underlying_type<ExecutionTier>::type,
std::underlying_type<TestExecutionTier>::type>::value,
"enum types match");
using base::ReadLittleEndianValue;
using base::WriteLittleEndianValue;
constexpr uint32_t kMaxFunctions = 10;
constexpr uint32_t kMaxGlobalsSize = 128;
// Don't execute more than 16k steps.
constexpr int kMaxNumSteps = 16 * 1024;
using compiler::CallDescriptor;
using compiler::MachineTypeForC;
using compiler::Node;
// TODO(titzer): check traps more robustly in tests.
// Currently, in tests, we just return 0xDEADBEEF from the function in which
// the trap occurs if the runtime context is not available to throw a JavaScript
// exception.
#define CHECK_TRAP32(x) \
CHECK_EQ(0xDEADBEEF, (base::bit_cast<uint32_t>(x)) & 0xFFFFFFFF)
#define CHECK_TRAP64(x) \
CHECK_EQ(0xDEADBEEFDEADBEEF, \
(base::bit_cast<uint64_t>(x)) & 0xFFFFFFFFFFFFFFFF)
#define CHECK_TRAP(x) CHECK_TRAP32(x)
#define WASM_WRAPPER_RETURN_VALUE 8754
#define ADD_CODE(vec, ...) \
do { \
uint8_t __buf[] = {__VA_ARGS__}; \
for (size_t __i = 0; __i < sizeof(__buf); __i++) \
vec.push_back(__buf[__i]); \
} while (false)
// For tests that must manually import a JSFunction with source code.
struct ManuallyImportedJSFunction {
const FunctionSig* sig;
DirectHandle<JSFunction> js_function;
};
// Helper Functions.
bool IsSameNan(uint16_t expected, uint16_t actual);
bool IsSameNan(float expected, float actual);
bool IsSameNan(double expected, double actual);
// A Wasm module builder. Globals are pre-set, however, memory and code may be
// progressively added by a test. In turn, we piecemeal update the runtime
// objects, i.e. {WasmInstanceObject} and {WasmModuleObject}.
class TestingModuleBuilder {
public:
TestingModuleBuilder(Zone*, ModuleOrigin origin, ManuallyImportedJSFunction*,
TestExecutionTier, Isolate* isolate);
~TestingModuleBuilder();
uint8_t* AddMemory(uint32_t size, SharedFlag shared = SharedFlag::kNotShared,
AddressType address_type = wasm::AddressType::kI32,
std::optional<size_t> max_size = {});
size_t CodeTableLength() const { return native_module_->num_functions(); }
template <typename T>
T* AddMemoryElems(uint32_t count,
AddressType address_type = wasm::AddressType::kI32) {
AddMemory(count * sizeof(T), SharedFlag::kNotShared, address_type);
return raw_mem_start<T>();
}
template <typename T>
T* AddGlobal(ValueType type = ValueType::For(MachineTypeForC<T>())) {
const WasmGlobal* global = AddGlobal(type);
return reinterpret_cast<T*>(globals_data_ + global->offset);
}
Zone& SignatureZone() { return test_module_->signature_zone; }
// TODO(14034): Allow selecting type finality.
ModuleTypeIndex AddSignature(const FunctionSig* sig) {
const bool is_final = true;
const bool is_shared = false;
test_module_->AddSignatureForTesting(sig, kNoSuperType, is_final,
is_shared);
GetTypeCanonicalizer()->AddRecursiveGroup(test_module_.get(), 1);
size_t size = test_module_->types.size();
// The {ModuleTypeIndex} can handle more, but users of this class
// often assume that each generated index fits into a byte, so
// ensure that here.
CHECK_GT(127, size);
return ModuleTypeIndex{static_cast<uint32_t>(size - 1)};
}
uint32_t mem_size() const {
CHECK_EQ(1, test_module_->memories.size());
return mem0_size_;
}
template <typename T>
T* raw_mem_start() const {
DCHECK_NOT_NULL(mem0_start_);
return reinterpret_cast<T*>(mem0_start_);
}
template <typename T>
T* raw_mem_end() const {
DCHECK_NOT_NULL(mem0_start_);
return reinterpret_cast<T*>(mem0_start_ + mem0_size_);
}
template <typename T>
T raw_mem_at(int i) {
DCHECK_NOT_NULL(mem0_start_);
return ReadMemory(&(reinterpret_cast<T*>(mem0_start_)[i]));
}
template <typename T>
T raw_val_at(int i) {
return ReadMemory(reinterpret_cast<T*>(mem0_start_ + i));
}
template <typename T>
void WriteMemory(T* p, T val) {
WriteLittleEndianValue<T>(reinterpret_cast<Address>(p), val);
}
template <typename T>
T ReadMemory(T* p) {
return ReadLittleEndianValue<T>(reinterpret_cast<Address>(p));
}
// Zero-initialize the memory.
void BlankMemory() {
uint8_t* raw = raw_mem_start<uint8_t>();
memset(raw, 0, mem0_size_);
}
// Pseudo-randomly initialize the memory.
void RandomizeMemory(unsigned int seed = 88) {
uint8_t* raw = raw_mem_start<uint8_t>();
uint8_t* end = raw_mem_end<uint8_t>();
v8::base::RandomNumberGenerator rng;
rng.SetSeed(seed);
rng.NextBytes(raw, end - raw);
}
void SetMemoryShared() {
CHECK_EQ(1, test_module_->memories.size());
test_module_->memories[0].is_shared = true;
}
enum FunctionType { kImport, kWasm };
uint32_t AddFunction(const FunctionSig* sig, const char* name,
FunctionType type);
// Freezes the signature map of the module and allocates the storage for
// export wrappers.
void InitializeWrapperCache();
// Wrap the code so it can be called as a JS function.
DirectHandle<JSFunction> WrapCode(uint32_t index);
// If function_indexes is {nullptr}, the contents of the table will be
// initialized with null functions.
void AddIndirectFunctionTable(const uint16_t* function_indexes,
uint32_t table_size,
ValueType table_type = kWasmFuncRef);
uint32_t AddBytes(base::Vector<const uint8_t> bytes);
uint32_t AddException(const FunctionSig* sig);
uint32_t AddPassiveDataSegment(base::Vector<const uint8_t> bytes);
WasmFunction* GetFunctionAt(int index) {
return &test_module_->functions[index];
}
Isolate* isolate() const { return isolate_; }
DirectHandle<WasmInstanceObject> instance_object() const {
return instance_object_;
}
DirectHandle<WasmTrustedInstanceData> trusted_instance_data() const {
return trusted_instance_data_;
}
WasmCode* GetFunctionCode(uint32_t index) const {
return native_module_->GetCode(index);
}
Address globals_start() const {
return reinterpret_cast<Address>(globals_data_);
}
void SetDebugState() {
native_module_->SetDebugState(kDebugging);
execution_tier_ = TestExecutionTier::kLiftoff;
}
void SwitchToDebug() {
SetDebugState();
WasmCodeRefScope ref_scope;
native_module_->RemoveCompiledCode(
NativeModule::RemoveFilter::kRemoveNonDebugCode);
}
TestExecutionTier test_execution_tier() const { return execution_tier_; }
ExecutionTier execution_tier() const {
switch (execution_tier_) {
#if V8_ENABLE_DRUMBRAKE
case TestExecutionTier::kInterpreter:
return ExecutionTier::kInterpreter;
#endif // V8_ENABLE_DRUMBRAKE
case TestExecutionTier::kTurbofan:
return ExecutionTier::kTurbofan;
case TestExecutionTier::kLiftoff:
return ExecutionTier::kLiftoff;
default:
UNREACHABLE();
}
}
void set_max_steps(int n) { max_steps_ = n; }
int* max_steps_ptr() { return &max_steps_; }
void EnableFeature(WasmEnabledFeature feature) {
enabled_features_.Add(feature);
}
private:
std::shared_ptr<WasmModule> test_module_;
Isolate* isolate_;
WasmEnabledFeatures enabled_features_;
uint32_t global_offset = 0;
// The TestingModuleBuilder only supports one memory currently.
uint8_t* mem0_start_ = nullptr;
uint32_t mem0_size_ = 0;
uint8_t* globals_data_ = nullptr;
TestExecutionTier execution_tier_;
DirectHandle<WasmInstanceObject> instance_object_;
DirectHandle<WasmTrustedInstanceData> trusted_instance_data_;
NativeModule* native_module_ = nullptr;
int32_t max_steps_ = kMaxNumSteps;
// Data segment arrays that are normally allocated on the instance.
std::vector<uint8_t> data_segment_data_;
std::vector<Address> data_segment_starts_;
std::vector<uint32_t> data_segment_sizes_;
const WasmGlobal* AddGlobal(ValueType type);
DirectHandle<WasmInstanceObject> InitInstanceObject();
};
// A helper for compiling wasm functions for testing.
// It contains the internal state for compilation (i.e. TurboFan graph).
class WasmFunctionCompiler {
public:
~WasmFunctionCompiler();
Isolate* isolate() { return builder_->isolate(); }
uint32_t function_index() { return function_->func_index; }
ModuleTypeIndex sig_index() { return function_->sig_index; }
void Build(std::initializer_list<const uint8_t> bytes) {
Build(base::VectorOf(bytes));
}
void Build(base::Vector<const uint8_t> bytes);
uint8_t AllocateLocal(ValueType type) {
uint32_t index = local_decls_.AddLocals(1, type);
uint8_t result = static_cast<uint8_t>(index);
DCHECK_EQ(index, result);
return result;
}
void SetSigIndex(ModuleTypeIndex sig_index) {
function_->sig_index = sig_index;
}
private:
friend class WasmRunnerBase;
WasmFunctionCompiler(Zone* zone, const FunctionSig* sig,
TestingModuleBuilder* builder, const char* name);
Zone* zone_;
TestingModuleBuilder* builder_;
WasmFunction* function_;
LocalDeclEncoder local_decls_;
};
// A helper class to build a module around Wasm bytecode, generate machine
// code, and run that code.
class WasmRunnerBase : public InitializedHandleScope {
public:
WasmRunnerBase(ManuallyImportedJSFunction* maybe_import, ModuleOrigin origin,
TestExecutionTier execution_tier, int num_params,
Isolate* isolate = nullptr)
: InitializedHandleScope(isolate),
zone_(&allocator_, ZONE_NAME, kCompressGraphZone),
builder_(&zone_, origin, maybe_import, execution_tier, isolate) {}
// Builds a graph from the given Wasm code and generates the machine
// code and call wrapper for that graph. This method must not be called
// more than once.
void Build(const uint8_t* start, const uint8_t* end) {
Build(base::VectorOf(start, end - start));
}
void Build(std::initializer_list<const uint8_t> bytes) {
Build(base::VectorOf(bytes));
}
void Build(base::Vector<const uint8_t> bytes) {
CHECK(!compiled_);
compiled_ = true;
functions_[0]->Build(bytes);
}
// Resets the state for building the next function.
// The main function called will always be the first function.
template <typename ReturnType, typename... ParamTypes>
WasmFunctionCompiler& NewFunction(const char* name = nullptr) {
return NewFunction(CreateSig<ReturnType, ParamTypes...>(), name);
}
// Resets the state for building the next function.
// The main function called will be the last generated function.
// Returns the index of the previously built function.
WasmFunctionCompiler& NewFunction(const FunctionSig* sig,
const char* name = nullptr) {
functions_.emplace_back(
new WasmFunctionCompiler(&zone_, sig, &builder_, name));
ModuleTypeIndex sig_index = builder().AddSignature(sig);
functions_.back()->SetSigIndex(sig_index);
return *functions_.back();
}
uint8_t AllocateLocal(ValueType type) {
return functions_[0]->AllocateLocal(type);
}
uint32_t function_index() { return functions_[0]->function_index(); }
WasmFunction* function() { return functions_[0]->function_; }
bool possible_nondeterminism() { return possible_nondeterminism_; }
TestingModuleBuilder& builder() { return builder_; }
Zone* zone() { return &zone_; }
void SwitchToDebug() { builder_.SwitchToDebug(); }
static const CanonicalSig* CanonicalizeSig(const FunctionSig* sig) {
// TODO(clemensb): Make this a single function call.
CanonicalTypeIndex sig_id = GetTypeCanonicalizer()->AddRecursiveGroup(sig);
return GetTypeCanonicalizer()->LookupFunctionSignature(sig_id);
}
template <typename ReturnType, typename... ParamTypes>
FunctionSig* CreateSig() {
std::array<MachineType, sizeof...(ParamTypes)> param_machine_types{
{MachineTypeForC<ParamTypes>()...}};
base::Vector<MachineType> param_vec(param_machine_types.data(),
param_machine_types.size());
return CreateSig(MachineTypeForC<ReturnType>(), param_vec);
}
// TODO(clemensb): Remove, use {CallViaJS} directly.
void CheckCallApplyViaJS(double expected, uint32_t function_index,
base::Vector<const DirectHandle<Object>> args) {
MaybeDirectHandle<Object> retval = CallViaJS(function_index, args);
if (retval.is_null()) {
CHECK_EQ(expected, static_cast<double>(0xDEADBEEF));
} else {
DirectHandle<Object> result = retval.ToHandleChecked();
if (IsSmi(*result)) {
CHECK_EQ(expected, Smi::ToInt(*result));
} else {
CHECK(IsHeapNumber(*result));
CHECK_DOUBLE_EQ(expected, Cast<HeapNumber>(*result)->value());
}
}
}
MaybeDirectHandle<Object> CallViaJS(
uint32_t function_index,
base::Vector<const DirectHandle<Object>> parameters) {
Isolate* isolate = main_isolate();
// Save the original context, because CEntry (for runtime calls) will
// reset / invalidate it when returning.
SaveContext save_context(isolate);
if (!jsfuncs_.has_value()) {
jsfuncs_.emplace(isolate);
}
if (jsfuncs_->size() <= function_index) {
jsfuncs_->resize(function_index + 1);
}
if ((*jsfuncs_)[function_index].is_null()) {
(*jsfuncs_)[function_index] = builder_.WrapCode(function_index);
}
DirectHandle<JSFunction> jsfunc = (*jsfuncs_)[function_index];
DirectHandle<Object> global(isolate->context()->global_object(), isolate);
return Execution::TryCall(isolate, jsfunc, global, parameters,
Execution::MessageHandling::kReport, nullptr);
}
private:
FunctionSig* CreateSig(MachineType return_type,
base::Vector<MachineType> param_types);
protected:
wasm::WasmCodeRefScope code_ref_scope_;
std::optional<DirectHandleVector<JSFunction>> jsfuncs_;
v8::internal::AccountingAllocator allocator_;
Zone zone_;
TestingModuleBuilder builder_;
std::vector<std::unique_ptr<WasmFunctionCompiler>> functions_;
bool compiled_ = false;
bool possible_nondeterminism_ = false;
int32_t main_fn_index_ = 0;
static void SetThreadInWasmFlag() {
*reinterpret_cast<int*>(trap_handler::GetThreadInWasmThreadLocalAddress()) =
true;
}
static void ClearThreadInWasmFlag() {
*reinterpret_cast<int*>(trap_handler::GetThreadInWasmThreadLocalAddress()) =
false;
}
};
template <typename T>
inline WasmValue WasmValueInitializer(T value) {
return WasmValue(value);
}
template <>
inline WasmValue WasmValueInitializer(int8_t value) {
return WasmValue(static_cast<int32_t>(value));
}
template <>
inline WasmValue WasmValueInitializer(int16_t value) {
return WasmValue(static_cast<int32_t>(value));
}
template <typename ReturnType, typename... ParamTypes>
class WasmRunner : public WasmRunnerBase {
public:
explicit WasmRunner(TestExecutionTier execution_tier,
ModuleOrigin origin = kWasmOrigin,
ManuallyImportedJSFunction* maybe_import = nullptr,
const char* main_fn_name = "main",
Isolate* isolate = nullptr)
: WasmRunnerBase(maybe_import, origin, execution_tier,
sizeof...(ParamTypes), isolate) {
WasmFunctionCompiler& main_fn =
NewFunction<ReturnType, ParamTypes...>(main_fn_name);
// Non-zero if there is an import.
main_fn_index_ = main_fn.function_index();
}
template <typename T>
DirectHandle<Object> MakeParam(T t) {
Factory* factory = builder_.isolate()->factory();
if constexpr (std::is_integral_v<T> && std::is_signed_v<T> &&
sizeof(T) <= sizeof(int)) {
return factory->NewNumberFromInt(t);
}
if constexpr (std::is_integral_v<T> && std::is_unsigned_v<T> &&
sizeof(T) <= sizeof(int)) {
return factory->NewNumberFromUint(t);
}
if constexpr (std::is_same_v<T, int64_t>) {
return BigInt::FromInt64(builder_.isolate(), t);
}
if constexpr (std::is_same_v<T, uint64_t>) {
return BigInt::FromUint64(builder_.isolate(), t);
}
if constexpr (std::is_same_v<T, float>) {
return factory->NewNumber(t);
}
if constexpr (std::is_same_v<T, double>) {
return factory->NewNumber(t);
}
UNIMPLEMENTED();
}
ReturnType Call(ParamTypes... p) {
std::array<DirectHandle<Object>, sizeof...(p)> param_objs = {
MakeParam(p)...};
MaybeDirectHandle<Object> retval =
CallViaJS(function()->func_index, base::VectorOf(param_objs));
if constexpr (std::is_void_v<ReturnType>) {
return;
}
if (retval.is_null()) {
return static_cast<ReturnType>(0xDEADBEEFDEADBEEF);
}
DirectHandle<Object> result = retval.ToHandleChecked();
// For int64_t and uint64_t returns we will get a BigInt.
if constexpr (std::is_integral_v<ReturnType>) {
if constexpr (sizeof(ReturnType) == sizeof(int64_t)) {
CHECK(IsBigInt(*result));
return Cast<BigInt>(*result)->AsInt64();
}
}
// Otherwise it must be a number (Smi or HeapNumber).
CHECK(IsNumber(*result));
double value = Object::NumberValue(Cast<Number>(*result));
// The JS API interprets all Wasm values as signed, hence we cast via the
// signed equivalent type to avoid undefined behaviour in the casting.
if constexpr (std::is_integral_v<ReturnType> &&
std::is_unsigned_v<ReturnType>) {
using signed_t = std::make_signed_t<ReturnType>;
return static_cast<ReturnType>(static_cast<signed_t>(value));
}
return static_cast<ReturnType>(value);
}
void CheckCallViaJS(double expected, ParamTypes... p) {
// TODO(clemensb): Inline into callers; use {Call} and {CHECK_EQ} directly.
ReturnType result = Call(p...);
if constexpr (std::is_floating_point_v<ReturnType>) {
if (std::isnan(result)) {
CHECK(IsSameNan(static_cast<ReturnType>(expected), result));
return;
}
}
CHECK_EQ(expected, result);
}
void CheckCallViaJSTraps(ParamTypes... p) {
std::array<DirectHandle<Object>, sizeof...(p)> param_objs = {
MakeParam(p)...};
MaybeDirectHandle<Object> retval =
CallViaJS(function()->func_index, base::VectorOf(param_objs));
CHECK(retval.is_null());
}
void SetMaxSteps(int n) { builder_.set_max_steps(n); }
};
// A macro to define tests that run in different engine configurations.
#if V8_ENABLE_DRUMBRAKE
#define TEST_IF_DRUMBRAKE(name) \
TEST(RunWasmInterpreter_##name) { \
FLAG_SCOPE(wasm_jitless); \
WasmInterpreterThread::Initialize(); \
RunWasm_##name(TestExecutionTier::kInterpreter); \
WasmInterpreterThread::Terminate(); \
}
#else
#define TEST_IF_DRUMBRAKE(name)
#endif // V8_ENABLE_DRUMBRAKE
#define WASM_EXEC_TEST(name) \
void RunWasm_##name(TestExecutionTier execution_tier); \
TEST(RunWasmTurbofan_##name) { \
RunWasm_##name(TestExecutionTier::kTurbofan); \
} \
TEST(RunWasmLiftoff_##name) { RunWasm_##name(TestExecutionTier::kLiftoff); } \
TEST_IF_DRUMBRAKE(name) \
void RunWasm_##name(TestExecutionTier execution_tier)
#define UNINITIALIZED_WASM_EXEC_TEST(name) \
void RunWasm_##name(TestExecutionTier execution_tier); \
UNINITIALIZED_TEST(RunWasmTurbofan_##name) { \
RunWasm_##name(TestExecutionTier::kTurbofan); \
} \
UNINITIALIZED_TEST(RunWasmLiftoff_##name) { \
RunWasm_##name(TestExecutionTier::kLiftoff); \
} \
void RunWasm_##name(TestExecutionTier execution_tier)
#define WASM_COMPILED_EXEC_TEST(name) \
void RunWasm_##name(TestExecutionTier execution_tier); \
TEST(RunWasmTurbofan_##name) { \
RunWasm_##name(TestExecutionTier::kTurbofan); \
} \
TEST(RunWasmLiftoff_##name) { RunWasm_##name(TestExecutionTier::kLiftoff); } \
void RunWasm_##name(TestExecutionTier execution_tier)
} // namespace v8::internal::wasm
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,379 @@
// Copyright 2021 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 <stddef.h>
#include <stdint.h>
#include "src/base/macros.h"
#include "src/compiler/node-observer.h"
#include "src/compiler/opcodes.h"
#include "src/wasm/compilation-environment.h"
#include "src/wasm/wasm-opcodes.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/common/wasm/wasm-macro-gen.h"
#ifdef V8_ENABLE_WASM_SIMD256_REVEC
#include "src/compiler/turboshaft/wasm-revec-phase.h"
#endif // V8_ENABLE_WASM_SIMD256_REVEC
namespace v8 {
namespace internal {
#ifdef V8_ENABLE_WASM_SIMD256_REVEC
enum class ExpectedResult {
kFail,
kPass,
};
class TSSimd256VerifyScope {
public:
static bool VerifyHaveAnySimd256Op(const compiler::turboshaft::Graph& graph) {
for (const compiler::turboshaft::Operation& op : graph.AllOperations()) {
switch (op.opcode) {
#define CASE_SIMD256(name) \
case compiler::turboshaft::Opcode::k##name: { \
return true; \
}
TURBOSHAFT_SIMD256_OPERATION_LIST(CASE_SIMD256)
default:
break;
}
#undef CASE_SIMD256
}
return false;
}
template <compiler::turboshaft::Opcode opcode>
static bool VerifyHaveOpcode(const compiler::turboshaft::Graph& graph) {
for (const compiler::turboshaft::Operation& op : graph.AllOperations()) {
if (op.opcode == opcode) {
return true;
}
}
return false;
}
template <typename TOp, TOp::Kind op_kind>
static bool VerifyHaveOpWithKind(const compiler::turboshaft::Graph& graph) {
for (const compiler::turboshaft::Operation& op : graph.AllOperations()) {
if (const TOp* t_op = op.TryCast<TOp>()) {
if (t_op->kind == op_kind) {
return true;
}
}
}
return false;
}
explicit TSSimd256VerifyScope(
Zone* zone,
std::function<bool(const compiler::turboshaft::Graph&)> raw_handler =
TSSimd256VerifyScope::VerifyHaveAnySimd256Op,
ExpectedResult expected = ExpectedResult::kPass)
: expected_(expected) {
std::function<void(const compiler::turboshaft::Graph&)> handler =
[raw_handler, this](const compiler::turboshaft::Graph& graph) {
check_pass_ = raw_handler(graph);
};
verifier_ =
std::make_unique<compiler::turboshaft::WasmRevecVerifier>(handler);
isolate_ = CcTest::InitIsolateOnce();
DCHECK_EQ(isolate_->wasm_revec_verifier_for_test(), nullptr);
isolate_->set_wasm_revec_verifier_for_test(verifier_.get());
}
~TSSimd256VerifyScope() {
isolate_->set_wasm_revec_verifier_for_test(nullptr);
CHECK_EQ(expected_ == ExpectedResult::kPass, check_pass_);
}
bool check_pass_ = false;
ExpectedResult expected_ = ExpectedResult::kPass;
Isolate* isolate_ = nullptr;
std::unique_ptr<compiler::turboshaft::WasmRevecVerifier> verifier_;
};
class SIMD256NodeObserver : public compiler::NodeObserver {
public:
explicit SIMD256NodeObserver(
std::function<void(const compiler::Node*)> handler)
: handler_(handler) {
DCHECK(handler_);
}
Observation OnNodeCreated(const compiler::Node* node) override {
handler_(node);
return Observation::kContinue;
}
private:
std::function<void(const compiler::Node*)> handler_;
};
class ObserveSIMD256Scope {
public:
explicit ObserveSIMD256Scope(Isolate* isolate,
compiler::NodeObserver* node_observer)
: isolate_(isolate), node_observer_(node_observer) {
DCHECK_NOT_NULL(isolate_);
DCHECK_NULL(isolate_->node_observer());
isolate_->set_node_observer(node_observer_);
}
~ObserveSIMD256Scope() {
DCHECK_NOT_NULL(isolate_->node_observer());
isolate_->set_node_observer(nullptr);
}
Isolate* isolate_;
compiler::NodeObserver* node_observer_;
};
// Build input wasm expressions and check if the revectorization success
// (create the expected simd256 node).
// TODO(42202660): Reimplement checks for Turboshaft (Turbofan checks were
// removed in https://crrev.com/c/6074953).
#define BUILD_AND_CHECK_REVEC_NODE(wasm_runner, expected_simd256_op, ...) \
r.Build({__VA_ARGS__});
#endif // V8_ENABLE_WASM_SIMD256_REVEC
namespace wasm {
using Int8UnOp = int8_t (*)(int8_t);
using Int8BinOp = int8_t (*)(int8_t, int8_t);
using Uint8BinOp = uint8_t (*)(uint8_t, uint8_t);
using Int8CompareOp = int (*)(int8_t, int8_t);
using Int8ShiftOp = int8_t (*)(int8_t, int);
using Int16UnOp = int16_t (*)(int16_t);
using Int16BinOp = int16_t (*)(int16_t, int16_t);
using Uint16BinOp = uint16_t (*)(uint16_t, uint16_t);
using Int16ShiftOp = int16_t (*)(int16_t, int);
using Int32UnOp = int32_t (*)(int32_t);
using Int32BinOp = int32_t (*)(int32_t, int32_t);
using Uint32BinOp = uint32_t (*)(uint32_t, uint32_t);
using Int32ShiftOp = int32_t (*)(int32_t, int);
using Int64UnOp = int64_t (*)(int64_t);
using Int64BinOp = int64_t (*)(int64_t, int64_t);
using Int64ShiftOp = int64_t (*)(int64_t, int);
using HalfUnOp = uint16_t (*)(uint16_t);
using HalfBinOp = uint16_t (*)(uint16_t, uint16_t);
using HalfCompareOp = int16_t (*)(uint16_t, uint16_t);
using FloatUnOp = float (*)(float);
using FloatBinOp = float (*)(float, float);
using FloatCompareOp = int32_t (*)(float, float);
using DoubleUnOp = double (*)(double);
using DoubleBinOp = double (*)(double, double);
using DoubleCompareOp = int64_t (*)(double, double);
using ConvertToIntOp = int32_t (*)(double, bool);
void RunI8x16UnOpTest(TestExecutionTier execution_tier, WasmOpcode opcode,
Int8UnOp expected_op);
template <typename T = int8_t, typename OpType = T (*)(T, T)>
void RunI8x16BinOpTest(TestExecutionTier execution_tier, WasmOpcode opcode,
OpType expected_op);
void RunI8x16ShiftOpTest(TestExecutionTier execution_tier, WasmOpcode opcode,
Int8ShiftOp expected_op);
void RunI8x16MixedRelationalOpTest(TestExecutionTier execution_tier,
WasmOpcode opcode, Int8BinOp expected_op);
void RunI16x8UnOpTest(TestExecutionTier execution_tier, WasmOpcode opcode,
Int16UnOp expected_op);
template <typename T = int16_t, typename OpType = T (*)(T, T)>
void RunI16x8BinOpTest(TestExecutionTier execution_tier, WasmOpcode opcode,
OpType expected_op);
void RunI16x8ShiftOpTest(TestExecutionTier execution_tier, WasmOpcode opcode,
Int16ShiftOp expected_op);
void RunI16x8MixedRelationalOpTest(TestExecutionTier execution_tier,
WasmOpcode opcode, Int16BinOp expected_op);
void RunI32x4UnOpTest(TestExecutionTier execution_tier, WasmOpcode opcode,
Int32UnOp expected_op);
void RunI32x4BinOpTest(TestExecutionTier execution_tier, WasmOpcode opcode,
Int32BinOp expected_op);
void RunI32x4ShiftOpTest(TestExecutionTier execution_tier, WasmOpcode opcode,
Int32ShiftOp expected_op);
void RunI64x2UnOpTest(TestExecutionTier execution_tier, WasmOpcode opcode,
Int64UnOp expected_op);
void RunI64x2BinOpTest(TestExecutionTier execution_tier, WasmOpcode opcode,
Int64BinOp expected_op);
void RunI64x2ShiftOpTest(TestExecutionTier execution_tier, WasmOpcode opcode,
Int64ShiftOp expected_op);
// Generic expected value functions.
template <typename T, typename = typename std::enable_if<
std::is_floating_point<T>::value>::type>
T Negate(T a) {
return -a;
}
template <typename T>
T Minimum(T a, T b) {
return std::min(a, b);
}
template <typename T>
T Maximum(T a, T b) {
return std::max(a, b);
}
#if V8_OS_AIX
template <typename T>
bool MightReverseSign(T float_op) {
return float_op == static_cast<T>(Negate) ||
float_op == static_cast<T>(std::abs);
}
#endif
// Test some values not included in the float inputs from value_helper. These
// tests are useful for opcodes that are synthesized during code gen, like Min
// and Max on ia32 and x64.
static constexpr uint32_t nan_test_array[] = {
// Bit patterns of quiet NaNs and signaling NaNs, with or without
// additional payload.
0x7FC00000, 0xFFC00000, 0x7FFFFFFF, 0xFFFFFFFF, 0x7F876543, 0xFF876543,
// NaN with top payload bit unset.
0x7FA00000,
// Both Infinities.
0x7F800000, 0xFF800000,
// Some "normal" numbers, 1 and -1.
0x3F800000, 0xBF800000};
#define FOR_FLOAT32_NAN_INPUTS(i) \
for (size_t i = 0; i < arraysize(nan_test_array); ++i)
// Test some values not included in the double inputs from value_helper. These
// tests are useful for opcodes that are synthesized during code gen, like Min
// and Max on ia32 and x64.
static constexpr uint64_t double_nan_test_array[] = {
// quiet NaNs, + and -
0x7FF8000000000001, 0xFFF8000000000001,
// with payload
0x7FF8000000000011, 0xFFF8000000000011,
// signaling NaNs, + and -
0x7FF0000000000001, 0xFFF0000000000001,
// with payload
0x7FF0000000000011, 0xFFF0000000000011,
// Both Infinities.
0x7FF0000000000000, 0xFFF0000000000000,
// Some "normal" numbers, 1 and -1.
0x3FF0000000000000, 0xBFF0000000000000};
#define FOR_FLOAT64_NAN_INPUTS(i) \
for (size_t i = 0; i < arraysize(double_nan_test_array); ++i)
// Returns true if the platform can represent the result.
template <typename T>
bool PlatformCanRepresent(T x) {
#if V8_TARGET_ARCH_ARM
return std::fpclassify(x) != FP_SUBNORMAL;
#else
return true;
#endif
}
bool isnan(uint16_t f);
bool IsCanonical(uint16_t actual);
// Returns true for very small and very large numbers. We skip these test
// values for the approximation instructions, which don't work at the extremes.
bool IsExtreme(float x);
bool IsCanonical(float actual);
void CheckFloatResult(float x, float y, float expected, float actual,
bool exact = true);
void CheckFloat16LaneResult(float x, float y, float z, uint16_t expected,
uint16_t actual, bool exact = true);
void CheckFloat16LaneResult(float x, float y, uint16_t expected,
uint16_t actual, bool exact = true);
bool IsExtreme(double x);
bool IsCanonical(double actual);
void CheckDoubleResult(double x, double y, double expected, double actual,
bool exact = true);
void RunF16x8UnOpTest(TestExecutionTier execution_tier, WasmOpcode opcode,
HalfUnOp expected_op, bool exact = true);
void RunF16x8BinOpTest(TestExecutionTier execution_tier, WasmOpcode opcode,
HalfBinOp expected_op);
void RunF16x8CompareOpTest(TestExecutionTier execution_tier, WasmOpcode opcode,
HalfCompareOp expected_op);
void RunF32x4UnOpTest(TestExecutionTier execution_tier, WasmOpcode opcode,
FloatUnOp expected_op, bool exact = true);
void RunF32x4BinOpTest(TestExecutionTier execution_tier, WasmOpcode opcode,
FloatBinOp expected_op);
void RunF32x4CompareOpTest(TestExecutionTier execution_tier, WasmOpcode opcode,
FloatCompareOp expected_op);
void RunF64x2UnOpTest(TestExecutionTier execution_tier, WasmOpcode opcode,
DoubleUnOp expected_op, bool exact = true);
void RunF64x2BinOpTest(TestExecutionTier execution_tier, WasmOpcode opcode,
DoubleBinOp expected_op);
void RunF64x2CompareOpTest(TestExecutionTier execution_tier, WasmOpcode opcode,
DoubleCompareOp expected_op);
#ifdef V8_ENABLE_WASM_SIMD256_REVEC
void RunI8x32UnOpRevecTest(WasmOpcode opcode, Int8UnOp expected_op,
compiler::IrOpcode::Value revec_opcode);
void RunI16x16UnOpRevecTest(WasmOpcode opcode, Int16UnOp expected_op,
compiler::IrOpcode::Value revec_opcode);
void RunI32x8UnOpRevecTest(WasmOpcode opcode, Int32UnOp expected_op,
compiler::IrOpcode::Value revec_opcode);
void RunF32x8UnOpRevecTest(WasmOpcode opcode, FloatUnOp expected_op,
compiler::IrOpcode::Value revec_opcode);
void RunF64x4UnOpRevecTest(WasmOpcode opcode, DoubleUnOp expected_op,
compiler::IrOpcode::Value revec_opcode);
template <typename T = int8_t, typename OpType = T (*)(T, T)>
void RunI8x32BinOpRevecTest(WasmOpcode opcode, OpType expected_op,
compiler::IrOpcode::Value revec_opcode);
template <typename T = int16_t, typename OpType = T (*)(T, T)>
void RunI16x16BinOpRevecTest(WasmOpcode opcode, OpType expected_op,
compiler::IrOpcode::Value revec_opcode);
template <typename T = int32_t, typename OpType = T (*)(T, T)>
void RunI32x8BinOpRevecTest(WasmOpcode opcode, OpType expected_op,
compiler::IrOpcode::Value revec_opcode);
void RunI64x4BinOpRevecTest(WasmOpcode opcode, Int64BinOp expected_op,
compiler::IrOpcode::Value revec_opcode);
void RunF64x4BinOpRevecTest(WasmOpcode opcode, DoubleBinOp expected_op,
compiler::IrOpcode::Value revec_opcode);
void RunF32x8BinOpRevecTest(WasmOpcode opcode, FloatBinOp expected_op,
compiler::IrOpcode::Value revec_opcode);
void RunI16x16ShiftOpRevecTest(WasmOpcode opcode, Int16ShiftOp expected_op,
compiler::IrOpcode::Value revec_opcode);
void RunI32x8ShiftOpRevecTest(WasmOpcode opcode, Int32ShiftOp expected_op,
compiler::IrOpcode::Value revec_opcode);
void RunI64x4ShiftOpRevecTest(WasmOpcode opcode, Int64ShiftOp expected_op,
compiler::IrOpcode::Value revec_opcode);
template <typename IntType>
void RunI32x8ConvertF32x8RevecTest(WasmOpcode opcode,
ConvertToIntOp expected_op,
compiler::IrOpcode::Value revec_opcode);
template <typename IntType>
void RunF32x8ConvertI32x8RevecTest(WasmOpcode opcode,
compiler::IrOpcode::Value revec_opcode);
template <typename NarrowIntType, typename WideIntType>
void RunIntSignExtensionRevecTest(WasmOpcode opcode_low, WasmOpcode opcode_high,
WasmOpcode splat_op,
compiler::IrOpcode::Value revec_opcode);
template <typename S, typename T>
void RunIntToIntNarrowingRevecTest(WasmOpcode opcode,
compiler::IrOpcode::Value revec_opcode);
#endif // V8_ENABLE_WASM_SIMD256_REVEC
} // namespace wasm
} // namespace internal
} // namespace v8