Home | History | Annotate | Download | only in asmjs
      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