1 // Copyright 2018 the V8 project authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "src/wasm/wasm-engine.h" 6 7 #include "src/code-tracer.h" 8 #include "src/compilation-statistics.h" 9 #include "src/objects-inl.h" 10 #include "src/objects/js-promise.h" 11 #include "src/wasm/function-compiler.h" 12 #include "src/wasm/module-compiler.h" 13 #include "src/wasm/module-decoder.h" 14 #include "src/wasm/streaming-decoder.h" 15 #include "src/wasm/wasm-objects-inl.h" 16 17 namespace v8 { 18 namespace internal { 19 namespace wasm { 20 21 WasmEngine::WasmEngine() 22 : code_manager_(&memory_tracker_, kMaxWasmCodeMemory) {} 23 24 WasmEngine::~WasmEngine() { 25 // All AsyncCompileJobs have been canceled. 26 DCHECK(jobs_.empty()); 27 } 28 29 bool WasmEngine::SyncValidate(Isolate* isolate, const WasmFeatures& enabled, 30 const ModuleWireBytes& bytes) { 31 // TODO(titzer): remove dependency on the isolate. 32 if (bytes.start() == nullptr || bytes.length() == 0) return false; 33 ModuleResult result = 34 DecodeWasmModule(enabled, bytes.start(), bytes.end(), true, kWasmOrigin, 35 isolate->counters(), allocator()); 36 return result.ok(); 37 } 38 39 MaybeHandle<WasmModuleObject> WasmEngine::SyncCompileTranslatedAsmJs( 40 Isolate* isolate, ErrorThrower* thrower, const ModuleWireBytes& bytes, 41 Handle<Script> asm_js_script, 42 Vector<const byte> asm_js_offset_table_bytes) { 43 ModuleResult result = 44 DecodeWasmModule(kAsmjsWasmFeatures, bytes.start(), bytes.end(), false, 45 kAsmJsOrigin, isolate->counters(), allocator()); 46 CHECK(!result.failed()); 47 48 // Transfer ownership of the WasmModule to the {Managed<WasmModule>} generated 49 // in {CompileToModuleObject}. 50 return CompileToModuleObject(isolate, kAsmjsWasmFeatures, thrower, 51 std::move(result.val), bytes, asm_js_script, 52 asm_js_offset_table_bytes); 53 } 54 55 MaybeHandle<WasmModuleObject> WasmEngine::SyncCompile( 56 Isolate* isolate, const WasmFeatures& enabled, ErrorThrower* thrower, 57 const ModuleWireBytes& bytes) { 58 ModuleResult result = 59 DecodeWasmModule(enabled, bytes.start(), bytes.end(), false, kWasmOrigin, 60 isolate->counters(), allocator()); 61 if (result.failed()) { 62 thrower->CompileFailed("Wasm decoding failed", result); 63 return {}; 64 } 65 66 // Transfer ownership of the WasmModule to the {Managed<WasmModule>} generated 67 // in {CompileToModuleObject}. 68 return CompileToModuleObject(isolate, enabled, thrower, std::move(result.val), 69 bytes, Handle<Script>(), Vector<const byte>()); 70 } 71 72 MaybeHandle<WasmInstanceObject> WasmEngine::SyncInstantiate( 73 Isolate* isolate, ErrorThrower* thrower, 74 Handle<WasmModuleObject> module_object, MaybeHandle<JSReceiver> imports, 75 MaybeHandle<JSArrayBuffer> memory) { 76 return InstantiateToInstanceObject(isolate, thrower, module_object, imports, 77 memory); 78 } 79 80 void WasmEngine::AsyncInstantiate( 81 Isolate* isolate, std::unique_ptr<InstantiationResultResolver> resolver, 82 Handle<WasmModuleObject> module_object, MaybeHandle<JSReceiver> imports) { 83 ErrorThrower thrower(isolate, "WebAssembly Instantiation"); 84 // Instantiate a TryCatch so that caught exceptions won't progagate out. 85 // They will still be set as pending exceptions on the isolate. 86 // TODO(clemensh): Avoid TryCatch, use Execution::TryCall internally to invoke 87 // start function and report thrown exception explicitly via out argument. 88 v8::TryCatch catcher(reinterpret_cast<v8::Isolate*>(isolate)); 89 catcher.SetVerbose(false); 90 catcher.SetCaptureMessage(false); 91 92 MaybeHandle<WasmInstanceObject> instance_object = SyncInstantiate( 93 isolate, &thrower, module_object, imports, Handle<JSArrayBuffer>::null()); 94 95 if (!instance_object.is_null()) { 96 resolver->OnInstantiationSucceeded(instance_object.ToHandleChecked()); 97 return; 98 } 99 100 if (isolate->has_pending_exception()) { 101 // The JS code executed during instantiation has thrown an exception. 102 // We have to move the exception to the promise chain. 103 Handle<Object> exception(isolate->pending_exception(), isolate); 104 isolate->clear_pending_exception(); 105 DCHECK(*isolate->external_caught_exception_address()); 106 *isolate->external_caught_exception_address() = false; 107 resolver->OnInstantiationFailed(exception); 108 thrower.Reset(); 109 } else { 110 DCHECK(thrower.error()); 111 resolver->OnInstantiationFailed(thrower.Reify()); 112 } 113 } 114 115 void WasmEngine::AsyncCompile( 116 Isolate* isolate, const WasmFeatures& enabled, 117 std::shared_ptr<CompilationResultResolver> resolver, 118 const ModuleWireBytes& bytes, bool is_shared) { 119 if (!FLAG_wasm_async_compilation) { 120 // Asynchronous compilation disabled; fall back on synchronous compilation. 121 ErrorThrower thrower(isolate, "WasmCompile"); 122 MaybeHandle<WasmModuleObject> module_object; 123 if (is_shared) { 124 // Make a copy of the wire bytes to avoid concurrent modification. 125 std::unique_ptr<uint8_t[]> copy(new uint8_t[bytes.length()]); 126 memcpy(copy.get(), bytes.start(), bytes.length()); 127 ModuleWireBytes bytes_copy(copy.get(), copy.get() + bytes.length()); 128 module_object = SyncCompile(isolate, enabled, &thrower, bytes_copy); 129 } else { 130 // The wire bytes are not shared, OK to use them directly. 131 module_object = SyncCompile(isolate, enabled, &thrower, bytes); 132 } 133 if (thrower.error()) { 134 resolver->OnCompilationFailed(thrower.Reify()); 135 return; 136 } 137 Handle<WasmModuleObject> module = module_object.ToHandleChecked(); 138 resolver->OnCompilationSucceeded(module); 139 return; 140 } 141 142 if (FLAG_wasm_test_streaming) { 143 std::shared_ptr<StreamingDecoder> streaming_decoder = 144 StartStreamingCompilation(isolate, enabled, 145 handle(isolate->context(), isolate), 146 std::move(resolver)); 147 streaming_decoder->OnBytesReceived(bytes.module_bytes()); 148 streaming_decoder->Finish(); 149 return; 150 } 151 // Make a copy of the wire bytes in case the user program changes them 152 // during asynchronous compilation. 153 std::unique_ptr<byte[]> copy(new byte[bytes.length()]); 154 memcpy(copy.get(), bytes.start(), bytes.length()); 155 156 AsyncCompileJob* job = CreateAsyncCompileJob( 157 isolate, enabled, std::move(copy), bytes.length(), 158 handle(isolate->context(), isolate), std::move(resolver)); 159 job->Start(); 160 } 161 162 std::shared_ptr<StreamingDecoder> WasmEngine::StartStreamingCompilation( 163 Isolate* isolate, const WasmFeatures& enabled, Handle<Context> context, 164 std::shared_ptr<CompilationResultResolver> resolver) { 165 AsyncCompileJob* job = 166 CreateAsyncCompileJob(isolate, enabled, std::unique_ptr<byte[]>(nullptr), 167 0, context, std::move(resolver)); 168 return job->CreateStreamingDecoder(); 169 } 170 171 bool WasmEngine::CompileFunction(Isolate* isolate, NativeModule* native_module, 172 uint32_t function_index, ExecutionTier tier) { 173 ErrorThrower thrower(isolate, "Manually requested tier up"); 174 // Note we assume that "one-off" compilations can discard detected features. 175 WasmFeatures detected = kNoWasmFeatures; 176 WasmCode* ret = WasmCompilationUnit::CompileWasmFunction( 177 isolate, native_module, &detected, &thrower, 178 GetModuleEnv(native_module->compilation_state()), 179 &native_module->module()->functions[function_index], tier); 180 return ret != nullptr; 181 } 182 183 std::shared_ptr<NativeModule> WasmEngine::ExportNativeModule( 184 Handle<WasmModuleObject> module_object) { 185 return module_object->managed_native_module()->get(); 186 } 187 188 Handle<WasmModuleObject> WasmEngine::ImportNativeModule( 189 Isolate* isolate, std::shared_ptr<NativeModule> shared_module) { 190 CHECK_EQ(code_manager(), shared_module->code_manager()); 191 Vector<const byte> wire_bytes = shared_module->wire_bytes(); 192 Handle<Script> script = CreateWasmScript(isolate, wire_bytes); 193 Handle<WasmModuleObject> module_object = 194 WasmModuleObject::New(isolate, shared_module, script); 195 196 // TODO(6792): Wrappers below might be cloned using {Factory::CopyCode}. 197 // This requires unlocking the code space here. This should eventually be 198 // moved into the allocator. 199 CodeSpaceMemoryModificationScope modification_scope(isolate->heap()); 200 CompileJsToWasmWrappers(isolate, module_object); 201 return module_object; 202 } 203 204 CompilationStatistics* WasmEngine::GetOrCreateTurboStatistics() { 205 base::LockGuard<base::Mutex> guard(&mutex_); 206 if (compilation_stats_ == nullptr) { 207 compilation_stats_.reset(new CompilationStatistics()); 208 } 209 return compilation_stats_.get(); 210 } 211 212 void WasmEngine::DumpAndResetTurboStatistics() { 213 base::LockGuard<base::Mutex> guard(&mutex_); 214 if (compilation_stats_ != nullptr) { 215 StdoutStream os; 216 os << AsPrintableStatistics{*compilation_stats_.get(), false} << std::endl; 217 } 218 compilation_stats_.reset(); 219 } 220 221 CodeTracer* WasmEngine::GetCodeTracer() { 222 base::LockGuard<base::Mutex> guard(&mutex_); 223 if (code_tracer_ == nullptr) code_tracer_.reset(new CodeTracer(-1)); 224 return code_tracer_.get(); 225 } 226 227 AsyncCompileJob* WasmEngine::CreateAsyncCompileJob( 228 Isolate* isolate, const WasmFeatures& enabled, 229 std::unique_ptr<byte[]> bytes_copy, size_t length, Handle<Context> context, 230 std::shared_ptr<CompilationResultResolver> resolver) { 231 AsyncCompileJob* job = 232 new AsyncCompileJob(isolate, enabled, std::move(bytes_copy), length, 233 context, std::move(resolver)); 234 // Pass ownership to the unique_ptr in {jobs_}. 235 base::LockGuard<base::Mutex> guard(&mutex_); 236 jobs_[job] = std::unique_ptr<AsyncCompileJob>(job); 237 return job; 238 } 239 240 std::unique_ptr<AsyncCompileJob> WasmEngine::RemoveCompileJob( 241 AsyncCompileJob* job) { 242 base::LockGuard<base::Mutex> guard(&mutex_); 243 auto item = jobs_.find(job); 244 DCHECK(item != jobs_.end()); 245 std::unique_ptr<AsyncCompileJob> result = std::move(item->second); 246 jobs_.erase(item); 247 return result; 248 } 249 250 bool WasmEngine::HasRunningCompileJob(Isolate* isolate) { 251 base::LockGuard<base::Mutex> guard(&mutex_); 252 for (auto& entry : jobs_) { 253 if (entry.first->isolate() == isolate) return true; 254 } 255 return false; 256 } 257 258 void WasmEngine::DeleteCompileJobsOnIsolate(Isolate* isolate) { 259 base::LockGuard<base::Mutex> guard(&mutex_); 260 for (auto it = jobs_.begin(); it != jobs_.end();) { 261 if (it->first->isolate() == isolate) { 262 it = jobs_.erase(it); 263 } else { 264 ++it; 265 } 266 } 267 } 268 269 namespace { 270 271 struct WasmEnginePointerConstructTrait final { 272 static void Construct(void* raw_ptr) { 273 auto engine_ptr = reinterpret_cast<std::shared_ptr<WasmEngine>*>(raw_ptr); 274 *engine_ptr = std::shared_ptr<WasmEngine>(); 275 } 276 }; 277 278 // Holds the global shared pointer to the single {WasmEngine} that is intended 279 // to be shared among Isolates within the same process. The {LazyStaticInstance} 280 // here is required because {std::shared_ptr} has a non-trivial initializer. 281 base::LazyStaticInstance<std::shared_ptr<WasmEngine>, 282 WasmEnginePointerConstructTrait>::type 283 global_wasm_engine; 284 285 } // namespace 286 287 void WasmEngine::InitializeOncePerProcess() { 288 if (!FLAG_wasm_shared_engine) return; 289 global_wasm_engine.Pointer()->reset(new WasmEngine()); 290 } 291 292 void WasmEngine::GlobalTearDown() { 293 if (!FLAG_wasm_shared_engine) return; 294 global_wasm_engine.Pointer()->reset(); 295 } 296 297 std::shared_ptr<WasmEngine> WasmEngine::GetWasmEngine() { 298 if (FLAG_wasm_shared_engine) return global_wasm_engine.Get(); 299 return std::shared_ptr<WasmEngine>(new WasmEngine()); 300 } 301 302 } // namespace wasm 303 } // namespace internal 304 } // namespace v8 305