// 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 #include #include "src/snapshot/code-serializer.h" #include "src/version.h" #include "src/wasm/module-decoder.h" #include "src/wasm/wasm-macro-gen.h" #include "src/wasm/wasm-module-builder.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-objects.h" #include "src/wasm/wasm-opcodes.h" #include "test/cctest/cctest.h" #include "test/common/wasm/test-signatures.h" #include "test/common/wasm/wasm-module-runner.h" using namespace v8::base; using namespace v8::internal; using namespace v8::internal::compiler; using namespace v8::internal::wasm; namespace { void Cleanup(Isolate* isolate = nullptr) { // 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. if (!isolate) { isolate = CcTest::InitIsolateOnce(); } reinterpret_cast(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(), ModuleOrigin::kWasmOrigin); 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(isolate)); testing::CompileAndRunWasmModule(isolate, buffer.begin(), buffer.end(), ModuleOrigin::kWasmOrigin); CHECK(try_catch.HasCaught()); isolate->clear_pending_exception(); } void ExportAsMain(WasmFunctionBuilder* f) { f->ExportAs(CStrVector("main")); } } // namespace TEST(Run_WasmModule_Return114) { { static const int32_t kReturnValue = 114; TestSignatures sigs; v8::internal::AccountingAllocator allocator; Zone zone(&allocator, ZONE_NAME); WasmModuleBuilder* builder = new (&zone) WasmModuleBuilder(&zone); WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v()); ExportAsMain(f); byte code[] = {WASM_I32V_2(kReturnValue)}; f->EmitCode(code, sizeof(code)); TestModule(&zone, builder, kReturnValue); } Cleanup(); } TEST(Run_WasmModule_CallAdd) { { v8::internal::AccountingAllocator allocator; Zone zone(&allocator, ZONE_NAME); TestSignatures sigs; WasmModuleBuilder* builder = new (&zone) WasmModuleBuilder(&zone); WasmFunctionBuilder* f1 = builder->AddFunction(sigs.i_ii()); uint16_t param1 = 0; uint16_t param2 = 1; byte code1[] = { WASM_I32_ADD(WASM_GET_LOCAL(param1), WASM_GET_LOCAL(param2))}; f1->EmitCode(code1, sizeof(code1)); WasmFunctionBuilder* f2 = builder->AddFunction(sigs.i_v()); ExportAsMain(f2); byte code2[] = { WASM_CALL_FUNCTION(f1->func_index(), WASM_I32V_2(77), WASM_I32V_1(22))}; f2->EmitCode(code2, sizeof(code2)); TestModule(&zone, builder, 99); } Cleanup(); } TEST(Run_WasmModule_ReadLoadedDataSegment) { { static const byte kDataSegmentDest0 = 12; v8::internal::AccountingAllocator allocator; Zone zone(&allocator, ZONE_NAME); TestSignatures sigs; WasmModuleBuilder* builder = new (&zone) WasmModuleBuilder(&zone); WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v()); ExportAsMain(f); byte code[] = { WASM_LOAD_MEM(MachineType::Int32(), WASM_I32V_1(kDataSegmentDest0))}; f->EmitCode(code, sizeof(code)); byte 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 = new (&zone) WasmModuleBuilder(&zone); WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v()); uint16_t localIndex = f->AddLocal(kWasmI32); ExportAsMain(f); byte code[] = {WASM_BLOCK_I( WASM_WHILE( WASM_I32_LTS(WASM_GET_LOCAL(localIndex), WASM_I32V_3(kCheckSize)), WASM_IF_ELSE( WASM_LOAD_MEM(MachineType::Int32(), WASM_GET_LOCAL(localIndex)), WASM_BRV(3, WASM_I32V_1(-1)), WASM_INC_LOCAL_BY(localIndex, 4))), WASM_I32V_1(11))}; f->EmitCode(code, sizeof(code)); TestModule(&zone, builder, 11); } Cleanup(); } TEST(Run_WasmModule_CallMain_recursive) { { v8::internal::AccountingAllocator allocator; Zone zone(&allocator, ZONE_NAME); TestSignatures sigs; WasmModuleBuilder* builder = new (&zone) WasmModuleBuilder(&zone); WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v()); uint16_t localIndex = f->AddLocal(kWasmI32); ExportAsMain(f); byte code[] = { WASM_SET_LOCAL(localIndex, WASM_LOAD_MEM(MachineType::Int32(), WASM_ZERO)), WASM_IF_ELSE_I(WASM_I32_LTS(WASM_GET_LOCAL(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))}; f->EmitCode(code, sizeof(code)); TestModule(&zone, builder, 55); } Cleanup(); } TEST(Run_WasmModule_Global) { { v8::internal::AccountingAllocator allocator; Zone zone(&allocator, ZONE_NAME); TestSignatures sigs; WasmModuleBuilder* builder = new (&zone) WasmModuleBuilder(&zone); uint32_t global1 = builder->AddGlobal(kWasmI32, 0); uint32_t global2 = builder->AddGlobal(kWasmI32, 0); WasmFunctionBuilder* f1 = builder->AddFunction(sigs.i_v()); byte code1[] = { WASM_I32_ADD(WASM_GET_GLOBAL(global1), WASM_GET_GLOBAL(global2))}; f1->EmitCode(code1, sizeof(code1)); WasmFunctionBuilder* f2 = builder->AddFunction(sigs.i_v()); ExportAsMain(f2); byte code2[] = {WASM_SET_GLOBAL(global1, WASM_I32V_1(56)), WASM_SET_GLOBAL(global2, WASM_I32V_1(41)), WASM_RETURN1(WASM_CALL_FUNCTION0(f1->func_index()))}; f2->EmitCode(code2, sizeof(code2)); TestModule(&zone, builder, 97); } Cleanup(); } // 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(); } void ClearSerializedData() { serialized_bytes_.first = nullptr; serialized_bytes_.second = 0; } void InvalidateVersion() { uint32_t* buffer = reinterpret_cast( const_cast(serialized_bytes_.first)); buffer[SerializedCodeData::kVersionHashOffset] = Version::Hash() + 1; } void InvalidateWireBytes() { memset(const_cast(wire_bytes_.first), '\0', wire_bytes_.second / 2); } v8::MaybeLocal Deserialize() { ErrorThrower thrower(current_isolate(), ""); v8::MaybeLocal deserialized = v8::WasmCompiledModule::DeserializeOrCompile( current_isolate_v8(), serialized_bytes(), wire_bytes()); return deserialized; } void DeserializeAndRun() { ErrorThrower thrower(current_isolate(), ""); v8::Local deserialized_module; CHECK(Deserialize().ToLocal(&deserialized_module)); Handle module_object = Handle::cast(v8::Utils::OpenHandle(*deserialized_module)); { DisallowHeapAllocation assume_no_gc; Handle compiled_part( WasmCompiledModule::cast(module_object->GetInternalField(0)), current_isolate()); CHECK_EQ(memcmp(compiled_part->module_bytes()->GetCharsAddress(), wire_bytes().first, wire_bytes().second), 0); } Handle instance = WasmModule::Instantiate(current_isolate(), &thrower, module_object, Handle::null(), Handle::null()) .ToHandleChecked(); Handle params[1] = { Handle(Smi::FromInt(41), current_isolate())}; int32_t result = testing::CallWasmFunctionForTesting( current_isolate(), instance, &thrower, kFunctionName, 1, params, ModuleOrigin::kWasmOrigin); CHECK(result == 42); } Isolate* current_isolate() { return reinterpret_cast(current_isolate_v8_); } ~WasmSerializationTest() { // Don't call from here if we move to gtest TearDown(); } private: static const char* kFunctionName; Zone* zone() { return &zone_; } const v8::WasmCompiledModule::CallerOwnedBuffer& wire_bytes() const { return wire_bytes_; } const v8::WasmCompiledModule::CallerOwnedBuffer& serialized_bytes() const { return serialized_bytes_; } v8::Isolate* current_isolate_v8() { return current_isolate_v8_; } void SetUp() { WasmModuleBuilder* builder = new (zone()) WasmModuleBuilder(zone()); TestSignatures sigs; WasmFunctionBuilder* f = builder->AddFunction(sigs.i_i()); byte code[] = {WASM_GET_LOCAL(0), kExprI32Const, 1, kExprI32Add}; f->EmitCode(code, sizeof(code)); f->ExportAs(CStrVector(kFunctionName)); ZoneBuffer buffer(&zone_); builder->WriteTo(buffer); Isolate* serialization_isolate = CcTest::InitIsolateOnce(); ErrorThrower thrower(serialization_isolate, ""); uint8_t* bytes = nullptr; size_t bytes_size = 0; { HandleScope scope(serialization_isolate); testing::SetupIsolateForWasmModule(serialization_isolate); ModuleResult decoding_result = DecodeWasmModule(serialization_isolate, buffer.begin(), buffer.end(), false, kWasmOrigin); CHECK(!decoding_result.failed()); Handle module_wrapper = WasmModuleWrapper::New( serialization_isolate, const_cast(decoding_result.val)); MaybeHandle compiled_module = decoding_result.val->CompileFunctions( serialization_isolate, module_wrapper, &thrower, ModuleWireBytes(buffer.begin(), buffer.end()), Handle