198deea289
Allow "iterative" finalization when off-thread finalization is enabled, meaning that each compiled function is finalized immediately after compilation, rather than all functions being first compiled and then finalized. This is what we do on the main thread, and it reduces peak Zone memory usage by being able to discard empty compilation Zones earlier. One necessary functionality for this was being able to defer the finalization of asm.js functions until the main thread pause, since they can't be finalized off-thread -- previously we would just bail out of doing the off-thread finalization if any inner function was asm.js. Bug: chromium:1011762 Change-Id: I21ff69d62eaa93b5ff908624b7115601e36f70f1 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2282536 Reviewed-by: Clemens Backes <clemensb@chromium.org> Reviewed-by: Ross McIlroy <rmcilroy@chromium.org> Commit-Queue: Leszek Swirski <leszeks@chromium.org> Auto-Submit: Leszek Swirski <leszeks@chromium.org> Cr-Commit-Position: refs/heads/master@{#69032}
428 lines
18 KiB
C++
428 lines
18 KiB
C++
// 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/ast/ast.h"
|
|
#include "src/base/optional.h"
|
|
#include "src/base/platform/elapsed-timer.h"
|
|
#include "src/codegen/compiler.h"
|
|
#include "src/codegen/unoptimized-compilation-info.h"
|
|
#include "src/common/assert-scope.h"
|
|
#include "src/common/message-template.h"
|
|
#include "src/execution/execution.h"
|
|
#include "src/execution/isolate.h"
|
|
#include "src/handles/handles.h"
|
|
#include "src/heap/factory.h"
|
|
#include "src/logging/counters.h"
|
|
#include "src/objects/heap-number-inl.h"
|
|
#include "src/objects/objects-inl.h"
|
|
#include "src/parsing/parse-info.h"
|
|
#include "src/parsing/scanner-character-streams.h"
|
|
#include "src/parsing/scanner.h"
|
|
#include "src/utils/vector.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 {
|
|
|
|
Handle<Object> StdlibMathMember(Isolate* isolate, Handle<JSReceiver> stdlib,
|
|
Handle<Name> name) {
|
|
Handle<Name> math_name(
|
|
isolate->factory()->InternalizeString(StaticCharVector("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()->InternalizeString(StaticCharVector(#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()->InternalizeString(StaticCharVector(#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()->InternalizeString(StaticCharVector(#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.empty());
|
|
return true;
|
|
}
|
|
|
|
void Report(Handle<Script> script, int position, Vector<const char> text,
|
|
MessageTemplate 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 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, compile time %0.3f ms, %zu bytes",
|
|
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),
|
|
compile_time_(0),
|
|
module_source_size_(0) {}
|
|
|
|
protected:
|
|
Status ExecuteJobImpl() final;
|
|
Status FinalizeJobImpl(Handle<SharedFunctionInfo> shared_info,
|
|
Isolate* isolate) final;
|
|
Status FinalizeJobImpl(Handle<SharedFunctionInfo> shared_info,
|
|
OffThreadIsolate* isolate) final {
|
|
return CompilationJob::RETRY_ON_MAIN_THREAD;
|
|
}
|
|
|
|
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 compile_time_; // Time (milliseconds) taken to execute step [2].
|
|
int module_source_size_; // Module source size in bytes.
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(AsmJsCompilationJob);
|
|
};
|
|
|
|
UnoptimizedCompilationJob::Status AsmJsCompilationJob::ExecuteJobImpl() {
|
|
// Step 1: Translate asm.js module to WebAssembly module.
|
|
Zone* compile_zone = &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_ = compile_zone->New<wasm::ZoneBuffer>(compile_zone);
|
|
parser.module_builder()->WriteTo(module_);
|
|
asm_offsets_ = compile_zone->New<wasm::ZoneBuffer>(compile_zone);
|
|
parser.module_builder()->WriteAsmJsOffsetTable(asm_offsets_);
|
|
stdlib_uses_ = *parser.stdlib_uses();
|
|
|
|
module_source_size_ = compilation_info()->literal()->end_position() -
|
|
compilation_info()->literal()->start_position();
|
|
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());
|
|
|
|
// The result is a compiled module and serialized standard library uses.
|
|
wasm::ErrorThrower thrower(isolate, "AsmJs::Compile");
|
|
Handle<AsmWasmData> result =
|
|
isolate->wasm_engine()
|
|
->SyncCompileTranslatedAsmJs(
|
|
isolate, &thrower,
|
|
wasm::ModuleWireBytes(module_->begin(), module_->end()),
|
|
VectorOf(*asm_offsets_), uses_bitset,
|
|
shared_info->language_mode())
|
|
.ToHandleChecked();
|
|
DCHECK(!thrower.error());
|
|
compile_time_ = compile_timer.Elapsed().InMillisecondsF();
|
|
|
|
compilation_info()->SetAsmWasmData(result);
|
|
|
|
RecordHistograms(isolate);
|
|
ReportCompilationSuccess(handle(Script::cast(shared_info->script()), isolate),
|
|
shared_info->StartPosition(), compile_time_,
|
|
module_->size());
|
|
return SUCCEEDED;
|
|
}
|
|
|
|
void AsmJsCompilationJob::RecordHistograms(Isolate* isolate) {
|
|
isolate->counters()->asm_module_size_bytes()->AddSample(module_source_size_);
|
|
}
|
|
|
|
std::unique_ptr<UnoptimizedCompilationJob> AsmJs::NewCompilationJob(
|
|
ParseInfo* parse_info, FunctionLiteral* literal,
|
|
AccountingAllocator* allocator) {
|
|
return std::make_unique<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 and flag-limited maximum allocation size.
|
|
if (size > wasm::max_initial_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<AsmWasmData> wasm_data,
|
|
Handle<JSReceiver> stdlib,
|
|
Handle<JSReceiver> foreign,
|
|
Handle<JSArrayBuffer> memory) {
|
|
base::ElapsedTimer instantiate_timer;
|
|
instantiate_timer.Start();
|
|
Handle<HeapNumber> uses_bitset(wasm_data->uses_bitset(), isolate);
|
|
Handle<Script> script(Script::cast(shared->script()), isolate);
|
|
const auto& wasm_engine = isolate->wasm_engine();
|
|
|
|
// Allocate the WasmModuleObject.
|
|
Handle<WasmModuleObject> module =
|
|
wasm_engine->FinalizeTranslatedAsmJs(isolate, wasm_data, script);
|
|
|
|
// TODO(asmjs): The position currently points to the module definition
|
|
// but should instead point to the instantiation site (more intuitive).
|
|
int position = shared->StartPosition();
|
|
|
|
// Check that the module is not instantiated as a generator or async function.
|
|
if (IsResumableFunction(shared->scope_info().function_kind())) {
|
|
ReportInstantiationFailure(script, position,
|
|
"Cannot be instantiated as resumable function");
|
|
return MaybeHandle<Object>();
|
|
}
|
|
|
|
// Check that all used stdlib members are valid.
|
|
bool stdlib_use_of_typed_array_present = false;
|
|
wasm::AsmJsParser::StdlibSet stdlib_uses =
|
|
wasm::AsmJsParser::StdlibSet::FromIntegral(uses_bitset->value_as_bits());
|
|
if (!stdlib_uses.empty()) { // 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>();
|
|
}
|
|
// AsmJs memory must be an ArrayBuffer.
|
|
if (memory->is_shared()) {
|
|
ReportInstantiationFailure(script, position,
|
|
"Invalid heap type: SharedArrayBuffer");
|
|
return MaybeHandle<Object>();
|
|
}
|
|
// Mark the buffer as being used as an asm.js memory. This implies two
|
|
// things: 1) if the buffer is from a Wasm memory, that memory can no longer
|
|
// be grown, since that would detach this buffer, and 2) the buffer cannot
|
|
// be postMessage()'d, as that also detaches the buffer.
|
|
memory->set_is_asmjs_memory(true);
|
|
memory->set_is_detachable(false);
|
|
size_t size = 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<WasmInstanceObject> maybe_instance =
|
|
wasm_engine->SyncInstantiate(isolate, &thrower, module, foreign, memory);
|
|
if (maybe_instance.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.begin());
|
|
} else {
|
|
ReportInstantiationFailure(script, position, "Internal wasm failure");
|
|
}
|
|
thrower.Reset(); // Ensure exceptions do not propagate.
|
|
return MaybeHandle<Object>();
|
|
}
|
|
DCHECK(!thrower.error());
|
|
Handle<WasmInstanceObject> instance = maybe_instance.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, instance, single_function_name);
|
|
if (!single_function.is_null() &&
|
|
!single_function.ToHandleChecked()->IsUndefined(isolate)) {
|
|
return single_function;
|
|
}
|
|
|
|
// Here we rely on the fact that the exports object is eagerly created.
|
|
// The following check is a weak indicator for that. If this ever changes,
|
|
// then we'll have to call the "exports" getter, and be careful about
|
|
// handling possible stack overflow exceptions.
|
|
DCHECK(instance->exports_object().IsJSObject());
|
|
return handle(instance->exports_object(), isolate);
|
|
}
|
|
|
|
} // namespace internal
|
|
} // namespace v8
|