1 // Copyright 2015 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/asmjs/asm-js.h" 6 7 #include "src/asmjs/asm-names.h" 8 #include "src/asmjs/asm-parser.h" 9 #include "src/assert-scope.h" 10 #include "src/ast/ast.h" 11 #include "src/base/optional.h" 12 #include "src/base/platform/elapsed-timer.h" 13 #include "src/compiler.h" 14 #include "src/execution.h" 15 #include "src/handles.h" 16 #include "src/heap/factory.h" 17 #include "src/isolate.h" 18 #include "src/objects-inl.h" 19 #include "src/parsing/parse-info.h" 20 #include "src/parsing/scanner-character-streams.h" 21 #include "src/parsing/scanner.h" 22 #include "src/unoptimized-compilation-info.h" 23 24 #include "src/wasm/wasm-engine.h" 25 #include "src/wasm/wasm-js.h" 26 #include "src/wasm/wasm-limits.h" 27 #include "src/wasm/wasm-module-builder.h" 28 #include "src/wasm/wasm-objects-inl.h" 29 #include "src/wasm/wasm-result.h" 30 31 namespace v8 { 32 namespace internal { 33 34 const char* const AsmJs::kSingleFunctionName = "__single_function__"; 35 36 namespace { 37 enum WasmDataEntries { 38 kWasmDataCompiledModule, 39 kWasmDataUsesBitSet, 40 kWasmDataEntryCount, 41 }; 42 43 Handle<Object> StdlibMathMember(Isolate* isolate, Handle<JSReceiver> stdlib, 44 Handle<Name> name) { 45 Handle<Name> math_name( 46 isolate->factory()->InternalizeOneByteString(STATIC_CHAR_VECTOR("Math"))); 47 Handle<Object> math = JSReceiver::GetDataProperty(stdlib, math_name); 48 if (!math->IsJSReceiver()) return isolate->factory()->undefined_value(); 49 Handle<JSReceiver> math_receiver = Handle<JSReceiver>::cast(math); 50 Handle<Object> value = JSReceiver::GetDataProperty(math_receiver, name); 51 return value; 52 } 53 54 bool AreStdlibMembersValid(Isolate* isolate, Handle<JSReceiver> stdlib, 55 wasm::AsmJsParser::StdlibSet members, 56 bool* is_typed_array) { 57 if (members.Contains(wasm::AsmJsParser::StandardMember::kInfinity)) { 58 members.Remove(wasm::AsmJsParser::StandardMember::kInfinity); 59 Handle<Name> name = isolate->factory()->Infinity_string(); 60 Handle<Object> value = JSReceiver::GetDataProperty(stdlib, name); 61 if (!value->IsNumber() || !std::isinf(value->Number())) return false; 62 } 63 if (members.Contains(wasm::AsmJsParser::StandardMember::kNaN)) { 64 members.Remove(wasm::AsmJsParser::StandardMember::kNaN); 65 Handle<Name> name = isolate->factory()->NaN_string(); 66 Handle<Object> value = JSReceiver::GetDataProperty(stdlib, name); 67 if (!value->IsNaN()) return false; 68 } 69 #define STDLIB_MATH_FUNC(fname, FName, ignore1, ignore2) \ 70 if (members.Contains(wasm::AsmJsParser::StandardMember::kMath##FName)) { \ 71 members.Remove(wasm::AsmJsParser::StandardMember::kMath##FName); \ 72 Handle<Name> name(isolate->factory()->InternalizeOneByteString( \ 73 STATIC_CHAR_VECTOR(#fname))); \ 74 Handle<Object> value = StdlibMathMember(isolate, stdlib, name); \ 75 if (!value->IsJSFunction()) return false; \ 76 SharedFunctionInfo* shared = Handle<JSFunction>::cast(value)->shared(); \ 77 if (!shared->HasBuiltinId() || \ 78 shared->builtin_id() != Builtins::kMath##FName) { \ 79 return false; \ 80 } \ 81 DCHECK_EQ(shared->GetCode(), \ 82 isolate->builtins()->builtin(Builtins::kMath##FName)); \ 83 } 84 STDLIB_MATH_FUNCTION_LIST(STDLIB_MATH_FUNC) 85 #undef STDLIB_MATH_FUNC 86 #define STDLIB_MATH_CONST(cname, const_value) \ 87 if (members.Contains(wasm::AsmJsParser::StandardMember::kMath##cname)) { \ 88 members.Remove(wasm::AsmJsParser::StandardMember::kMath##cname); \ 89 Handle<Name> name(isolate->factory()->InternalizeOneByteString( \ 90 STATIC_CHAR_VECTOR(#cname))); \ 91 Handle<Object> value = StdlibMathMember(isolate, stdlib, name); \ 92 if (!value->IsNumber() || value->Number() != const_value) return false; \ 93 } 94 STDLIB_MATH_VALUE_LIST(STDLIB_MATH_CONST) 95 #undef STDLIB_MATH_CONST 96 #define STDLIB_ARRAY_TYPE(fname, FName) \ 97 if (members.Contains(wasm::AsmJsParser::StandardMember::k##FName)) { \ 98 members.Remove(wasm::AsmJsParser::StandardMember::k##FName); \ 99 *is_typed_array = true; \ 100 Handle<Name> name(isolate->factory()->InternalizeOneByteString( \ 101 STATIC_CHAR_VECTOR(#FName))); \ 102 Handle<Object> value = JSReceiver::GetDataProperty(stdlib, name); \ 103 if (!value->IsJSFunction()) return false; \ 104 Handle<JSFunction> func = Handle<JSFunction>::cast(value); \ 105 if (!func.is_identical_to(isolate->fname())) return false; \ 106 } 107 STDLIB_ARRAY_TYPE(int8_array_fun, Int8Array) 108 STDLIB_ARRAY_TYPE(uint8_array_fun, Uint8Array) 109 STDLIB_ARRAY_TYPE(int16_array_fun, Int16Array) 110 STDLIB_ARRAY_TYPE(uint16_array_fun, Uint16Array) 111 STDLIB_ARRAY_TYPE(int32_array_fun, Int32Array) 112 STDLIB_ARRAY_TYPE(uint32_array_fun, Uint32Array) 113 STDLIB_ARRAY_TYPE(float32_array_fun, Float32Array) 114 STDLIB_ARRAY_TYPE(float64_array_fun, Float64Array) 115 #undef STDLIB_ARRAY_TYPE 116 // All members accounted for. 117 DCHECK(members.IsEmpty()); 118 return true; 119 } 120 121 void Report(Handle<Script> script, int position, Vector<const char> text, 122 MessageTemplate::Template message_template, 123 v8::Isolate::MessageErrorLevel level) { 124 Isolate* isolate = script->GetIsolate(); 125 MessageLocation location(script, position, position); 126 Handle<String> text_object = isolate->factory()->InternalizeUtf8String(text); 127 Handle<JSMessageObject> message = MessageHandler::MakeMessageObject( 128 isolate, message_template, &location, text_object, 129 Handle<FixedArray>::null()); 130 message->set_error_level(level); 131 MessageHandler::ReportMessage(isolate, &location, message); 132 } 133 134 // Hook to report successful execution of {AsmJs::CompileAsmViaWasm} phase. 135 void ReportCompilationSuccess(Handle<Script> script, int position, 136 double translate_time, double compile_time, 137 size_t module_size) { 138 if (FLAG_suppress_asm_messages || !FLAG_trace_asm_time) return; 139 EmbeddedVector<char, 100> text; 140 int length = SNPrintF( 141 text, "success, asm->wasm: %0.3f ms, compile: %0.3f ms, %" PRIuS " bytes", 142 translate_time, compile_time, module_size); 143 CHECK_NE(-1, length); 144 text.Truncate(length); 145 Report(script, position, text, MessageTemplate::kAsmJsCompiled, 146 v8::Isolate::kMessageInfo); 147 } 148 149 // Hook to report failed execution of {AsmJs::CompileAsmViaWasm} phase. 150 void ReportCompilationFailure(ParseInfo* parse_info, int position, 151 const char* reason) { 152 if (FLAG_suppress_asm_messages) return; 153 parse_info->pending_error_handler()->ReportWarningAt( 154 position, position, MessageTemplate::kAsmJsInvalid, reason); 155 } 156 157 // Hook to report successful execution of {AsmJs::InstantiateAsmWasm} phase. 158 void ReportInstantiationSuccess(Handle<Script> script, int position, 159 double instantiate_time) { 160 if (FLAG_suppress_asm_messages || !FLAG_trace_asm_time) return; 161 EmbeddedVector<char, 50> text; 162 int length = SNPrintF(text, "success, %0.3f ms", instantiate_time); 163 CHECK_NE(-1, length); 164 text.Truncate(length); 165 Report(script, position, text, MessageTemplate::kAsmJsInstantiated, 166 v8::Isolate::kMessageInfo); 167 } 168 169 // Hook to report failed execution of {AsmJs::InstantiateAsmWasm} phase. 170 void ReportInstantiationFailure(Handle<Script> script, int position, 171 const char* reason) { 172 if (FLAG_suppress_asm_messages) return; 173 Vector<const char> text = CStrVector(reason); 174 Report(script, position, text, MessageTemplate::kAsmJsLinkingFailed, 175 v8::Isolate::kMessageWarning); 176 } 177 178 } // namespace 179 180 // The compilation of asm.js modules is split into two distinct steps: 181 // [1] ExecuteJobImpl: The asm.js module source is parsed, validated, and 182 // translated to a valid WebAssembly module. The result are two vectors 183 // representing the encoded module as well as encoded source position 184 // information and a StdlibSet bit set. 185 // [2] FinalizeJobImpl: The module is handed to WebAssembly which decodes it 186 // into an internal representation and eventually compiles it to machine 187 // code. 188 class AsmJsCompilationJob final : public UnoptimizedCompilationJob { 189 public: 190 explicit AsmJsCompilationJob(ParseInfo* parse_info, FunctionLiteral* literal, 191 AccountingAllocator* allocator) 192 : UnoptimizedCompilationJob(parse_info->stack_limit(), parse_info, 193 &compilation_info_), 194 allocator_(allocator), 195 zone_(allocator, ZONE_NAME), 196 compilation_info_(&zone_, parse_info, literal), 197 module_(nullptr), 198 asm_offsets_(nullptr), 199 translate_time_(0), 200 compile_time_(0), 201 module_source_size_(0), 202 translate_time_micro_(0), 203 translate_zone_size_(0) {} 204 205 protected: 206 Status ExecuteJobImpl() final; 207 Status FinalizeJobImpl(Handle<SharedFunctionInfo> shared_info, 208 Isolate* isolate) final; 209 210 private: 211 void RecordHistograms(Isolate* isolate); 212 213 AccountingAllocator* allocator_; 214 Zone zone_; 215 UnoptimizedCompilationInfo compilation_info_; 216 wasm::ZoneBuffer* module_; 217 wasm::ZoneBuffer* asm_offsets_; 218 wasm::AsmJsParser::StdlibSet stdlib_uses_; 219 220 double translate_time_; // Time (milliseconds) taken to execute step [1]. 221 double compile_time_; // Time (milliseconds) taken to execute step [2]. 222 int module_source_size_; // Module source size in bytes. 223 int64_t translate_time_micro_; // Time (microseconds) taken to translate. 224 size_t translate_zone_size_; 225 226 DISALLOW_COPY_AND_ASSIGN(AsmJsCompilationJob); 227 }; 228 229 UnoptimizedCompilationJob::Status AsmJsCompilationJob::ExecuteJobImpl() { 230 // Step 1: Translate asm.js module to WebAssembly module. 231 size_t compile_zone_start = compilation_info()->zone()->allocation_size(); 232 base::ElapsedTimer translate_timer; 233 translate_timer.Start(); 234 235 Zone* compile_zone = compilation_info()->zone(); 236 Zone translate_zone(allocator_, ZONE_NAME); 237 238 Utf16CharacterStream* stream = parse_info()->character_stream(); 239 base::Optional<AllowHandleDereference> allow_deref; 240 if (stream->can_access_heap()) { 241 allow_deref.emplace(); 242 } 243 stream->Seek(compilation_info()->literal()->start_position()); 244 wasm::AsmJsParser parser(&translate_zone, stack_limit(), stream); 245 if (!parser.Run()) { 246 if (!FLAG_suppress_asm_messages) { 247 ReportCompilationFailure(parse_info(), parser.failure_location(), 248 parser.failure_message()); 249 } 250 return FAILED; 251 } 252 module_ = new (compile_zone) wasm::ZoneBuffer(compile_zone); 253 parser.module_builder()->WriteTo(*module_); 254 asm_offsets_ = new (compile_zone) wasm::ZoneBuffer(compile_zone); 255 parser.module_builder()->WriteAsmJsOffsetTable(*asm_offsets_); 256 stdlib_uses_ = *parser.stdlib_uses(); 257 258 size_t compile_zone_size = 259 compilation_info()->zone()->allocation_size() - compile_zone_start; 260 translate_zone_size_ = translate_zone.allocation_size(); 261 translate_time_ = translate_timer.Elapsed().InMillisecondsF(); 262 translate_time_micro_ = translate_timer.Elapsed().InMicroseconds(); 263 module_source_size_ = compilation_info()->literal()->end_position() - 264 compilation_info()->literal()->start_position(); 265 if (FLAG_trace_asm_parser) { 266 PrintF( 267 "[asm.js translation successful: time=%0.3fms, " 268 "translate_zone=%" PRIuS "KB, compile_zone+=%" PRIuS "KB]\n", 269 translate_time_, translate_zone_size_ / KB, compile_zone_size / KB); 270 } 271 return SUCCEEDED; 272 } 273 274 UnoptimizedCompilationJob::Status AsmJsCompilationJob::FinalizeJobImpl( 275 Handle<SharedFunctionInfo> shared_info, Isolate* isolate) { 276 // Step 2: Compile and decode the WebAssembly module. 277 base::ElapsedTimer compile_timer; 278 compile_timer.Start(); 279 280 Handle<HeapNumber> uses_bitset = 281 isolate->factory()->NewHeapNumberFromBits(stdlib_uses_.ToIntegral()); 282 283 wasm::ErrorThrower thrower(isolate, "AsmJs::Compile"); 284 Handle<WasmModuleObject> compiled = 285 isolate->wasm_engine() 286 ->SyncCompileTranslatedAsmJs( 287 isolate, &thrower, 288 wasm::ModuleWireBytes(module_->begin(), module_->end()), 289 parse_info()->script(), 290 Vector<const byte>(asm_offsets_->begin(), asm_offsets_->size())) 291 .ToHandleChecked(); 292 DCHECK(!thrower.error()); 293 compile_time_ = compile_timer.Elapsed().InMillisecondsF(); 294 295 // The result is a compiled module and serialized standard library uses. 296 Handle<FixedArray> result = 297 isolate->factory()->NewFixedArray(kWasmDataEntryCount); 298 result->set(kWasmDataCompiledModule, *compiled); 299 result->set(kWasmDataUsesBitSet, *uses_bitset); 300 compilation_info()->SetAsmWasmData(result); 301 302 RecordHistograms(isolate); 303 ReportCompilationSuccess(parse_info()->script(), 304 compilation_info()->literal()->position(), 305 translate_time_, compile_time_, module_->size()); 306 return SUCCEEDED; 307 } 308 309 void AsmJsCompilationJob::RecordHistograms(Isolate* isolate) { 310 Counters* counters = isolate->counters(); 311 counters->asm_wasm_translation_time()->AddSample( 312 static_cast<int>(translate_time_micro_)); 313 counters->asm_wasm_translation_peak_memory_bytes()->AddSample( 314 static_cast<int>(translate_zone_size_)); 315 counters->asm_module_size_bytes()->AddSample(module_source_size_); 316 // translation_throughput is not exact (assumes MB == 1000000). But that is ok 317 // since the metric is stored in buckets that lose some precision anyways. 318 int translation_throughput = 319 translate_time_micro_ != 0 320 ? static_cast<int>(static_cast<int64_t>(module_source_size_) / 321 translate_time_micro_) 322 : 0; 323 counters->asm_wasm_translation_throughput()->AddSample( 324 translation_throughput); 325 } 326 327 UnoptimizedCompilationJob* AsmJs::NewCompilationJob( 328 ParseInfo* parse_info, FunctionLiteral* literal, 329 AccountingAllocator* allocator) { 330 return new AsmJsCompilationJob(parse_info, literal, allocator); 331 } 332 333 namespace { 334 inline bool IsValidAsmjsMemorySize(size_t size) { 335 // Enforce asm.js spec minimum size. 336 if (size < (1u << 12u)) return false; 337 // Enforce engine-limited maximum allocation size. 338 if (size > wasm::kV8MaxWasmMemoryBytes) return false; 339 // Enforce flag-limited maximum allocation size. 340 if (size > (FLAG_wasm_max_mem_pages * uint64_t{wasm::kWasmPageSize})) { 341 return false; 342 } 343 // Enforce power-of-2 sizes for 2^12 - 2^24. 344 if (size < (1u << 24u)) { 345 uint32_t size32 = static_cast<uint32_t>(size); 346 return base::bits::IsPowerOfTwo(size32); 347 } 348 // Enforce multiple of 2^24 for sizes >= 2^24 349 if ((size % (1u << 24u)) != 0) return false; 350 // All checks passed! 351 return true; 352 } 353 } // namespace 354 355 MaybeHandle<Object> AsmJs::InstantiateAsmWasm(Isolate* isolate, 356 Handle<SharedFunctionInfo> shared, 357 Handle<FixedArray> wasm_data, 358 Handle<JSReceiver> stdlib, 359 Handle<JSReceiver> foreign, 360 Handle<JSArrayBuffer> memory) { 361 base::ElapsedTimer instantiate_timer; 362 instantiate_timer.Start(); 363 Handle<HeapNumber> uses_bitset( 364 HeapNumber::cast(wasm_data->get(kWasmDataUsesBitSet)), isolate); 365 Handle<WasmModuleObject> module( 366 WasmModuleObject::cast(wasm_data->get(kWasmDataCompiledModule)), isolate); 367 Handle<Script> script(Script::cast(shared->script()), isolate); 368 // TODO(mstarzinger): The position currently points to the module definition 369 // but should instead point to the instantiation site (more intuitive). 370 int position = shared->StartPosition(); 371 372 // Check that all used stdlib members are valid. 373 bool stdlib_use_of_typed_array_present = false; 374 wasm::AsmJsParser::StdlibSet stdlib_uses(uses_bitset->value_as_bits()); 375 if (!stdlib_uses.IsEmpty()) { // No checking needed if no uses. 376 if (stdlib.is_null()) { 377 ReportInstantiationFailure(script, position, "Requires standard library"); 378 return MaybeHandle<Object>(); 379 } 380 if (!AreStdlibMembersValid(isolate, stdlib, stdlib_uses, 381 &stdlib_use_of_typed_array_present)) { 382 ReportInstantiationFailure(script, position, "Unexpected stdlib member"); 383 return MaybeHandle<Object>(); 384 } 385 } 386 387 // Check that a valid heap buffer is provided if required. 388 if (stdlib_use_of_typed_array_present) { 389 if (memory.is_null()) { 390 ReportInstantiationFailure(script, position, "Requires heap buffer"); 391 return MaybeHandle<Object>(); 392 } 393 memory->set_is_growable(false); 394 size_t size = NumberToSize(memory->byte_length()); 395 // Check the asm.js heap size against the valid limits. 396 if (!IsValidAsmjsMemorySize(size)) { 397 ReportInstantiationFailure(script, position, "Invalid heap size"); 398 return MaybeHandle<Object>(); 399 } 400 } else { 401 memory = Handle<JSArrayBuffer>::null(); 402 } 403 404 wasm::ErrorThrower thrower(isolate, "AsmJs::Instantiate"); 405 MaybeHandle<Object> maybe_module_object = 406 isolate->wasm_engine()->SyncInstantiate(isolate, &thrower, module, 407 foreign, memory); 408 if (maybe_module_object.is_null()) { 409 // An exception caused by the module start function will be set as pending 410 // and bypass the {ErrorThrower}, this happens in case of a stack overflow. 411 if (isolate->has_pending_exception()) isolate->clear_pending_exception(); 412 if (thrower.error()) { 413 ScopedVector<char> error_reason(100); 414 SNPrintF(error_reason, "Internal wasm failure: %s", thrower.error_msg()); 415 ReportInstantiationFailure(script, position, error_reason.start()); 416 } else { 417 ReportInstantiationFailure(script, position, "Internal wasm failure"); 418 } 419 thrower.Reset(); // Ensure exceptions do not propagate. 420 return MaybeHandle<Object>(); 421 } 422 DCHECK(!thrower.error()); 423 Handle<Object> module_object = maybe_module_object.ToHandleChecked(); 424 425 ReportInstantiationSuccess(script, position, 426 instantiate_timer.Elapsed().InMillisecondsF()); 427 428 Handle<Name> single_function_name( 429 isolate->factory()->InternalizeUtf8String(AsmJs::kSingleFunctionName)); 430 MaybeHandle<Object> single_function = 431 Object::GetProperty(isolate, module_object, single_function_name); 432 if (!single_function.is_null() && 433 !single_function.ToHandleChecked()->IsUndefined(isolate)) { 434 return single_function; 435 } 436 437 Handle<String> exports_name = 438 isolate->factory()->InternalizeUtf8String("exports"); 439 return Object::GetProperty(isolate, module_object, exports_name); 440 } 441 442 } // namespace internal 443 } // namespace v8 444