// Copyright 2015 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/asmjs/asm-js.h" #include "src/asmjs/asm-names.h" #include "src/asmjs/asm-parser.h" #include "src/assert-scope.h" #include "src/ast/ast.h" #include "src/base/optional.h" #include "src/base/platform/elapsed-timer.h" #include "src/compiler.h" #include "src/execution.h" #include "src/handles.h" #include "src/heap/factory.h" #include "src/isolate.h" #include "src/objects-inl.h" #include "src/parsing/parse-info.h" #include "src/parsing/scanner-character-streams.h" #include "src/parsing/scanner.h" #include "src/unoptimized-compilation-info.h" #include "src/wasm/wasm-engine.h" #include "src/wasm/wasm-js.h" #include "src/wasm/wasm-limits.h" #include "src/wasm/wasm-module-builder.h" #include "src/wasm/wasm-objects-inl.h" #include "src/wasm/wasm-result.h" namespace v8 { namespace internal { const char* const AsmJs::kSingleFunctionName = "__single_function__"; namespace { enum WasmDataEntries { kWasmDataCompiledModule, kWasmDataUsesBitSet, kWasmDataEntryCount, }; Handle<Object> StdlibMathMember(Isolate* isolate, Handle<JSReceiver> stdlib, Handle<Name> name) { Handle<Name> math_name( isolate->factory()->InternalizeOneByteString(STATIC_CHAR_VECTOR("Math"))); Handle<Object> math = JSReceiver::GetDataProperty(stdlib, math_name); if (!math->IsJSReceiver()) return isolate->factory()->undefined_value(); Handle<JSReceiver> math_receiver = Handle<JSReceiver>::cast(math); Handle<Object> value = JSReceiver::GetDataProperty(math_receiver, name); return value; } bool AreStdlibMembersValid(Isolate* isolate, Handle<JSReceiver> stdlib, wasm::AsmJsParser::StdlibSet members, bool* is_typed_array) { if (members.Contains(wasm::AsmJsParser::StandardMember::kInfinity)) { members.Remove(wasm::AsmJsParser::StandardMember::kInfinity); Handle<Name> name = isolate->factory()->Infinity_string(); Handle<Object> value = JSReceiver::GetDataProperty(stdlib, name); if (!value->IsNumber() || !std::isinf(value->Number())) return false; } if (members.Contains(wasm::AsmJsParser::StandardMember::kNaN)) { members.Remove(wasm::AsmJsParser::StandardMember::kNaN); Handle<Name> name = isolate->factory()->NaN_string(); Handle<Object> value = JSReceiver::GetDataProperty(stdlib, name); if (!value->IsNaN()) return false; } #define STDLIB_MATH_FUNC(fname, FName, ignore1, ignore2) \ if (members.Contains(wasm::AsmJsParser::StandardMember::kMath##FName)) { \ members.Remove(wasm::AsmJsParser::StandardMember::kMath##FName); \ Handle<Name> name(isolate->factory()->InternalizeOneByteString( \ STATIC_CHAR_VECTOR(#fname))); \ Handle<Object> value = StdlibMathMember(isolate, stdlib, name); \ if (!value->IsJSFunction()) return false; \ SharedFunctionInfo* shared = Handle<JSFunction>::cast(value)->shared(); \ if (!shared->HasBuiltinId() || \ shared->builtin_id() != Builtins::kMath##FName) { \ return false; \ } \ DCHECK_EQ(shared->GetCode(), \ isolate->builtins()->builtin(Builtins::kMath##FName)); \ } STDLIB_MATH_FUNCTION_LIST(STDLIB_MATH_FUNC) #undef STDLIB_MATH_FUNC #define STDLIB_MATH_CONST(cname, const_value) \ if (members.Contains(wasm::AsmJsParser::StandardMember::kMath##cname)) { \ members.Remove(wasm::AsmJsParser::StandardMember::kMath##cname); \ Handle<Name> name(isolate->factory()->InternalizeOneByteString( \ STATIC_CHAR_VECTOR(#cname))); \ Handle<Object> value = StdlibMathMember(isolate, stdlib, name); \ if (!value->IsNumber() || value->Number() != const_value) return false; \ } STDLIB_MATH_VALUE_LIST(STDLIB_MATH_CONST) #undef STDLIB_MATH_CONST #define STDLIB_ARRAY_TYPE(fname, FName) \ if (members.Contains(wasm::AsmJsParser::StandardMember::k##FName)) { \ members.Remove(wasm::AsmJsParser::StandardMember::k##FName); \ *is_typed_array = true; \ Handle<Name> name(isolate->factory()->InternalizeOneByteString( \ STATIC_CHAR_VECTOR(#FName))); \ Handle<Object> value = JSReceiver::GetDataProperty(stdlib, name); \ if (!value->IsJSFunction()) return false; \ Handle<JSFunction> func = Handle<JSFunction>::cast(value); \ if (!func.is_identical_to(isolate->fname())) return false; \ } STDLIB_ARRAY_TYPE(int8_array_fun, Int8Array) STDLIB_ARRAY_TYPE(uint8_array_fun, Uint8Array) STDLIB_ARRAY_TYPE(int16_array_fun, Int16Array) STDLIB_ARRAY_TYPE(uint16_array_fun, Uint16Array) STDLIB_ARRAY_TYPE(int32_array_fun, Int32Array) STDLIB_ARRAY_TYPE(uint32_array_fun, Uint32Array) STDLIB_ARRAY_TYPE(float32_array_fun, Float32Array) STDLIB_ARRAY_TYPE(float64_array_fun, Float64Array) #undef STDLIB_ARRAY_TYPE // All members accounted for. DCHECK(members.IsEmpty()); return true; } void Report(Handle<Script> script, int position, Vector<const char> text, MessageTemplate::Template message_template, v8::Isolate::MessageErrorLevel level) { Isolate* isolate = script->GetIsolate(); MessageLocation location(script, position, position); Handle<String> text_object = isolate->factory()->InternalizeUtf8String(text); Handle<JSMessageObject> message = MessageHandler::MakeMessageObject( isolate, message_template, &location, text_object, Handle<FixedArray>::null()); message->set_error_level(level); MessageHandler::ReportMessage(isolate, &location, message); } // Hook to report successful execution of {AsmJs::CompileAsmViaWasm} phase. void ReportCompilationSuccess(Handle<Script> script, int position, double translate_time, double compile_time, size_t module_size) { if (FLAG_suppress_asm_messages || !FLAG_trace_asm_time) return; EmbeddedVector<char, 100> text; int length = SNPrintF( text, "success, asm->wasm: %0.3f ms, compile: %0.3f ms, %" PRIuS " bytes", translate_time, compile_time, module_size); CHECK_NE(-1, length); text.Truncate(length); Report(script, position, text, MessageTemplate::kAsmJsCompiled, v8::Isolate::kMessageInfo); } // Hook to report failed execution of {AsmJs::CompileAsmViaWasm} phase. void ReportCompilationFailure(ParseInfo* parse_info, int position, const char* reason) { if (FLAG_suppress_asm_messages) return; parse_info->pending_error_handler()->ReportWarningAt( position, position, MessageTemplate::kAsmJsInvalid, reason); } // Hook to report successful execution of {AsmJs::InstantiateAsmWasm} phase. void ReportInstantiationSuccess(Handle<Script> script, int position, double instantiate_time) { if (FLAG_suppress_asm_messages || !FLAG_trace_asm_time) return; EmbeddedVector<char, 50> text; int length = SNPrintF(text, "success, %0.3f ms", instantiate_time); CHECK_NE(-1, length); text.Truncate(length); Report(script, position, text, MessageTemplate::kAsmJsInstantiated, v8::Isolate::kMessageInfo); } // Hook to report failed execution of {AsmJs::InstantiateAsmWasm} phase. void ReportInstantiationFailure(Handle<Script> script, int position, const char* reason) { if (FLAG_suppress_asm_messages) return; Vector<const char> text = CStrVector(reason); Report(script, position, text, MessageTemplate::kAsmJsLinkingFailed, v8::Isolate::kMessageWarning); } } // namespace // The compilation of asm.js modules is split into two distinct steps: // [1] ExecuteJobImpl: The asm.js module source is parsed, validated, and // translated to a valid WebAssembly module. The result are two vectors // representing the encoded module as well as encoded source position // information and a StdlibSet bit set. // [2] FinalizeJobImpl: The module is handed to WebAssembly which decodes it // into an internal representation and eventually compiles it to machine // code. class AsmJsCompilationJob final : public UnoptimizedCompilationJob { public: explicit AsmJsCompilationJob(ParseInfo* parse_info, FunctionLiteral* literal, AccountingAllocator* allocator) : UnoptimizedCompilationJob(parse_info->stack_limit(), parse_info, &compilation_info_), allocator_(allocator), zone_(allocator, ZONE_NAME), compilation_info_(&zone_, parse_info, literal), module_(nullptr), asm_offsets_(nullptr), translate_time_(0), compile_time_(0), module_source_size_(0), translate_time_micro_(0), translate_zone_size_(0) {} protected: Status ExecuteJobImpl() final; Status FinalizeJobImpl(Handle<SharedFunctionInfo> shared_info, Isolate* isolate) final; private: void RecordHistograms(Isolate* isolate); AccountingAllocator* allocator_; Zone zone_; UnoptimizedCompilationInfo compilation_info_; wasm::ZoneBuffer* module_; wasm::ZoneBuffer* asm_offsets_; wasm::AsmJsParser::StdlibSet stdlib_uses_; double translate_time_; // Time (milliseconds) taken to execute step [1]. double compile_time_; // Time (milliseconds) taken to execute step [2]. int module_source_size_; // Module source size in bytes. int64_t translate_time_micro_; // Time (microseconds) taken to translate. size_t translate_zone_size_; DISALLOW_COPY_AND_ASSIGN(AsmJsCompilationJob); }; UnoptimizedCompilationJob::Status AsmJsCompilationJob::ExecuteJobImpl() { // Step 1: Translate asm.js module to WebAssembly module. size_t compile_zone_start = compilation_info()->zone()->allocation_size(); base::ElapsedTimer translate_timer; translate_timer.Start(); Zone* compile_zone = compilation_info()->zone(); Zone translate_zone(allocator_, ZONE_NAME); Utf16CharacterStream* stream = parse_info()->character_stream(); base::Optional<AllowHandleDereference> allow_deref; if (stream->can_access_heap()) { allow_deref.emplace(); } stream->Seek(compilation_info()->literal()->start_position()); wasm::AsmJsParser parser(&translate_zone, stack_limit(), stream); if (!parser.Run()) { if (!FLAG_suppress_asm_messages) { ReportCompilationFailure(parse_info(), parser.failure_location(), parser.failure_message()); } return FAILED; } module_ = new (compile_zone) wasm::ZoneBuffer(compile_zone); parser.module_builder()->WriteTo(*module_); asm_offsets_ = new (compile_zone) wasm::ZoneBuffer(compile_zone); parser.module_builder()->WriteAsmJsOffsetTable(*asm_offsets_); stdlib_uses_ = *parser.stdlib_uses(); size_t compile_zone_size = compilation_info()->zone()->allocation_size() - compile_zone_start; translate_zone_size_ = translate_zone.allocation_size(); translate_time_ = translate_timer.Elapsed().InMillisecondsF(); translate_time_micro_ = translate_timer.Elapsed().InMicroseconds(); module_source_size_ = compilation_info()->literal()->end_position() - compilation_info()->literal()->start_position(); if (FLAG_trace_asm_parser) { PrintF( "[asm.js translation successful: time=%0.3fms, " "translate_zone=%" PRIuS "KB, compile_zone+=%" PRIuS "KB]\n", translate_time_, translate_zone_size_ / KB, compile_zone_size / KB); } return SUCCEEDED; } UnoptimizedCompilationJob::Status AsmJsCompilationJob::FinalizeJobImpl( Handle<SharedFunctionInfo> shared_info, Isolate* isolate) { // Step 2: Compile and decode the WebAssembly module. base::ElapsedTimer compile_timer; compile_timer.Start(); Handle<HeapNumber> uses_bitset = isolate->factory()->NewHeapNumberFromBits(stdlib_uses_.ToIntegral()); wasm::ErrorThrower thrower(isolate, "AsmJs::Compile"); Handle<WasmModuleObject> compiled = isolate->wasm_engine() ->SyncCompileTranslatedAsmJs( isolate, &thrower, wasm::ModuleWireBytes(module_->begin(), module_->end()), parse_info()->script(), Vector<const byte>(asm_offsets_->begin(), asm_offsets_->size())) .ToHandleChecked(); DCHECK(!thrower.error()); compile_time_ = compile_timer.Elapsed().InMillisecondsF(); // The result is a compiled module and serialized standard library uses. Handle<FixedArray> result = isolate->factory()->NewFixedArray(kWasmDataEntryCount); result->set(kWasmDataCompiledModule, *compiled); result->set(kWasmDataUsesBitSet, *uses_bitset); compilation_info()->SetAsmWasmData(result); RecordHistograms(isolate); ReportCompilationSuccess(parse_info()->script(), compilation_info()->literal()->position(), translate_time_, compile_time_, module_->size()); return SUCCEEDED; } void AsmJsCompilationJob::RecordHistograms(Isolate* isolate) { Counters* counters = isolate->counters(); counters->asm_wasm_translation_time()->AddSample( static_cast<int>(translate_time_micro_)); counters->asm_wasm_translation_peak_memory_bytes()->AddSample( static_cast<int>(translate_zone_size_)); counters->asm_module_size_bytes()->AddSample(module_source_size_); // translation_throughput is not exact (assumes MB == 1000000). But that is ok // since the metric is stored in buckets that lose some precision anyways. int translation_throughput = translate_time_micro_ != 0 ? static_cast<int>(static_cast<int64_t>(module_source_size_) / translate_time_micro_) : 0; counters->asm_wasm_translation_throughput()->AddSample( translation_throughput); } UnoptimizedCompilationJob* AsmJs::NewCompilationJob( ParseInfo* parse_info, FunctionLiteral* literal, AccountingAllocator* allocator) { return new AsmJsCompilationJob(parse_info, literal, allocator); } namespace { inline bool IsValidAsmjsMemorySize(size_t size) { // Enforce asm.js spec minimum size. if (size < (1u << 12u)) return false; // Enforce engine-limited maximum allocation size. if (size > wasm::kV8MaxWasmMemoryBytes) return false; // Enforce flag-limited maximum allocation size. if (size > (FLAG_wasm_max_mem_pages * uint64_t{wasm::kWasmPageSize})) { return false; } // Enforce power-of-2 sizes for 2^12 - 2^24. if (size < (1u << 24u)) { uint32_t size32 = static_cast<uint32_t>(size); return base::bits::IsPowerOfTwo(size32); } // Enforce multiple of 2^24 for sizes >= 2^24 if ((size % (1u << 24u)) != 0) return false; // All checks passed! return true; } } // namespace MaybeHandle<Object> AsmJs::InstantiateAsmWasm(Isolate* isolate, Handle<SharedFunctionInfo> shared, Handle<FixedArray> wasm_data, Handle<JSReceiver> stdlib, Handle<JSReceiver> foreign, Handle<JSArrayBuffer> memory) { base::ElapsedTimer instantiate_timer; instantiate_timer.Start(); Handle<HeapNumber> uses_bitset( HeapNumber::cast(wasm_data->get(kWasmDataUsesBitSet)), isolate); Handle<WasmModuleObject> module( WasmModuleObject::cast(wasm_data->get(kWasmDataCompiledModule)), isolate); Handle<Script> script(Script::cast(shared->script()), isolate); // TODO(mstarzinger): The position currently points to the module definition // but should instead point to the instantiation site (more intuitive). int position = shared->StartPosition(); // Check that all used stdlib members are valid. bool stdlib_use_of_typed_array_present = false; wasm::AsmJsParser::StdlibSet stdlib_uses(uses_bitset->value_as_bits()); if (!stdlib_uses.IsEmpty()) { // No checking needed if no uses. if (stdlib.is_null()) { ReportInstantiationFailure(script, position, "Requires standard library"); return MaybeHandle<Object>(); } if (!AreStdlibMembersValid(isolate, stdlib, stdlib_uses, &stdlib_use_of_typed_array_present)) { ReportInstantiationFailure(script, position, "Unexpected stdlib member"); return MaybeHandle<Object>(); } } // Check that a valid heap buffer is provided if required. if (stdlib_use_of_typed_array_present) { if (memory.is_null()) { ReportInstantiationFailure(script, position, "Requires heap buffer"); return MaybeHandle<Object>(); } memory->set_is_growable(false); size_t size = NumberToSize(memory->byte_length()); // Check the asm.js heap size against the valid limits. if (!IsValidAsmjsMemorySize(size)) { ReportInstantiationFailure(script, position, "Invalid heap size"); return MaybeHandle<Object>(); } } else { memory = Handle<JSArrayBuffer>::null(); } wasm::ErrorThrower thrower(isolate, "AsmJs::Instantiate"); MaybeHandle<Object> maybe_module_object = isolate->wasm_engine()->SyncInstantiate(isolate, &thrower, module, foreign, memory); if (maybe_module_object.is_null()) { // An exception caused by the module start function will be set as pending // and bypass the {ErrorThrower}, this happens in case of a stack overflow. if (isolate->has_pending_exception()) isolate->clear_pending_exception(); if (thrower.error()) { ScopedVector<char> error_reason(100); SNPrintF(error_reason, "Internal wasm failure: %s", thrower.error_msg()); ReportInstantiationFailure(script, position, error_reason.start()); } else { ReportInstantiationFailure(script, position, "Internal wasm failure"); } thrower.Reset(); // Ensure exceptions do not propagate. return MaybeHandle<Object>(); } DCHECK(!thrower.error()); Handle<Object> module_object = maybe_module_object.ToHandleChecked(); ReportInstantiationSuccess(script, position, instantiate_timer.Elapsed().InMillisecondsF()); Handle<Name> single_function_name( isolate->factory()->InternalizeUtf8String(AsmJs::kSingleFunctionName)); MaybeHandle<Object> single_function = Object::GetProperty(isolate, module_object, single_function_name); if (!single_function.is_null() && !single_function.ToHandleChecked()->IsUndefined(isolate)) { return single_function; } Handle<String> exports_name = isolate->factory()->InternalizeUtf8String("exports"); return Object::GetProperty(isolate, module_object, exports_name); } } // namespace internal } // namespace v8