blob: 2553ecb91927ba5e6d198a47bbbf976a1c5ffb5c [file] [log] [blame]
// 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,
LocalIsolate* 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_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