• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
StdlibMathMember(Isolate * isolate,Handle<JSReceiver> stdlib,Handle<Name> name)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 
AreStdlibMembersValid(Isolate * isolate,Handle<JSReceiver> stdlib,wasm::AsmJsParser::StdlibSet members,bool * is_typed_array)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 
Report(Handle<Script> script,int position,Vector<const char> text,MessageTemplate::Template message_template,v8::Isolate::MessageErrorLevel level)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.
ReportCompilationSuccess(Handle<Script> script,int position,double translate_time,double compile_time,size_t module_size)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.
ReportCompilationFailure(ParseInfo * parse_info,int position,const char * reason)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.
ReportInstantiationSuccess(Handle<Script> script,int position,double instantiate_time)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.
ReportInstantiationFailure(Handle<Script> script,int position,const char * reason)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:
AsmJsCompilationJob(ParseInfo * parse_info,FunctionLiteral * literal,AccountingAllocator * allocator)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 
ExecuteJobImpl()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 
FinalizeJobImpl(Handle<SharedFunctionInfo> shared_info,Isolate * isolate)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 
RecordHistograms(Isolate * isolate)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 
NewCompilationJob(ParseInfo * parse_info,FunctionLiteral * literal,AccountingAllocator * allocator)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 {
IsValidAsmjsMemorySize(size_t size)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 
InstantiateAsmWasm(Isolate * isolate,Handle<SharedFunctionInfo> shared,Handle<FixedArray> wasm_data,Handle<JSReceiver> stdlib,Handle<JSReceiver> foreign,Handle<JSArrayBuffer> memory)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