| // 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 "include/libplatform/libplatform.h" |
| #include "src/api/api-inl.h" |
| #include "src/base/vector.h" |
| #include "src/handles/global-handles-inl.h" |
| #include "src/init/v8.h" |
| #include "src/objects/managed.h" |
| #include "src/objects/objects-inl.h" |
| #include "src/wasm/module-compiler.h" |
| #include "src/wasm/module-decoder.h" |
| #include "src/wasm/streaming-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-objects.h" |
| #include "src/wasm/wasm-serialization.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::internal::wasm { |
| |
| class MockPlatform final : public TestPlatform { |
| public: |
| MockPlatform() : task_runner_(std::make_shared<MockTaskRunner>()) {} |
| |
| ~MockPlatform() { |
| 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) 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 PostTask(std::unique_ptr<v8::Task> task) override { |
| base::MutexGuard lock_scope(&tasks_lock_); |
| tasks_.push(std::move(task)); |
| } |
| |
| void PostNonNestableTask(std::unique_ptr<Task> task) override { |
| PostTask(std::move(task)); |
| } |
| |
| void PostDelayedTask(std::unique_ptr<Task> task, |
| double delay_in_seconds) override { |
| base::MutexGuard lock_scope(&tasks_lock_); |
| delayed_tasks_.emplace_back( |
| std::move(task), base::TimeTicks::Now() + |
| base::TimeDelta::FromSecondsD(delay_in_seconds)); |
| } |
| |
| void PostNonNestableDelayedTask(std::unique_ptr<Task> task, |
| double delay_in_seconds) override { |
| PostDelayedTask(std::move(task), delay_in_seconds); |
| } |
| |
| void PostIdleTask(std::unique_ptr<IdleTask> task) override { |
| UNREACHABLE(); |
| } |
| |
| bool IdleTasksEnabled() override { return false; } |
| bool NonNestableTasksEnabled() const override { return true; } |
| bool NonNestableDelayedTasksEnabled() const override { return true; } |
| |
| // The test must call this repeatedly if delayed tasks were posted, until |
| // all such tasks have been executed. |
| void ExecuteTasks() { |
| std::queue<std::unique_ptr<v8::Task>> tasks; |
| while (true) { |
| { |
| base::MutexGuard lock_scope(&tasks_lock_); |
| tasks.swap(tasks_); |
| // Move all delayed tasks which are ready for execution to {tasks_}. |
| base::TimeTicks now = base::TimeTicks::Now(); |
| for (auto it = delayed_tasks_.begin(), end = delayed_tasks_.end(); |
| it != end;) { |
| if (it->second > now) { |
| ++it; |
| continue; |
| } |
| tasks.push(std::move(it->first)); |
| it = delayed_tasks_.erase(it); |
| } |
| } |
| // Stop if there are no tasks to execute. Otherwise execute the tasks, |
| // then check again. |
| 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_; |
| std::list<std::pair<std::unique_ptr<v8::Task>, base::TimeTicks>> |
| delayed_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 IsActive() override { return orig_handle_->IsActive(); } |
| bool IsValid() override { return orig_handle_->IsValid(); } |
| |
| private: |
| std::unique_ptr<JobHandle> orig_handle_; |
| MockPlatform* platform_; |
| }; |
| |
| std::shared_ptr<MockTaskRunner> task_runner_; |
| std::unordered_set<MockJobHandle*> job_handles_; |
| }; |
| |
| namespace { |
| |
| enum class CompilationState { |
| kPending, |
| kFinished, |
| kFailed, |
| }; |
| |
| class TestResolver : public CompilationResultResolver { |
| public: |
| TestResolver(i::Isolate* isolate, CompilationState* state, |
| std::string* error_message, |
| Handle<WasmModuleObject>* module_object) |
| : isolate_(isolate), |
| state_(state), |
| error_message_(error_message), |
| module_object_(module_object) {} |
| |
| void OnCompilationSucceeded(i::Handle<i::WasmModuleObject> module) override { |
| *state_ = CompilationState::kFinished; |
| *module_object_ = isolate_->global_handles()->Create(*module); |
| } |
| |
| void OnCompilationFailed(i::Handle<i::Object> error_reason) override { |
| *state_ = CompilationState::kFailed; |
| Handle<String> str = |
| Object::ToString(isolate_, error_reason).ToHandleChecked(); |
| error_message_->assign(str->ToCString().get()); |
| // Print the error message, for easier debugging on tests that unexpectedly |
| // fail compilation. |
| PrintF("Compilation failed: %s\n", error_message_->c_str()); |
| } |
| |
| private: |
| i::Isolate* isolate_; |
| CompilationState* const state_; |
| std::string* const error_message_; |
| Handle<WasmModuleObject>* const module_object_; |
| }; |
| |
| class StreamTester { |
| public: |
| explicit StreamTester(v8::Isolate* isolate) |
| : zone_(&allocator_, "StreamTester") { |
| Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); |
| v8::Local<v8::Context> context = isolate->GetCurrentContext(); |
| |
| WasmFeatures features = WasmFeatures::FromIsolate(i_isolate); |
| stream_ = GetWasmEngine()->StartStreamingCompilation( |
| i_isolate, features, v8::Utils::OpenHandle(*context), |
| "WebAssembly.compileStreaming()", |
| std::make_shared<TestResolver>(i_isolate, &state_, &error_message_, |
| &module_object_)); |
| } |
| |
| std::shared_ptr<StreamingDecoder> stream() const { return stream_; } |
| |
| // Compiled module object, valid after successful compile. |
| Handle<WasmModuleObject> module_object() const { |
| CHECK(!module_object_.is_null()); |
| return module_object_; |
| } |
| |
| // Compiled native module, valid after successful compile. |
| NativeModule* native_module() const { |
| return module_object()->native_module(); |
| } |
| std::shared_ptr<NativeModule> shared_native_module() const { |
| return module_object()->shared_native_module(); |
| } |
| |
| // Run all compiler tasks, both foreground and background tasks. |
| void RunCompilerTasks() { |
| static_cast<MockPlatform*>(i::V8::GetCurrentPlatform())->ExecuteTasks(); |
| } |
| |
| bool IsPromiseFulfilled() { return state_ == CompilationState::kFinished; } |
| |
| bool IsPromiseRejected() { return state_ == CompilationState::kFailed; } |
| |
| bool IsPromisePending() { return state_ == CompilationState::kPending; } |
| |
| void OnBytesReceived(const uint8_t* start, size_t length) { |
| stream_->OnBytesReceived(base::Vector<const uint8_t>(start, length)); |
| } |
| |
| void FinishStream() { stream_->Finish(); } |
| |
| void SetCompiledModuleBytes(base::Vector<const uint8_t> bytes) { |
| stream_->SetCompiledModuleBytes(bytes); |
| } |
| |
| Zone* zone() { return &zone_; } |
| |
| const std::string& error_message() const { return error_message_; } |
| |
| private: |
| AccountingAllocator allocator_; |
| Zone zone_; |
| CompilationState state_ = CompilationState::kPending; |
| std::string error_message_; |
| Handle<WasmModuleObject> module_object_; |
| std::shared_ptr<StreamingDecoder> stream_; |
| }; |
| } // namespace |
| |
| #define RUN_STREAM(name) \ |
| v8::Isolate* isolate = CcTest::isolate(); \ |
| v8::HandleScope handle_scope(isolate); \ |
| v8::Local<v8::Context> context = v8::Context::New(isolate); \ |
| v8::Context::Scope context_scope(context); \ |
| /* Reduce tiering budget so we do not need to execute too long. */ \ |
| i::FlagScope<int> reduced_tiering_budget(&i::v8_flags.wasm_tiering_budget, \ |
| 10); \ |
| RunStream_##name(&platform, isolate); |
| |
| #define STREAM_TEST(name) \ |
| void RunStream_##name(MockPlatform*, v8::Isolate*); \ |
| TEST_WITH_PLATFORM(Async##name, MockPlatform) { \ |
| if (i::v8_flags.memory_balancer) return; \ |
| RUN_STREAM(name); \ |
| } \ |
| \ |
| TEST_WITH_PLATFORM(SingleThreaded##name, MockPlatform) { \ |
| if (i::v8_flags.memory_balancer) return; \ |
| i::FlagScope<bool> single_threaded_scope(&i::v8_flags.single_threaded, \ |
| true); \ |
| RUN_STREAM(name); \ |
| } \ |
| void RunStream_##name(MockPlatform* platform, v8::Isolate* isolate) |
| |
| constexpr const char* kExportNames[] = {"a", "b", "c"}; |
| |
| // Create a valid module with 3 functions. |
| ZoneBuffer GetValidModuleBytes(Zone* zone) { |
| ZoneBuffer buffer(zone); |
| TestSignatures sigs; |
| WasmModuleBuilder builder(zone); |
| uint8_t i = 0; |
| for (const char* export_name : kExportNames) { |
| WasmFunctionBuilder* f = builder.AddFunction(sigs.i_iii()); |
| uint8_t code[] = {kExprLocalGet, i, kExprEnd}; |
| f->EmitCode(code, arraysize(code)); |
| CHECK_GE(3, ++i); |
| builder.AddExport(base::CStrVector(export_name), f); |
| } |
| builder.WriteTo(&buffer); |
| return buffer; |
| } |
| |
| // Create the same valid module as above and serialize it to test streaming |
| // with compiled module caching. |
| ZoneBuffer GetValidCompiledModuleBytes(v8::Isolate* isolate, Zone* zone, |
| ZoneBuffer wire_bytes) { |
| // Use a tester to compile to a NativeModule. |
| StreamTester tester(isolate); |
| tester.OnBytesReceived(wire_bytes.begin(), wire_bytes.size()); |
| tester.FinishStream(); |
| tester.RunCompilerTasks(); |
| CHECK(tester.IsPromiseFulfilled()); |
| |
| NativeModule* native_module = tester.native_module(); |
| CHECK_NOT_NULL(native_module); |
| |
| auto* i_isolate = reinterpret_cast<i::Isolate*>(isolate); |
| ErrorThrower thrower{i_isolate, "GetValidCompiledModuleBytes"}; |
| Handle<WasmInstanceObject> instance = |
| GetWasmEngine() |
| ->SyncInstantiate(i_isolate, &thrower, tester.module_object(), {}, {}) |
| .ToHandleChecked(); |
| CHECK(!thrower.error()); |
| |
| // Call the exported functions repeatedly until they are all tiered up. |
| std::vector<Handle<WasmExportedFunction>> exported_functions; |
| for (const char* export_name : kExportNames) { |
| exported_functions.push_back( |
| testing::GetExportedFunction(i_isolate, instance, export_name) |
| .ToHandleChecked()); |
| } |
| while (true) { |
| WasmCodeRefScope code_ref_scope; |
| std::vector<WasmCode*> all_code = native_module->SnapshotCodeTable().first; |
| if (std::all_of(all_code.begin(), all_code.end(), [](const WasmCode* code) { |
| return code && code->tier() == ExecutionTier::kTurbofan; |
| })) { |
| break; |
| } |
| for (Handle<WasmExportedFunction> exported_function : exported_functions) { |
| Handle<Object> return_value = |
| Execution::Call(i_isolate, exported_function, |
| ReadOnlyRoots{i_isolate}.undefined_value_handle(), 0, |
| nullptr) |
| .ToHandleChecked(); |
| CHECK(IsSmi(*return_value)); |
| CHECK_EQ(0, Smi::cast(*return_value).value()); |
| } |
| tester.RunCompilerTasks(); |
| } |
| |
| // Serialize the NativeModule. |
| i::wasm::WasmSerializer serializer(native_module); |
| size_t size = serializer.GetSerializedNativeModuleSize(); |
| std::vector<uint8_t> buffer(size); |
| CHECK(serializer.SerializeNativeModule(base::VectorOf(buffer))); |
| ZoneBuffer result(zone, size); |
| result.write(buffer.data(), size); |
| return result; |
| } |
| |
| // Test that all bytes arrive before doing any compilation. FinishStream is |
| // called immediately. |
| STREAM_TEST(TestAllBytesArriveImmediatelyStreamFinishesFirst) { |
| StreamTester tester(isolate); |
| ZoneBuffer buffer = GetValidModuleBytes(tester.zone()); |
| |
| tester.OnBytesReceived(buffer.begin(), buffer.end() - buffer.begin()); |
| tester.FinishStream(); |
| |
| tester.RunCompilerTasks(); |
| |
| CHECK(tester.IsPromiseFulfilled()); |
| } |
| |
| // Test that all bytes arrive before doing any compilation. FinishStream is |
| // called after the compilation is done. |
| STREAM_TEST(TestAllBytesArriveAOTCompilerFinishesFirst) { |
| StreamTester tester(isolate); |
| ZoneBuffer buffer = GetValidModuleBytes(tester.zone()); |
| |
| tester.OnBytesReceived(buffer.begin(), buffer.end() - buffer.begin()); |
| |
| tester.RunCompilerTasks(); |
| tester.FinishStream(); |
| tester.RunCompilerTasks(); |
| |
| CHECK(tester.IsPromiseFulfilled()); |
| } |
| |
| size_t GetFunctionOffset(i::Isolate* isolate, base::Vector<const uint8_t> bytes, |
| size_t index) { |
| ModuleResult result = DecodeWasmModule(WasmFeatures::All(), bytes, false, |
| ModuleOrigin::kWasmOrigin); |
| CHECK(result.ok()); |
| const WasmFunction* func = &result.value()->functions[index]; |
| return func->code.offset(); |
| } |
| |
| // Test that some functions come in the beginning, some come after some |
| // functions already got compiled. |
| STREAM_TEST(TestCutAfterOneFunctionStreamFinishesFirst) { |
| StreamTester tester(isolate); |
| ZoneBuffer buffer = GetValidModuleBytes(tester.zone()); |
| |
| Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); |
| size_t offset = GetFunctionOffset(i_isolate, base::VectorOf(buffer), 1); |
| tester.OnBytesReceived(buffer.begin(), offset); |
| tester.RunCompilerTasks(); |
| CHECK(tester.IsPromisePending()); |
| tester.OnBytesReceived(buffer.begin() + offset, buffer.size() - offset); |
| tester.FinishStream(); |
| tester.RunCompilerTasks(); |
| CHECK(tester.IsPromiseFulfilled()); |
| } |
| |
| // Test that some functions come in the beginning, some come after some |
| // functions already got compiled. Call FinishStream after the compilation is |
| // done. |
| STREAM_TEST(TestCutAfterOneFunctionCompilerFinishesFirst) { |
| StreamTester tester(isolate); |
| ZoneBuffer buffer = GetValidModuleBytes(tester.zone()); |
| |
| Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); |
| size_t offset = GetFunctionOffset(i_isolate, base::VectorOf(buffer), 1); |
| tester.OnBytesReceived(buffer.begin(), offset); |
| tester.RunCompilerTasks(); |
| CHECK(tester.IsPromisePending()); |
| tester.OnBytesReceived(buffer.begin() + offset, buffer.size() - offset); |
| tester.RunCompilerTasks(); |
| tester.FinishStream(); |
| tester.RunCompilerTasks(); |
| CHECK(tester.IsPromiseFulfilled()); |
| } |
| |
| // Create a module with an invalid global section. |
| ZoneBuffer GetModuleWithInvalidSection(Zone* zone) { |
| ZoneBuffer buffer(zone); |
| TestSignatures sigs; |
| WasmModuleBuilder builder(zone); |
| // Add an invalid global to the module. The decoder will fail there. |
| builder.AddGlobal(kWasmVoid, true, WasmInitExpr::GlobalGet(12)); |
| { |
| WasmFunctionBuilder* f = builder.AddFunction(sigs.i_iii()); |
| uint8_t code[] = {kExprLocalGet, 0, kExprEnd}; |
| f->EmitCode(code, arraysize(code)); |
| } |
| { |
| WasmFunctionBuilder* f = builder.AddFunction(sigs.i_iii()); |
| uint8_t code[] = {kExprLocalGet, 1, kExprEnd}; |
| f->EmitCode(code, arraysize(code)); |
| } |
| { |
| WasmFunctionBuilder* f = builder.AddFunction(sigs.i_iii()); |
| uint8_t code[] = {kExprLocalGet, 2, kExprEnd}; |
| f->EmitCode(code, arraysize(code)); |
| } |
| builder.WriteTo(&buffer); |
| return buffer; |
| } |
| |
| // Test an error in a section, found by the ModuleDecoder. |
| STREAM_TEST(TestErrorInSectionStreamFinishesFirst) { |
| StreamTester tester(isolate); |
| ZoneBuffer buffer = GetModuleWithInvalidSection(tester.zone()); |
| |
| tester.OnBytesReceived(buffer.begin(), buffer.end() - buffer.begin()); |
| tester.FinishStream(); |
| |
| tester.RunCompilerTasks(); |
| |
| CHECK(tester.IsPromiseRejected()); |
| } |
| |
| STREAM_TEST(TestErrorInSectionCompilerFinishesFirst) { |
| StreamTester tester(isolate); |
| ZoneBuffer buffer = GetModuleWithInvalidSection(tester.zone()); |
| |
| tester.OnBytesReceived(buffer.begin(), buffer.end() - buffer.begin()); |
| tester.RunCompilerTasks(); |
| tester.FinishStream(); |
| tester.RunCompilerTasks(); |
| |
| CHECK(tester.IsPromiseRejected()); |
| } |
| |
| STREAM_TEST(TestErrorInSectionWithCuts) { |
| StreamTester tester(isolate); |
| ZoneBuffer buffer = GetModuleWithInvalidSection(tester.zone()); |
| |
| const uint8_t* current = buffer.begin(); |
| size_t remaining = buffer.end() - buffer.begin(); |
| while (current < buffer.end()) { |
| size_t size = std::min(remaining, size_t{10}); |
| tester.OnBytesReceived(current, size); |
| tester.RunCompilerTasks(); |
| current += 10; |
| remaining -= size; |
| } |
| tester.FinishStream(); |
| tester.RunCompilerTasks(); |
| |
| CHECK(tester.IsPromiseRejected()); |
| } |
| |
| ZoneBuffer GetModuleWithInvalidSectionSize(Zone* zone) { |
| // We get a valid module and overwrite the size of the first section with an |
| // invalid value. |
| ZoneBuffer buffer = GetValidModuleBytes(zone); |
| // 9 == 4 (wasm magic) + 4 (version) + 1 (section code) |
| uint8_t* section_size_address = const_cast<uint8_t*>(buffer.begin()) + 9; |
| // 0x808080800F is an invalid module size in leb encoding. |
| section_size_address[0] = 0x80; |
| section_size_address[1] = 0x80; |
| section_size_address[2] = 0x80; |
| section_size_address[3] = 0x80; |
| section_size_address[4] = 0x0F; |
| return buffer; |
| } |
| |
| STREAM_TEST(TestErrorInSectionSizeStreamFinishesFirst) { |
| StreamTester tester(isolate); |
| ZoneBuffer buffer = GetModuleWithInvalidSectionSize(tester.zone()); |
| tester.OnBytesReceived(buffer.begin(), buffer.end() - buffer.begin()); |
| tester.FinishStream(); |
| tester.RunCompilerTasks(); |
| |
| CHECK(tester.IsPromiseRejected()); |
| } |
| |
| STREAM_TEST(TestErrorInSectionSizeCompilerFinishesFirst) { |
| StreamTester tester(isolate); |
| ZoneBuffer buffer = GetModuleWithInvalidSectionSize(tester.zone()); |
| tester.OnBytesReceived(buffer.begin(), buffer.end() - buffer.begin()); |
| tester.RunCompilerTasks(); |
| tester.FinishStream(); |
| tester.RunCompilerTasks(); |
| |
| CHECK(tester.IsPromiseRejected()); |
| } |
| |
| STREAM_TEST(TestErrorInSectionSizeWithCuts) { |
| StreamTester tester(isolate); |
| ZoneBuffer buffer = GetModuleWithInvalidSectionSize(tester.zone()); |
| const uint8_t* current = buffer.begin(); |
| size_t remaining = buffer.end() - buffer.begin(); |
| while (current < buffer.end()) { |
| size_t size = std::min(remaining, size_t{10}); |
| tester.OnBytesReceived(current, size); |
| tester.RunCompilerTasks(); |
| current += 10; |
| remaining -= size; |
| } |
| tester.RunCompilerTasks(); |
| tester.FinishStream(); |
| tester.RunCompilerTasks(); |
| |
| CHECK(tester.IsPromiseRejected()); |
| } |
| |
| // Test an error in the code section, found by the ModuleDecoder. The error is a |
| // functions count in the code section which differs from the functions count in |
| // the function section. |
| STREAM_TEST(TestErrorInCodeSectionDetectedByModuleDecoder) { |
| StreamTester tester(isolate); |
| |
| uint8_t code[] = { |
| U32V_1(4), // body size |
| U32V_1(0), // locals count |
| kExprLocalGet, 0, kExprEnd // body |
| }; |
| |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| kTypeSectionCode, // section code |
| U32V_1(1 + SIZEOF_SIG_ENTRY_x_x), // section size |
| U32V_1(1), // type count |
| SIG_ENTRY_x_x(kI32Code, kI32Code), // signature entry |
| kFunctionSectionCode, // section code |
| U32V_1(1 + 3), // section size |
| U32V_1(3), // functions count |
| 0, // signature index |
| 0, // signature index |
| 0, // signature index |
| kCodeSectionCode, // section code |
| U32V_1(1 + arraysize(code) * 2), // section size |
| U32V_1(2), // !!! invalid function count !!! |
| }; |
| |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.FinishStream(); |
| |
| tester.RunCompilerTasks(); |
| |
| CHECK(tester.IsPromiseRejected()); |
| } |
| |
| STREAM_TEST(TestSectionOrderErrorWithEmptyCodeSection) { |
| // Valid: Export, then Code. |
| const uint8_t valid[] = {WASM_MODULE_HEADER, SECTION(Export, ENTRY_COUNT(0)), |
| SECTION(Code, ENTRY_COUNT(0))}; |
| // Invalid: Code, then Export. |
| const uint8_t invalid[] = {WASM_MODULE_HEADER, SECTION(Code, ENTRY_COUNT(0)), |
| SECTION(Export, ENTRY_COUNT(0))}; |
| |
| StreamTester tester_valid(isolate); |
| tester_valid.OnBytesReceived(valid, arraysize(valid)); |
| tester_valid.FinishStream(); |
| tester_valid.RunCompilerTasks(); |
| CHECK(tester_valid.IsPromiseFulfilled()); |
| |
| StreamTester tester_invalid(isolate); |
| tester_invalid.OnBytesReceived(invalid, arraysize(invalid)); |
| tester_invalid.FinishStream(); |
| tester_invalid.RunCompilerTasks(); |
| CHECK(tester_invalid.IsPromiseRejected()); |
| CHECK_NE(std::string::npos, |
| tester_invalid.error_message().find("unexpected section <Export>")); |
| } |
| |
| STREAM_TEST(TestSectionOrderErrorWithNonEmptyCodeSection) { |
| // Valid: Export, then Code. |
| const uint8_t valid[] = { |
| WASM_MODULE_HEADER, SECTION(Type, ENTRY_COUNT(1), SIG_ENTRY_v_v), |
| SECTION(Function, ENTRY_COUNT(1), SIG_INDEX(0)), |
| SECTION(Export, ENTRY_COUNT(0)), |
| SECTION(Code, ENTRY_COUNT(1), ADD_COUNT(WASM_NO_LOCALS, kExprEnd))}; |
| // Invalid: Code, then Export. |
| const uint8_t invalid[] = { |
| WASM_MODULE_HEADER, SECTION(Type, ENTRY_COUNT(1), SIG_ENTRY_v_v), |
| SECTION(Function, ENTRY_COUNT(1), SIG_INDEX(0)), |
| SECTION(Code, ENTRY_COUNT(1), ADD_COUNT(WASM_NO_LOCALS, kExprEnd)), |
| SECTION(Export, ENTRY_COUNT(0))}; |
| |
| StreamTester tester_valid(isolate); |
| tester_valid.OnBytesReceived(valid, arraysize(valid)); |
| tester_valid.FinishStream(); |
| tester_valid.RunCompilerTasks(); |
| CHECK(tester_valid.IsPromiseFulfilled()); |
| |
| StreamTester tester_invalid(isolate); |
| tester_invalid.OnBytesReceived(invalid, arraysize(invalid)); |
| tester_invalid.FinishStream(); |
| tester_invalid.RunCompilerTasks(); |
| CHECK(tester_invalid.IsPromiseRejected()); |
| CHECK_NE(std::string::npos, |
| tester_invalid.error_message().find("unexpected section <Export>")); |
| } |
| |
| // Test an error in the code section, found by the StreamingDecoder. The error |
| // is an invalid function body size, so that there are not enough bytes in the |
| // code section for the function body. |
| STREAM_TEST(TestErrorInCodeSectionDetectedByStreamingDecoder) { |
| StreamTester tester(isolate); |
| |
| uint8_t code[] = { |
| U32V_1(26), // !!! invalid body size !!! |
| U32V_1(0), // locals count |
| kExprLocalGet, 0, kExprEnd // body |
| }; |
| |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| kTypeSectionCode, // section code |
| U32V_1(1 + SIZEOF_SIG_ENTRY_x_x), // section size |
| U32V_1(1), // type count |
| SIG_ENTRY_x_x(kI32Code, kI32Code), // signature entry |
| kFunctionSectionCode, // section code |
| U32V_1(1 + 3), // section size |
| U32V_1(3), // functions count |
| 0, // signature index |
| 0, // signature index |
| 0, // signature index |
| kCodeSectionCode, // section code |
| U32V_1(1 + arraysize(code) * 3), // section size |
| U32V_1(3), // functions count |
| }; |
| |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.FinishStream(); |
| |
| tester.RunCompilerTasks(); |
| |
| CHECK(tester.IsPromiseRejected()); |
| } |
| |
| // Test an error in the code section, found by the Compiler. The error is an |
| // invalid return type. |
| STREAM_TEST(TestErrorInCodeSectionDetectedByCompiler) { |
| StreamTester tester(isolate); |
| |
| uint8_t code[] = { |
| U32V_1(4), // !!! invalid body size !!! |
| U32V_1(0), // locals count |
| kExprLocalGet, 0, kExprEnd // body |
| }; |
| |
| uint8_t invalid_code[] = { |
| U32V_1(4), // !!! invalid body size !!! |
| U32V_1(0), // locals count |
| kExprI64Const, 0, kExprEnd // body |
| }; |
| |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| kTypeSectionCode, // section code |
| U32V_1(1 + SIZEOF_SIG_ENTRY_x_x), // section size |
| U32V_1(1), // type count |
| SIG_ENTRY_x_x(kI32Code, kI32Code), // signature entry |
| kFunctionSectionCode, // section code |
| U32V_1(1 + 3), // section size |
| U32V_1(3), // functions count |
| 0, // signature index |
| 0, // signature index |
| 0, // signature index |
| kCodeSectionCode, // section code |
| U32V_1(1 + arraysize(code) * 2 + |
| arraysize(invalid_code)), // section size |
| U32V_1(3), // functions count |
| }; |
| |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.RunCompilerTasks(); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.RunCompilerTasks(); |
| tester.OnBytesReceived(invalid_code, arraysize(invalid_code)); |
| tester.RunCompilerTasks(); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.RunCompilerTasks(); |
| tester.FinishStream(); |
| tester.RunCompilerTasks(); |
| |
| CHECK(tester.IsPromiseRejected()); |
| } |
| |
| // Test Abort before any bytes arrive. |
| STREAM_TEST(TestAbortImmediately) { |
| StreamTester tester(isolate); |
| tester.stream()->Abort(); |
| tester.RunCompilerTasks(); |
| } |
| |
| // Test Abort within a section. |
| STREAM_TEST(TestAbortWithinSection1) { |
| StreamTester tester(isolate); |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| kTypeSectionCode, // section code |
| U32V_1(1 + SIZEOF_SIG_ENTRY_x_x), // section size |
| U32V_1(1) // type count |
| // Type section is not yet complete. |
| }; |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.RunCompilerTasks(); |
| tester.stream()->Abort(); |
| tester.RunCompilerTasks(); |
| } |
| |
| // Test Abort within a section. |
| STREAM_TEST(TestAbortWithinSection2) { |
| StreamTester tester(isolate); |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| kTypeSectionCode, // section code |
| U32V_1(1 + SIZEOF_SIG_ENTRY_x_x), // section size |
| U32V_1(1), // type count |
| SIG_ENTRY_x_x(kI32Code, kI32Code), // signature entry |
| kFunctionSectionCode, // section code |
| U32V_1(1 + 3), // section size |
| U32V_1(3), // functions count |
| // Function section is not yet complete. |
| }; |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.RunCompilerTasks(); |
| tester.stream()->Abort(); |
| tester.RunCompilerTasks(); |
| } |
| |
| // Test Abort just before the code section. |
| STREAM_TEST(TestAbortAfterSection) { |
| StreamTester tester(isolate); |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| kTypeSectionCode, // section code |
| U32V_1(1 + SIZEOF_SIG_ENTRY_x_x), // section size |
| U32V_1(1), // type count |
| SIG_ENTRY_x_x(kI32Code, kI32Code), // signature entry |
| }; |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.RunCompilerTasks(); |
| tester.stream()->Abort(); |
| tester.RunCompilerTasks(); |
| } |
| |
| // Test Abort after the function count in the code section. The compiler tasks |
| // execute before the abort. |
| STREAM_TEST(TestAbortAfterFunctionsCount1) { |
| StreamTester tester(isolate); |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| kTypeSectionCode, // section code |
| U32V_1(1 + SIZEOF_SIG_ENTRY_x_x), // section size |
| U32V_1(1), // type count |
| SIG_ENTRY_x_x(kI32Code, kI32Code), // signature entry |
| kFunctionSectionCode, // section code |
| U32V_1(1 + 3), // section size |
| U32V_1(3), // functions count |
| 0, // signature index |
| 0, // signature index |
| 0, // signature index |
| kCodeSectionCode, // section code |
| U32V_1(20), // section size |
| U32V_1(3), // functions count |
| }; |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.RunCompilerTasks(); |
| tester.stream()->Abort(); |
| tester.RunCompilerTasks(); |
| } |
| |
| // Test Abort after the function count in the code section. The compiler tasks |
| // do not execute before the abort. |
| STREAM_TEST(TestAbortAfterFunctionsCount2) { |
| StreamTester tester(isolate); |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| kTypeSectionCode, // section code |
| U32V_1(1 + SIZEOF_SIG_ENTRY_x_x), // section size |
| U32V_1(1), // type count |
| SIG_ENTRY_x_x(kI32Code, kI32Code), // signature entry |
| kFunctionSectionCode, // section code |
| U32V_1(1 + 3), // section size |
| U32V_1(3), // functions count |
| 0, // signature index |
| 0, // signature index |
| 0, // signature index |
| kCodeSectionCode, // section code |
| U32V_1(20), // section size |
| U32V_1(3), // functions count |
| }; |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.stream()->Abort(); |
| tester.RunCompilerTasks(); |
| } |
| |
| // Test Abort after some functions got compiled. The compiler tasks execute |
| // before the abort. |
| STREAM_TEST(TestAbortAfterFunctionGotCompiled1) { |
| StreamTester tester(isolate); |
| |
| uint8_t code[] = { |
| U32V_1(4), // !!! invalid body size !!! |
| U32V_1(0), // locals count |
| kExprLocalGet, 0, kExprEnd // body |
| }; |
| |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| kTypeSectionCode, // section code |
| U32V_1(1 + SIZEOF_SIG_ENTRY_x_x), // section size |
| U32V_1(1), // type count |
| SIG_ENTRY_x_x(kI32Code, kI32Code), // signature entry |
| kFunctionSectionCode, // section code |
| U32V_1(1 + 3), // section size |
| U32V_1(3), // functions count |
| 0, // signature index |
| 0, // signature index |
| 0, // signature index |
| kCodeSectionCode, // section code |
| U32V_1(20), // section size |
| U32V_1(3), // functions count |
| }; |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.RunCompilerTasks(); |
| tester.stream()->Abort(); |
| tester.RunCompilerTasks(); |
| } |
| |
| // Test Abort after some functions got compiled. The compiler tasks execute |
| // before the abort. |
| STREAM_TEST(TestAbortAfterFunctionGotCompiled2) { |
| StreamTester tester(isolate); |
| |
| uint8_t code[] = { |
| U32V_1(4), // !!! invalid body size !!! |
| U32V_1(0), // locals count |
| kExprLocalGet, 0, kExprEnd // body |
| }; |
| |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| kTypeSectionCode, // section code |
| U32V_1(1 + SIZEOF_SIG_ENTRY_x_x), // section size |
| U32V_1(1), // type count |
| SIG_ENTRY_x_x(kI32Code, kI32Code), // signature entry |
| kFunctionSectionCode, // section code |
| U32V_1(1 + 3), // section size |
| U32V_1(3), // functions count |
| 0, // signature index |
| 0, // signature index |
| 0, // signature index |
| kCodeSectionCode, // section code |
| U32V_1(20), // section size |
| U32V_1(3), // functions count |
| }; |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.stream()->Abort(); |
| tester.RunCompilerTasks(); |
| } |
| |
| // Test Abort after all functions got compiled. |
| STREAM_TEST(TestAbortAfterCodeSection1) { |
| StreamTester tester(isolate); |
| |
| uint8_t code[] = { |
| U32V_1(4), // body size |
| U32V_1(0), // locals count |
| kExprLocalGet, 0, kExprEnd // body |
| }; |
| |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| kTypeSectionCode, // section code |
| U32V_1(1 + SIZEOF_SIG_ENTRY_x_x), // section size |
| U32V_1(1), // type count |
| SIG_ENTRY_x_x(kI32Code, kI32Code), // signature entry |
| kFunctionSectionCode, // section code |
| U32V_1(1 + 3), // section size |
| U32V_1(3), // functions count |
| 0, // signature index |
| 0, // signature index |
| 0, // signature index |
| kCodeSectionCode, // section code |
| U32V_1(1 + arraysize(code) * 3), // section size |
| U32V_1(3), // functions count |
| }; |
| |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.RunCompilerTasks(); |
| tester.stream()->Abort(); |
| tester.RunCompilerTasks(); |
| } |
| |
| // Test Abort after all functions got compiled. |
| STREAM_TEST(TestAbortAfterCodeSection2) { |
| StreamTester tester(isolate); |
| |
| uint8_t code[] = { |
| U32V_1(4), // body size |
| U32V_1(0), // locals count |
| kExprLocalGet, 0, kExprEnd // body |
| }; |
| |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| kTypeSectionCode, // section code |
| U32V_1(1 + SIZEOF_SIG_ENTRY_x_x), // section size |
| U32V_1(1), // type count |
| SIG_ENTRY_x_x(kI32Code, kI32Code), // signature entry |
| kFunctionSectionCode, // section code |
| U32V_1(1 + 3), // section size |
| U32V_1(3), // functions count |
| 0, // signature index |
| 0, // signature index |
| 0, // signature index |
| kCodeSectionCode, // section code |
| U32V_1(1 + arraysize(code) * 3), // section size |
| U32V_1(3), // functions count |
| }; |
| |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.stream()->Abort(); |
| tester.RunCompilerTasks(); |
| } |
| |
| STREAM_TEST(TestAbortAfterCompilationError1) { |
| StreamTester tester(isolate); |
| |
| uint8_t code[] = { |
| U32V_1(4), // !!! invalid body size !!! |
| U32V_1(0), // locals count |
| kExprLocalGet, 0, kExprEnd // body |
| }; |
| |
| uint8_t invalid_code[] = { |
| U32V_1(4), // !!! invalid body size !!! |
| U32V_1(0), // locals count |
| kExprI64Const, 0, kExprEnd // body |
| }; |
| |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| kTypeSectionCode, // section code |
| U32V_1(1 + SIZEOF_SIG_ENTRY_x_x), // section size |
| U32V_1(1), // type count |
| SIG_ENTRY_x_x(kI32Code, kI32Code), // signature entry |
| kFunctionSectionCode, // section code |
| U32V_1(1 + 3), // section size |
| U32V_1(3), // functions count |
| 0, // signature index |
| 0, // signature index |
| 0, // signature index |
| kCodeSectionCode, // section code |
| U32V_1(1 + arraysize(code) * 2 + |
| arraysize(invalid_code)), // section size |
| U32V_1(3), // functions count |
| }; |
| |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.OnBytesReceived(invalid_code, arraysize(invalid_code)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.RunCompilerTasks(); |
| tester.stream()->Abort(); |
| tester.RunCompilerTasks(); |
| } |
| |
| STREAM_TEST(TestAbortAfterCompilationError2) { |
| StreamTester tester(isolate); |
| |
| uint8_t code[] = { |
| U32V_1(4), // !!! invalid body size !!! |
| U32V_1(0), // locals count |
| kExprLocalGet, 0, kExprEnd // body |
| }; |
| |
| uint8_t invalid_code[] = { |
| U32V_1(4), // !!! invalid body size !!! |
| U32V_1(0), // locals count |
| kExprI64Const, 0, kExprEnd // body |
| }; |
| |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| kTypeSectionCode, // section code |
| U32V_1(1 + SIZEOF_SIG_ENTRY_x_x), // section size |
| U32V_1(1), // type count |
| SIG_ENTRY_x_x(kI32Code, kI32Code), // signature entry |
| kFunctionSectionCode, // section code |
| U32V_1(1 + 3), // section size |
| U32V_1(3), // functions count |
| 0, // signature index |
| 0, // signature index |
| 0, // signature index |
| kCodeSectionCode, // section code |
| U32V_1(1 + arraysize(code) * 2 + |
| arraysize(invalid_code)), // section size |
| U32V_1(3), // functions count |
| }; |
| |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.OnBytesReceived(invalid_code, arraysize(invalid_code)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.stream()->Abort(); |
| tester.RunCompilerTasks(); |
| } |
| |
| STREAM_TEST(TestOnlyModuleHeader) { |
| StreamTester tester(isolate); |
| |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| }; |
| |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.FinishStream(); |
| tester.RunCompilerTasks(); |
| |
| CHECK(tester.IsPromiseFulfilled()); |
| } |
| |
| STREAM_TEST(TestModuleWithZeroFunctions) { |
| StreamTester tester(isolate); |
| |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| kTypeSectionCode, // section code |
| U32V_1(1), // section size |
| U32V_1(0), // type count |
| kFunctionSectionCode, // section code |
| U32V_1(1), // section size |
| U32V_1(0), // functions count |
| kCodeSectionCode, // section code |
| U32V_1(1), // section size |
| U32V_1(0), // functions count |
| }; |
| |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.FinishStream(); |
| tester.RunCompilerTasks(); |
| CHECK(tester.IsPromiseFulfilled()); |
| } |
| |
| STREAM_TEST(TestModuleWithMultipleFunctions) { |
| StreamTester tester(isolate); |
| |
| uint8_t code[] = { |
| U32V_1(4), // body size |
| U32V_1(0), // locals count |
| kExprLocalGet, 0, kExprEnd // body |
| }; |
| |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| kTypeSectionCode, // section code |
| U32V_1(1 + SIZEOF_SIG_ENTRY_x_x), // section size |
| U32V_1(1), // type count |
| SIG_ENTRY_x_x(kI32Code, kI32Code), // signature entry |
| kFunctionSectionCode, // section code |
| U32V_1(1 + 3), // section size |
| U32V_1(3), // functions count |
| 0, // signature index |
| 0, // signature index |
| 0, // signature index |
| kCodeSectionCode, // section code |
| U32V_1(1 + arraysize(code) * 3), // section size |
| U32V_1(3), // functions count |
| }; |
| |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.RunCompilerTasks(); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.FinishStream(); |
| tester.RunCompilerTasks(); |
| CHECK(tester.IsPromiseFulfilled()); |
| } |
| |
| STREAM_TEST(TestModuleWithDataSection) { |
| StreamTester tester(isolate); |
| |
| uint8_t code[] = { |
| U32V_1(4), // body size |
| U32V_1(0), // locals count |
| kExprLocalGet, 0, kExprEnd // body |
| }; |
| |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| kTypeSectionCode, // section code |
| U32V_1(1 + SIZEOF_SIG_ENTRY_x_x), // section size |
| U32V_1(1), // type count |
| SIG_ENTRY_x_x(kI32Code, kI32Code), // signature entry |
| kFunctionSectionCode, // section code |
| U32V_1(1 + 3), // section size |
| U32V_1(3), // functions count |
| 0, // signature index |
| 0, // signature index |
| 0, // signature index |
| kCodeSectionCode, // section code |
| U32V_1(1 + arraysize(code) * 3), // section size |
| U32V_1(3), // functions count |
| }; |
| |
| const uint8_t data_section[] = { |
| kDataSectionCode, // section code |
| U32V_1(1), // section size |
| U32V_1(0), // data segment count |
| }; |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.RunCompilerTasks(); |
| tester.OnBytesReceived(data_section, arraysize(data_section)); |
| tester.RunCompilerTasks(); |
| tester.FinishStream(); |
| tester.RunCompilerTasks(); |
| CHECK(tester.IsPromiseFulfilled()); |
| } |
| // Test that all bytes arrive before doing any compilation. FinishStream is |
| // called immediately. |
| STREAM_TEST(TestModuleWithImportedFunction) { |
| StreamTester tester(isolate); |
| ZoneBuffer buffer(tester.zone()); |
| TestSignatures sigs; |
| WasmModuleBuilder builder(tester.zone()); |
| builder.AddImport(base::ArrayVector("Test"), sigs.i_iii()); |
| { |
| WasmFunctionBuilder* f = builder.AddFunction(sigs.i_iii()); |
| uint8_t code[] = {kExprLocalGet, 0, kExprEnd}; |
| f->EmitCode(code, arraysize(code)); |
| } |
| builder.WriteTo(&buffer); |
| |
| tester.OnBytesReceived(buffer.begin(), buffer.end() - buffer.begin()); |
| tester.FinishStream(); |
| |
| tester.RunCompilerTasks(); |
| |
| CHECK(tester.IsPromiseFulfilled()); |
| } |
| |
| STREAM_TEST(TestIncrementalCaching) { |
| FLAG_VALUE_SCOPE(wasm_tier_up, false); |
| constexpr int threshold = 10; // 10 bytes |
| FlagScope<int> caching_threshold(&v8_flags.wasm_caching_threshold, threshold); |
| StreamTester tester(isolate); |
| int call_cache_counter = 0; |
| tester.stream()->SetMoreFunctionsCanBeSerializedCallback( |
| [&call_cache_counter]( |
| const std::shared_ptr<i::wasm::NativeModule>& native_module) { |
| call_cache_counter++; |
| }); |
| |
| ZoneBuffer buffer(tester.zone()); |
| TestSignatures sigs; |
| WasmModuleBuilder builder(tester.zone()); |
| builder.SetMinMemorySize(1); |
| |
| base::Vector<const char> function_names[] = { |
| base::CStrVector("f0"), base::CStrVector("f1"), base::CStrVector("f2")}; |
| for (int i = 0; i < 3; ++i) { |
| WasmFunctionBuilder* f = builder.AddFunction(sigs.v_v()); |
| |
| constexpr int64_t val = 0x123456789abc; |
| constexpr int index = 0x1234; |
| uint8_t store_mem[] = { |
| WASM_STORE_MEM(MachineType::Int64(), WASM_I32V(index), WASM_I64V(val))}; |
| constexpr uint32_t kStoreLength = 20; |
| CHECK_EQ(kStoreLength, arraysize(store_mem)); |
| |
| // Produce a store {threshold} many times to reach the caching threshold. |
| constexpr uint32_t kCodeLength = kStoreLength * threshold + 1; |
| uint8_t code[kCodeLength]; |
| for (int j = 0; j < threshold; ++j) { |
| memcpy(code + (j * kStoreLength), store_mem, kStoreLength); |
| } |
| code[kCodeLength - 1] = WasmOpcode::kExprEnd; |
| f->EmitCode(code, kCodeLength); |
| builder.AddExport(function_names[i], f); |
| } |
| builder.WriteTo(&buffer); |
| tester.OnBytesReceived(buffer.begin(), buffer.end() - buffer.begin()); |
| tester.FinishStream(); |
| tester.RunCompilerTasks(); |
| CHECK(tester.IsPromiseFulfilled()); |
| tester.native_module(); |
| constexpr base::Vector<const char> kNoSourceUrl{"", 0}; |
| Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); |
| Handle<Script> script = GetWasmEngine()->GetOrCreateScript( |
| i_isolate, tester.shared_native_module(), kNoSourceUrl); |
| Handle<WasmModuleObject> module_object = |
| WasmModuleObject::New(i_isolate, tester.shared_native_module(), script); |
| ErrorThrower thrower(i_isolate, "Instantiation"); |
| // We instantiated before, so the second instantiation must also succeed: |
| Handle<WasmInstanceObject> instance = |
| GetWasmEngine() |
| ->SyncInstantiate(i_isolate, &thrower, module_object, {}, {}) |
| .ToHandleChecked(); |
| CHECK(!thrower.error()); |
| |
| WasmCodeRefScope code_scope; |
| NativeModule* module = tester.native_module(); |
| CHECK(module->GetCode(0) == nullptr || module->GetCode(0)->is_liftoff()); |
| CHECK(module->GetCode(1) == nullptr || module->GetCode(1)->is_liftoff()); |
| CHECK(module->GetCode(2) == nullptr || module->GetCode(2)->is_liftoff()); |
| // No TurboFan compilation happened yet, and therefore no call to the cache. |
| CHECK_EQ(0, call_cache_counter); |
| i::wasm::TriggerTierUp(instance->trusted_data(i_isolate), 0); |
| tester.RunCompilerTasks(); |
| CHECK(!module->GetCode(0)->is_liftoff()); |
| CHECK(module->GetCode(1) == nullptr || module->GetCode(1)->is_liftoff()); |
| CHECK(module->GetCode(2) == nullptr || module->GetCode(2)->is_liftoff()); |
| CHECK_EQ(1, call_cache_counter); |
| size_t serialized_size; |
| { |
| i::wasm::WasmSerializer serializer(tester.native_module()); |
| serialized_size = serializer.GetSerializedNativeModuleSize(); |
| } |
| i::wasm::TriggerTierUp(instance->trusted_data(i_isolate), 1); |
| tester.RunCompilerTasks(); |
| CHECK(!module->GetCode(0)->is_liftoff()); |
| CHECK(!module->GetCode(1)->is_liftoff()); |
| CHECK(module->GetCode(2) == nullptr || module->GetCode(2)->is_liftoff()); |
| CHECK_EQ(2, call_cache_counter); |
| { |
| i::wasm::WasmSerializer serializer(tester.native_module()); |
| CHECK_LT(serialized_size, serializer.GetSerializedNativeModuleSize()); |
| } |
| } |
| |
| STREAM_TEST(TestModuleWithErrorAfterDataSection) { |
| StreamTester tester(isolate); |
| |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| kTypeSectionCode, // section code |
| U32V_1(1 + SIZEOF_SIG_ENTRY_x_x), // section size |
| U32V_1(1), // type count |
| SIG_ENTRY_x_x(kI32Code, kI32Code), // signature entry |
| kFunctionSectionCode, // section code |
| U32V_1(1 + 1), // section size |
| U32V_1(1), // functions count |
| 0, // signature index |
| kCodeSectionCode, // section code |
| U32V_1(6), // section size |
| U32V_1(1), // functions count |
| U32V_1(4), // body size |
| U32V_1(0), // locals count |
| kExprLocalGet, // some code |
| 0, // some code |
| kExprEnd, // some code |
| kDataSectionCode, // section code |
| U32V_1(1), // section size |
| U32V_1(0), // data segment count |
| kUnknownSectionCode, // section code |
| U32V_1(1), // invalid section size |
| }; |
| |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.FinishStream(); |
| tester.RunCompilerTasks(); |
| CHECK(tester.IsPromiseRejected()); |
| } |
| |
| // Test that cached bytes work. |
| STREAM_TEST(TestDeserializationBypassesCompilation) { |
| StreamTester tester(isolate); |
| ZoneBuffer wire_bytes = GetValidModuleBytes(tester.zone()); |
| ZoneBuffer module_bytes = |
| GetValidCompiledModuleBytes(isolate, tester.zone(), wire_bytes); |
| tester.SetCompiledModuleBytes(base::VectorOf(module_bytes)); |
| tester.OnBytesReceived(wire_bytes.begin(), wire_bytes.size()); |
| tester.FinishStream(); |
| |
| tester.RunCompilerTasks(); |
| |
| CHECK(tester.IsPromiseFulfilled()); |
| } |
| |
| // Test that bad cached bytes don't cause compilation of wire bytes to fail. |
| STREAM_TEST(TestDeserializationFails) { |
| StreamTester tester(isolate); |
| ZoneBuffer wire_bytes = GetValidModuleBytes(tester.zone()); |
| ZoneBuffer module_bytes = |
| GetValidCompiledModuleBytes(isolate, tester.zone(), wire_bytes); |
| // corrupt header |
| uint8_t first_byte = *module_bytes.begin(); |
| module_bytes.patch_u8(0, first_byte + 1); |
| tester.SetCompiledModuleBytes(base::VectorOf(module_bytes)); |
| tester.OnBytesReceived(wire_bytes.begin(), wire_bytes.size()); |
| tester.FinishStream(); |
| |
| tester.RunCompilerTasks(); |
| |
| CHECK(tester.IsPromiseFulfilled()); |
| } |
| |
| // Test that a non-empty function section with a missing code section fails. |
| STREAM_TEST(TestFunctionSectionWithoutCodeSection) { |
| StreamTester tester(isolate); |
| |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| kTypeSectionCode, // section code |
| U32V_1(1 + SIZEOF_SIG_ENTRY_x_x), // section size |
| U32V_1(1), // type count |
| SIG_ENTRY_x_x(kI32Code, kI32Code), // signature entry |
| kFunctionSectionCode, // section code |
| U32V_1(1 + 3), // section size |
| U32V_1(3), // functions count |
| 0, // signature index |
| 0, // signature index |
| 0, // signature index |
| }; |
| |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.FinishStream(); |
| |
| tester.RunCompilerTasks(); |
| |
| CHECK(tester.IsPromiseRejected()); |
| } |
| |
| STREAM_TEST(TestMoreFunctionsCanBeSerializedCallback) { |
| // The "more functions can be serialized" callback will only be triggered with |
| // dynamic tiering, so skip this test if dynamic tiering is disabled. |
| if (!v8_flags.wasm_dynamic_tiering) return; |
| |
| // Reduce the caching threshold to 10 bytes so that our three small functions |
| // trigger caching. |
| FlagScope<int> caching_threshold(&v8_flags.wasm_caching_threshold, 10); |
| StreamTester tester(isolate); |
| bool callback_called = false; |
| tester.stream()->SetMoreFunctionsCanBeSerializedCallback( |
| [&callback_called](const std::shared_ptr<NativeModule> module) { |
| callback_called = true; |
| }); |
| |
| uint8_t code[] = { |
| ADD_COUNT(U32V_1(0), // locals count |
| kExprLocalGet, 0, kExprEnd) // body |
| }; |
| |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| SECTION(Type, |
| ENTRY_COUNT(1), // type count |
| SIG_ENTRY_x_x(kI32Code, kI32Code)), // signature entry |
| SECTION(Function, ENTRY_COUNT(3), SIG_INDEX(0), SIG_INDEX(0), |
| SIG_INDEX(0)), |
| SECTION(Export, ENTRY_COUNT(3), // 3 exports |
| ADD_COUNT('a'), kExternalFunction, FUNC_INDEX(0), // "a" (0) |
| ADD_COUNT('b'), kExternalFunction, FUNC_INDEX(1), // "b" (1) |
| ADD_COUNT('c'), kExternalFunction, FUNC_INDEX(2)), // "c" (2) |
| kCodeSectionCode, // section code |
| U32V_1(1 + arraysize(code) * 3), // section size |
| U32V_1(3), // functions count |
| }; |
| |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| |
| tester.FinishStream(); |
| tester.RunCompilerTasks(); |
| CHECK(tester.IsPromiseFulfilled()); |
| |
| // Continue executing functions (eventually triggering tier-up) until the |
| // callback is called at least once. |
| auto* i_isolate = CcTest::i_isolate(); |
| ErrorThrower thrower{i_isolate, "TestMoreFunctionsCanBeSerializedCallback"}; |
| Handle<WasmInstanceObject> instance = |
| GetWasmEngine() |
| ->SyncInstantiate(i_isolate, &thrower, tester.module_object(), {}, {}) |
| .ToHandleChecked(); |
| CHECK(!thrower.error()); |
| |
| Handle<WasmExportedFunction> exported_functions[]{ |
| testing::GetExportedFunction(i_isolate, instance, "a").ToHandleChecked(), |
| testing::GetExportedFunction(i_isolate, instance, "b").ToHandleChecked(), |
| testing::GetExportedFunction(i_isolate, instance, "c").ToHandleChecked()}; |
| |
| // If Liftoff is enabled, then the callback should only be called after |
| // tiering up. |
| CHECK_IMPLIES(v8_flags.liftoff, !callback_called); |
| while (!callback_called) { |
| for (Handle<WasmExportedFunction> exported_function : exported_functions) { |
| Execution::Call(i_isolate, exported_function, |
| ReadOnlyRoots{i_isolate}.undefined_value_handle(), 0, |
| nullptr) |
| .Check(); |
| } |
| tester.RunCompilerTasks(); |
| } |
| } |
| |
| STREAM_TEST(TestMoreFunctionsCanBeSerializedCallbackWithTimeout) { |
| // The "more functions can be serialized" callback will only be triggered with |
| // dynamic tiering, so skip this test if dynamic tiering is disabled. |
| if (!v8_flags.wasm_dynamic_tiering) return; |
| |
| // Reduce the caching threshold to 10 bytes so that our three small functions |
| // trigger caching. |
| FlagScope<int> caching_threshold(&v8_flags.wasm_caching_threshold, 10); |
| // Set the caching timeout to 10ms. |
| constexpr int kCachingTimeoutMs = 10; |
| FlagScope<int> caching_timeout(&v8_flags.wasm_caching_timeout_ms, |
| kCachingTimeoutMs); |
| // Timeouts used in the test below. |
| // 1) A very generous timeout during which we expect the caching callback to |
| // be called. Some bots are really slow here, especially when executing other |
| // tests in parallel, so choose a really large timeout. As we do not expect to |
| // run into this timeout, this does not increase test execution time. |
| constexpr int caching_expected_timeout_ms = 10'000; |
| // 2) A smaller timeout during which we *do not* expect another caching event. |
| // We expect to run into this timeout, so do not choose it too long. Also, |
| // running into this timeout because it was chosen too small will only make |
| // the test pass (flakily), so it is not too critical. |
| constexpr int no_caching_expected_timeout_ms = 2 * kCachingTimeoutMs; |
| |
| // Use a semaphore to wait for the caching event on the main thread. |
| base::Semaphore caching_was_triggered{0}; |
| StreamTester tester(isolate); |
| base::TimeTicks last_time_callback_was_called; |
| tester.stream()->SetMoreFunctionsCanBeSerializedCallback( |
| [&](const std::shared_ptr<NativeModule> module) { |
| base::TimeTicks now = base::TimeTicks::Now(); |
| int64_t ms_since_last_time = |
| (now - last_time_callback_was_called).InMilliseconds(); |
| // The timeout should have been respected. |
| CHECK_LE(kCachingTimeoutMs, ms_since_last_time); |
| last_time_callback_was_called = now; |
| caching_was_triggered.Signal(); |
| }); |
| |
| // This is used when waiting for the semaphore to be signalled. We need to |
| // continue running compiler tasks while waiting. |
| auto WaitForCaching = [&caching_was_triggered, &tester](int ms) { |
| constexpr base::TimeDelta oneMs = base::TimeDelta::FromMilliseconds(1); |
| for (int waited_ms = 0; waited_ms < ms; ++waited_ms) { |
| if (caching_was_triggered.WaitFor(oneMs)) return true; |
| tester.RunCompilerTasks(); |
| } |
| return false; |
| }; |
| |
| uint8_t code[] = { |
| ADD_COUNT(U32V_1(0), // locals count |
| kExprLocalGet, 0, kExprEnd) // body |
| }; |
| |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| SECTION(Type, |
| ENTRY_COUNT(1), // type count |
| SIG_ENTRY_x_x(kI32Code, kI32Code)), // signature entry |
| SECTION(Function, ENTRY_COUNT(3), SIG_INDEX(0), SIG_INDEX(0), |
| SIG_INDEX(0)), |
| SECTION(Export, ENTRY_COUNT(3), // 3 exports |
| ADD_COUNT('a'), kExternalFunction, FUNC_INDEX(0), // "a" (0) |
| ADD_COUNT('b'), kExternalFunction, FUNC_INDEX(1), // "b" (1) |
| ADD_COUNT('c'), kExternalFunction, FUNC_INDEX(2)), // "c" (2) |
| kCodeSectionCode, // section code |
| U32V_1(1 + arraysize(code) * 3), // section size |
| U32V_1(3), // functions count |
| }; |
| |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| |
| tester.FinishStream(); |
| tester.RunCompilerTasks(); |
| CHECK(tester.IsPromiseFulfilled()); |
| |
| // Create an instance. |
| auto* i_isolate = CcTest::i_isolate(); |
| ErrorThrower thrower{i_isolate, "TestMoreFunctionsCanBeSerializedCallback"}; |
| Handle<WasmInstanceObject> instance = |
| GetWasmEngine() |
| ->SyncInstantiate(i_isolate, &thrower, tester.module_object(), {}, {}) |
| .ToHandleChecked(); |
| CHECK(!thrower.error()); |
| |
| // Execute the first function 100 times (which triggers tier-up and hence |
| // caching). |
| Handle<WasmExportedFunction> func_a = |
| testing::GetExportedFunction(i_isolate, instance, "a").ToHandleChecked(); |
| Handle<Object> receiver = ReadOnlyRoots{i_isolate}.undefined_value_handle(); |
| for (int i = 0; i < 100; ++i) { |
| Execution::Call(i_isolate, func_a, receiver, 0, nullptr).Check(); |
| } |
| |
| // Ensure that background compilation is being executed. |
| tester.RunCompilerTasks(); |
| |
| // The caching callback should be called within the next second (be generous). |
| CHECK(WaitForCaching(caching_expected_timeout_ms)); |
| |
| // There should be no other caching happening within the next 20ms. |
| CHECK(!WaitForCaching(no_caching_expected_timeout_ms)); |
| |
| // Now execute the other two functions 100 times and validate that this |
| // triggers another event (but not two). |
| Handle<WasmExportedFunction> func_b_and_c[]{ |
| testing::GetExportedFunction(i_isolate, instance, "b").ToHandleChecked(), |
| testing::GetExportedFunction(i_isolate, instance, "c").ToHandleChecked()}; |
| for (int i = 0; i < 100; ++i) { |
| for (auto func : func_b_and_c) { |
| Execution::Call(i_isolate, func, receiver, 0, nullptr).Check(); |
| } |
| } |
| |
| // Ensure that background compilation is being executed. |
| tester.RunCompilerTasks(); |
| |
| // The caching callback should be called within the next second (be generous). |
| CHECK(WaitForCaching(caching_expected_timeout_ms)); |
| |
| // There should be no other caching happening within the next 20ms. |
| CHECK(!WaitForCaching(no_caching_expected_timeout_ms)); |
| } |
| |
| STREAM_TEST(TestHardCachingThreshold) { |
| // The "more functions can be serialized" callback will only be triggered with |
| // dynamic tiering, so skip this test if dynamic tiering is disabled. |
| if (!v8_flags.wasm_dynamic_tiering) return; |
| |
| // Reduce the caching threshold to 1 byte and set the hard threshold to 10 |
| // bytes so that one small function hits both thresholds. |
| FlagScope<int> caching_threshold(&v8_flags.wasm_caching_threshold, 1); |
| FlagScope<int> hard_caching_threshold(&v8_flags.wasm_caching_hard_threshold, |
| 10); |
| // Set a caching timeout such that the hard threshold has any meaning. This |
| // timeout should never be reached. |
| constexpr int kCachingTimeoutMs = 1000; |
| FlagScope<int> caching_timeout(&v8_flags.wasm_caching_timeout_ms, |
| kCachingTimeoutMs); |
| |
| // Use a semaphore to wait for the caching event on the main thread. |
| std::atomic<bool> caching_was_triggered{false}; |
| StreamTester tester(isolate); |
| tester.stream()->SetMoreFunctionsCanBeSerializedCallback( |
| [&](const std::shared_ptr<NativeModule>& module) { |
| caching_was_triggered = true; |
| }); |
| |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| SECTION(Type, |
| ENTRY_COUNT(1), // type count |
| SIG_ENTRY_x_x(kI32Code, kI32Code)), // signature entry |
| SECTION(Function, ENTRY_COUNT(1), SIG_INDEX(0)), |
| SECTION(Export, ENTRY_COUNT(1), // 1 export |
| ADD_COUNT('a'), kExternalFunction, FUNC_INDEX(0)), // "a" (0) |
| SECTION(Code, |
| U32V_1(1), // functions count |
| ADD_COUNT(U32V_1(0), // locals count |
| kExprLocalGet, 0, kExprEnd)) // body |
| }; |
| |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.FinishStream(); |
| tester.RunCompilerTasks(); |
| CHECK(tester.IsPromiseFulfilled()); |
| |
| CHECK(!caching_was_triggered); |
| |
| // Create an instance. |
| auto* i_isolate = CcTest::i_isolate(); |
| ErrorThrower thrower{i_isolate, "TestMoreFunctionsCanBeSerializedCallback"}; |
| Handle<WasmInstanceObject> instance = |
| GetWasmEngine() |
| ->SyncInstantiate(i_isolate, &thrower, tester.module_object(), {}, {}) |
| .ToHandleChecked(); |
| CHECK(!thrower.error()); |
| CHECK(!caching_was_triggered); |
| |
| // Execute the function 100 times (which triggers tier-up and hence caching). |
| Handle<WasmExportedFunction> func_a = |
| testing::GetExportedFunction(i_isolate, instance, "a").ToHandleChecked(); |
| Handle<Object> receiver = ReadOnlyRoots{i_isolate}.undefined_value_handle(); |
| for (int i = 0; i < 100; ++i) { |
| Execution::Call(i_isolate, func_a, receiver, 0, nullptr).Check(); |
| } |
| |
| // Ensure that background compilation is being executed. |
| tester.RunCompilerTasks(); |
| |
| // Caching should have been triggered now. |
| CHECK(caching_was_triggered); |
| } |
| |
| // Test that a compile error contains the name of the function, even if the name |
| // section is not present at the time the error is detected. |
| STREAM_TEST(TestCompileErrorFunctionName) { |
| const uint8_t bytes_module_with_code[] = { |
| WASM_MODULE_HEADER, // module header |
| kTypeSectionCode, // section code |
| U32V_1(1 + SIZEOF_SIG_ENTRY_x_x), // section size |
| U32V_1(1), // type count |
| SIG_ENTRY_x_x(kI32Code, kI32Code), // signature entry |
| kFunctionSectionCode, // section code |
| U32V_1(2), // section size |
| U32V_1(1), // functions count |
| 0, // signature index |
| kCodeSectionCode, // section code |
| U32V_1(4), // section size |
| U32V_1(1), // functions count |
| 2, // body size |
| 0, // local definitions count |
| kExprNop, // body |
| }; |
| |
| const uint8_t bytes_names[] = { |
| kUnknownSectionCode, // section code |
| U32V_1(11), // section size |
| 4, // section name length |
| 'n', // section name |
| 'a', // section name |
| 'm', // section name |
| 'e', // section name |
| NameSectionKindCode::kFunctionCode, // name section kind |
| 4, // name section kind length |
| 1, // num function names |
| 0, // function index |
| 1, // function name length |
| 'f', // function name |
| }; |
| |
| for (bool late_names : {false, true}) { |
| StreamTester tester(isolate); |
| |
| tester.OnBytesReceived(bytes_module_with_code, |
| arraysize(bytes_module_with_code)); |
| if (late_names) tester.RunCompilerTasks(); |
| tester.OnBytesReceived(bytes_names, arraysize(bytes_names)); |
| tester.FinishStream(); |
| |
| tester.RunCompilerTasks(); |
| |
| CHECK(tester.IsPromiseRejected()); |
| CHECK_EQ( |
| "CompileError: WebAssembly.compileStreaming(): Compiling function " |
| "#0:\"f\" failed: function body must end with \"end\" opcode @+26", |
| tester.error_message()); |
| } |
| } |
| |
| STREAM_TEST(TestSetModuleCodeSection) { |
| StreamTester tester(isolate); |
| |
| uint8_t code[] = { |
| U32V_1(1), // functions count |
| U32V_1(4), // body size |
| U32V_1(0), // locals count |
| kExprLocalGet, 0, kExprEnd // body |
| }; |
| |
| const uint8_t bytes[] = { |
| WASM_MODULE_HEADER, // module header |
| kTypeSectionCode, // section code |
| U32V_1(1 + SIZEOF_SIG_ENTRY_x_x), // section size |
| U32V_1(1), // type count |
| SIG_ENTRY_x_x(kI32Code, kI32Code), // signature entry |
| kFunctionSectionCode, // section code |
| U32V_1(1 + 1), // section size |
| U32V_1(1), // functions count |
| 0, // signature index |
| kCodeSectionCode, // section code |
| U32V_1(arraysize(code)), // section size |
| }; |
| |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.OnBytesReceived(code, arraysize(code)); |
| tester.FinishStream(); |
| tester.RunCompilerTasks(); |
| CHECK_EQ(tester.native_module()->module()->code.offset(), arraysize(bytes)); |
| CHECK_EQ(tester.native_module()->module()->code.length(), arraysize(code)); |
| CHECK(tester.IsPromiseFulfilled()); |
| } |
| |
| // Test that profiler does not crash when module is only partly compiled. |
| STREAM_TEST(TestProfilingMidStreaming) { |
| StreamTester tester(isolate); |
| Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); |
| Zone* zone = tester.zone(); |
| |
| // Build module with one exported (named) function. |
| ZoneBuffer buffer(zone); |
| { |
| TestSignatures sigs; |
| WasmModuleBuilder builder(zone); |
| WasmFunctionBuilder* f = builder.AddFunction(sigs.v_v()); |
| uint8_t code[] = {kExprEnd}; |
| f->EmitCode(code, arraysize(code)); |
| builder.AddExport(base::VectorOf("foo", 3), f); |
| builder.WriteTo(&buffer); |
| } |
| |
| // Start profiler to force code logging. |
| v8::CpuProfiler* cpu_profiler = v8::CpuProfiler::New(isolate); |
| cpu_profiler->StartProfiling(v8::String::Empty(isolate), |
| v8::CpuProfilingOptions{}); |
| |
| // Send incomplete wire bytes and start compilation. |
| tester.OnBytesReceived(buffer.begin(), buffer.end() - buffer.begin()); |
| tester.RunCompilerTasks(); |
| |
| // Trigger code logging explicitly like the profiler would do. |
| CHECK(WasmCode::ShouldBeLogged(i_isolate)); |
| GetWasmEngine()->LogOutstandingCodesForIsolate(i_isolate); |
| CHECK(tester.IsPromisePending()); |
| |
| // Finalize stream, stop profiler and clean up. |
| tester.FinishStream(); |
| CHECK(tester.IsPromiseFulfilled()); |
| v8::CpuProfile* profile = |
| cpu_profiler->StopProfiling(v8::String::Empty(isolate)); |
| profile->Delete(); |
| cpu_profiler->Dispose(); |
| } |
| |
| STREAM_TEST(TierDownWithError) { |
| // https://crbug.com/1160031 |
| StreamTester tester(isolate); |
| Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); |
| Zone* zone = tester.zone(); |
| |
| ZoneBuffer buffer(zone); |
| { |
| TestSignatures sigs; |
| WasmModuleBuilder builder(zone); |
| // Type error at i32.add. |
| builder.AddFunction(sigs.v_v())->Emit(kExprI32Add); |
| builder.WriteTo(&buffer); |
| } |
| |
| GetWasmEngine()->EnterDebuggingForIsolate(i_isolate); |
| |
| tester.OnBytesReceived(buffer.begin(), buffer.size()); |
| tester.FinishStream(); |
| tester.RunCompilerTasks(); |
| } |
| |
| STREAM_TEST(Regress1334651) { |
| StreamTester tester(isolate); |
| |
| const uint8_t bytes[] = {WASM_MODULE_HEADER, SECTION(Code, ENTRY_COUNT(0)), |
| SECTION(Unknown, 0)}; |
| |
| tester.OnBytesReceived(bytes, arraysize(bytes)); |
| tester.FinishStream(); |
| tester.RunCompilerTasks(); |
| } |
| |
| #undef STREAM_TEST |
| |
| } // namespace v8::internal::wasm |