V8 API Reference, 7.2.502.16 (for Deno 0.2.4)
asm-js.cc
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/counters.h"
15 #include "src/execution.h"
16 #include "src/handles.h"
17 #include "src/heap/factory.h"
18 #include "src/isolate.h"
19 #include "src/message-template.h"
20 #include "src/objects-inl.h"
21 #include "src/parsing/parse-info.h"
22 #include "src/parsing/scanner-character-streams.h"
23 #include "src/parsing/scanner.h"
24 #include "src/unoptimized-compilation-info.h"
25 
26 #include "src/wasm/wasm-engine.h"
27 #include "src/wasm/wasm-js.h"
28 #include "src/wasm/wasm-limits.h"
29 #include "src/wasm/wasm-module-builder.h"
30 #include "src/wasm/wasm-objects-inl.h"
31 #include "src/wasm/wasm-result.h"
32 
33 namespace v8 {
34 namespace internal {
35 
36 const char* const AsmJs::kSingleFunctionName = "__single_function__";
37 
38 namespace {
39 
40 Handle<Object> StdlibMathMember(Isolate* isolate, Handle<JSReceiver> stdlib,
41  Handle<Name> name) {
42  Handle<Name> math_name(
43  isolate->factory()->InternalizeOneByteString(STATIC_CHAR_VECTOR("Math")));
44  Handle<Object> math = JSReceiver::GetDataProperty(stdlib, math_name);
45  if (!math->IsJSReceiver()) return isolate->factory()->undefined_value();
46  Handle<JSReceiver> math_receiver = Handle<JSReceiver>::cast(math);
47  Handle<Object> value = JSReceiver::GetDataProperty(math_receiver, name);
48  return value;
49 }
50 
51 bool AreStdlibMembersValid(Isolate* isolate, Handle<JSReceiver> stdlib,
52  wasm::AsmJsParser::StdlibSet members,
53  bool* is_typed_array) {
54  if (members.Contains(wasm::AsmJsParser::StandardMember::kInfinity)) {
55  members.Remove(wasm::AsmJsParser::StandardMember::kInfinity);
56  Handle<Name> name = isolate->factory()->Infinity_string();
57  Handle<Object> value = JSReceiver::GetDataProperty(stdlib, name);
58  if (!value->IsNumber() || !std::isinf(value->Number())) return false;
59  }
60  if (members.Contains(wasm::AsmJsParser::StandardMember::kNaN)) {
61  members.Remove(wasm::AsmJsParser::StandardMember::kNaN);
62  Handle<Name> name = isolate->factory()->NaN_string();
63  Handle<Object> value = JSReceiver::GetDataProperty(stdlib, name);
64  if (!value->IsNaN()) return false;
65  }
66 #define STDLIB_MATH_FUNC(fname, FName, ignore1, ignore2) \
67  if (members.Contains(wasm::AsmJsParser::StandardMember::kMath##FName)) { \
68  members.Remove(wasm::AsmJsParser::StandardMember::kMath##FName); \
69  Handle<Name> name(isolate->factory()->InternalizeOneByteString( \
70  STATIC_CHAR_VECTOR(#fname))); \
71  Handle<Object> value = StdlibMathMember(isolate, stdlib, name); \
72  if (!value->IsJSFunction()) return false; \
73  SharedFunctionInfo* shared = Handle<JSFunction>::cast(value)->shared(); \
74  if (!shared->HasBuiltinId() || \
75  shared->builtin_id() != Builtins::kMath##FName) { \
76  return false; \
77  } \
78  DCHECK_EQ(shared->GetCode(), \
79  isolate->builtins()->builtin(Builtins::kMath##FName)); \
80  }
81  STDLIB_MATH_FUNCTION_LIST(STDLIB_MATH_FUNC)
82 #undef STDLIB_MATH_FUNC
83 #define STDLIB_MATH_CONST(cname, const_value) \
84  if (members.Contains(wasm::AsmJsParser::StandardMember::kMath##cname)) { \
85  members.Remove(wasm::AsmJsParser::StandardMember::kMath##cname); \
86  Handle<Name> name(isolate->factory()->InternalizeOneByteString( \
87  STATIC_CHAR_VECTOR(#cname))); \
88  Handle<Object> value = StdlibMathMember(isolate, stdlib, name); \
89  if (!value->IsNumber() || value->Number() != const_value) return false; \
90  }
91  STDLIB_MATH_VALUE_LIST(STDLIB_MATH_CONST)
92 #undef STDLIB_MATH_CONST
93 #define STDLIB_ARRAY_TYPE(fname, FName) \
94  if (members.Contains(wasm::AsmJsParser::StandardMember::k##FName)) { \
95  members.Remove(wasm::AsmJsParser::StandardMember::k##FName); \
96  *is_typed_array = true; \
97  Handle<Name> name(isolate->factory()->InternalizeOneByteString( \
98  STATIC_CHAR_VECTOR(#FName))); \
99  Handle<Object> value = JSReceiver::GetDataProperty(stdlib, name); \
100  if (!value->IsJSFunction()) return false; \
101  Handle<JSFunction> func = Handle<JSFunction>::cast(value); \
102  if (!func.is_identical_to(isolate->fname())) return false; \
103  }
104  STDLIB_ARRAY_TYPE(int8_array_fun, Int8Array)
105  STDLIB_ARRAY_TYPE(uint8_array_fun, Uint8Array)
106  STDLIB_ARRAY_TYPE(int16_array_fun, Int16Array)
107  STDLIB_ARRAY_TYPE(uint16_array_fun, Uint16Array)
108  STDLIB_ARRAY_TYPE(int32_array_fun, Int32Array)
109  STDLIB_ARRAY_TYPE(uint32_array_fun, Uint32Array)
110  STDLIB_ARRAY_TYPE(float32_array_fun, Float32Array)
111  STDLIB_ARRAY_TYPE(float64_array_fun, Float64Array)
112 #undef STDLIB_ARRAY_TYPE
113  // All members accounted for.
114  DCHECK(members.IsEmpty());
115  return true;
116 }
117 
118 void Report(Handle<Script> script, int position, Vector<const char> text,
119  MessageTemplate message_template,
120  v8::Isolate::MessageErrorLevel level) {
121  Isolate* isolate = script->GetIsolate();
122  MessageLocation location(script, position, position);
123  Handle<String> text_object = isolate->factory()->InternalizeUtf8String(text);
124  Handle<JSMessageObject> message = MessageHandler::MakeMessageObject(
125  isolate, message_template, &location, text_object,
126  Handle<FixedArray>::null());
127  message->set_error_level(level);
128  MessageHandler::ReportMessage(isolate, &location, message);
129 }
130 
131 // Hook to report successful execution of {AsmJs::CompileAsmViaWasm} phase.
132 void ReportCompilationSuccess(Handle<Script> script, int position,
133  double translate_time, double compile_time,
134  size_t module_size) {
135  if (FLAG_suppress_asm_messages || !FLAG_trace_asm_time) return;
136  EmbeddedVector<char, 100> text;
137  int length = SNPrintF(
138  text, "success, asm->wasm: %0.3f ms, compile: %0.3f ms, %" PRIuS " bytes",
139  translate_time, compile_time, module_size);
140  CHECK_NE(-1, length);
141  text.Truncate(length);
142  Report(script, position, text, MessageTemplate::kAsmJsCompiled,
143  v8::Isolate::kMessageInfo);
144 }
145 
146 // Hook to report failed execution of {AsmJs::CompileAsmViaWasm} phase.
147 void ReportCompilationFailure(ParseInfo* parse_info, int position,
148  const char* reason) {
149  if (FLAG_suppress_asm_messages) return;
150  parse_info->pending_error_handler()->ReportWarningAt(
151  position, position, MessageTemplate::kAsmJsInvalid, reason);
152 }
153 
154 // Hook to report successful execution of {AsmJs::InstantiateAsmWasm} phase.
155 void ReportInstantiationSuccess(Handle<Script> script, int position,
156  double instantiate_time) {
157  if (FLAG_suppress_asm_messages || !FLAG_trace_asm_time) return;
158  EmbeddedVector<char, 50> text;
159  int length = SNPrintF(text, "success, %0.3f ms", instantiate_time);
160  CHECK_NE(-1, length);
161  text.Truncate(length);
162  Report(script, position, text, MessageTemplate::kAsmJsInstantiated,
163  v8::Isolate::kMessageInfo);
164 }
165 
166 // Hook to report failed execution of {AsmJs::InstantiateAsmWasm} phase.
167 void ReportInstantiationFailure(Handle<Script> script, int position,
168  const char* reason) {
169  if (FLAG_suppress_asm_messages) return;
170  Vector<const char> text = CStrVector(reason);
171  Report(script, position, text, MessageTemplate::kAsmJsLinkingFailed,
172  v8::Isolate::kMessageWarning);
173 }
174 
175 } // namespace
176 
177 // The compilation of asm.js modules is split into two distinct steps:
178 // [1] ExecuteJobImpl: The asm.js module source is parsed, validated, and
179 // translated to a valid WebAssembly module. The result are two vectors
180 // representing the encoded module as well as encoded source position
181 // information and a StdlibSet bit set.
182 // [2] FinalizeJobImpl: The module is handed to WebAssembly which decodes it
183 // into an internal representation and eventually compiles it to machine
184 // code.
186  public:
187  explicit AsmJsCompilationJob(ParseInfo* parse_info, FunctionLiteral* literal,
188  AccountingAllocator* allocator)
189  : UnoptimizedCompilationJob(parse_info->stack_limit(), parse_info,
190  &compilation_info_),
191  allocator_(allocator),
192  zone_(allocator, ZONE_NAME),
193  compilation_info_(&zone_, parse_info, literal),
194  module_(nullptr),
195  asm_offsets_(nullptr),
196  translate_time_(0),
197  compile_time_(0),
198  module_source_size_(0),
199  translate_time_micro_(0),
200  translate_zone_size_(0) {}
201 
202  protected:
203  Status ExecuteJobImpl() final;
204  Status FinalizeJobImpl(Handle<SharedFunctionInfo> shared_info,
205  Isolate* isolate) final;
206 
207  private:
208  void RecordHistograms(Isolate* isolate);
209 
210  AccountingAllocator* allocator_;
211  Zone zone_;
212  UnoptimizedCompilationInfo compilation_info_;
213  wasm::ZoneBuffer* module_;
214  wasm::ZoneBuffer* asm_offsets_;
215  wasm::AsmJsParser::StdlibSet stdlib_uses_;
216 
217  double translate_time_; // Time (milliseconds) taken to execute step [1].
218  double compile_time_; // Time (milliseconds) taken to execute step [2].
219  int module_source_size_; // Module source size in bytes.
220  int64_t translate_time_micro_; // Time (microseconds) taken to translate.
221  size_t translate_zone_size_;
222 
223  DISALLOW_COPY_AND_ASSIGN(AsmJsCompilationJob);
224 };
225 
226 UnoptimizedCompilationJob::Status AsmJsCompilationJob::ExecuteJobImpl() {
227  // Step 1: Translate asm.js module to WebAssembly module.
228  size_t compile_zone_start = compilation_info()->zone()->allocation_size();
229  base::ElapsedTimer translate_timer;
230  translate_timer.Start();
231 
232  Zone* compile_zone = compilation_info()->zone();
233  Zone translate_zone(allocator_, ZONE_NAME);
234 
235  Utf16CharacterStream* stream = parse_info()->character_stream();
237  if (stream->can_access_heap()) {
238  allow_deref.emplace();
239  }
240  stream->Seek(compilation_info()->literal()->start_position());
241  wasm::AsmJsParser parser(&translate_zone, stack_limit(), stream);
242  if (!parser.Run()) {
243  if (!FLAG_suppress_asm_messages) {
244  ReportCompilationFailure(parse_info(), parser.failure_location(),
245  parser.failure_message());
246  }
247  return FAILED;
248  }
249  module_ = new (compile_zone) wasm::ZoneBuffer(compile_zone);
250  parser.module_builder()->WriteTo(*module_);
251  asm_offsets_ = new (compile_zone) wasm::ZoneBuffer(compile_zone);
252  parser.module_builder()->WriteAsmJsOffsetTable(*asm_offsets_);
253  stdlib_uses_ = *parser.stdlib_uses();
254 
255  size_t compile_zone_size =
256  compilation_info()->zone()->allocation_size() - compile_zone_start;
257  translate_zone_size_ = translate_zone.allocation_size();
258  translate_time_ = translate_timer.Elapsed().InMillisecondsF();
259  translate_time_micro_ = translate_timer.Elapsed().InMicroseconds();
260  module_source_size_ = compilation_info()->literal()->end_position() -
261  compilation_info()->literal()->start_position();
262  if (FLAG_trace_asm_parser) {
263  PrintF(
264  "[asm.js translation successful: time=%0.3fms, "
265  "translate_zone=%" PRIuS "KB, compile_zone+=%" PRIuS "KB]\n",
266  translate_time_, translate_zone_size_ / KB, compile_zone_size / KB);
267  }
268  return SUCCEEDED;
269 }
270 
271 UnoptimizedCompilationJob::Status AsmJsCompilationJob::FinalizeJobImpl(
272  Handle<SharedFunctionInfo> shared_info, Isolate* isolate) {
273  // Step 2: Compile and decode the WebAssembly module.
274  base::ElapsedTimer compile_timer;
275  compile_timer.Start();
276 
277  Handle<HeapNumber> uses_bitset =
278  isolate->factory()->NewHeapNumberFromBits(stdlib_uses_.ToIntegral());
279 
280  // The result is a compiled module and serialized standard library uses.
281  wasm::ErrorThrower thrower(isolate, "AsmJs::Compile");
282  Handle<AsmWasmData> result =
283  isolate->wasm_engine()
284  ->SyncCompileTranslatedAsmJs(
285  isolate, &thrower,
286  wasm::ModuleWireBytes(module_->begin(), module_->end()),
287  Vector<const byte>(asm_offsets_->begin(), asm_offsets_->size()),
288  uses_bitset)
289  .ToHandleChecked();
290  DCHECK(!thrower.error());
291  compile_time_ = compile_timer.Elapsed().InMillisecondsF();
292 
293  compilation_info()->SetAsmWasmData(result);
294 
295  RecordHistograms(isolate);
296  ReportCompilationSuccess(parse_info()->script(),
297  compilation_info()->literal()->position(),
298  translate_time_, compile_time_, module_->size());
299  return SUCCEEDED;
300 }
301 
302 void AsmJsCompilationJob::RecordHistograms(Isolate* isolate) {
303  Counters* counters = isolate->counters();
304  counters->asm_wasm_translation_time()->AddSample(
305  static_cast<int>(translate_time_micro_));
306  counters->asm_wasm_translation_peak_memory_bytes()->AddSample(
307  static_cast<int>(translate_zone_size_));
308  counters->asm_module_size_bytes()->AddSample(module_source_size_);
309  // translation_throughput is not exact (assumes MB == 1000000). But that is ok
310  // since the metric is stored in buckets that lose some precision anyways.
311  int translation_throughput =
312  translate_time_micro_ != 0
313  ? static_cast<int>(static_cast<int64_t>(module_source_size_) /
314  translate_time_micro_)
315  : 0;
316  counters->asm_wasm_translation_throughput()->AddSample(
317  translation_throughput);
318 }
319 
320 UnoptimizedCompilationJob* AsmJs::NewCompilationJob(
321  ParseInfo* parse_info, FunctionLiteral* literal,
322  AccountingAllocator* allocator) {
323  return new AsmJsCompilationJob(parse_info, literal, allocator);
324 }
325 
326 namespace {
327 inline bool IsValidAsmjsMemorySize(size_t size) {
328  // Enforce asm.js spec minimum size.
329  if (size < (1u << 12u)) return false;
330  // Enforce engine-limited and flag-limited maximum allocation size.
331  if (size > wasm::max_mem_pages() * uint64_t{wasm::kWasmPageSize}) {
332  return false;
333  }
334  // Enforce power-of-2 sizes for 2^12 - 2^24.
335  if (size < (1u << 24u)) {
336  uint32_t size32 = static_cast<uint32_t>(size);
337  return base::bits::IsPowerOfTwo(size32);
338  }
339  // Enforce multiple of 2^24 for sizes >= 2^24
340  if ((size % (1u << 24u)) != 0) return false;
341  // All checks passed!
342  return true;
343 }
344 } // namespace
345 
346 MaybeHandle<Object> AsmJs::InstantiateAsmWasm(Isolate* isolate,
347  Handle<SharedFunctionInfo> shared,
348  Handle<AsmWasmData> wasm_data,
349  Handle<JSReceiver> stdlib,
350  Handle<JSReceiver> foreign,
351  Handle<JSArrayBuffer> memory) {
352  base::ElapsedTimer instantiate_timer;
353  instantiate_timer.Start();
354  Handle<HeapNumber> uses_bitset(wasm_data->uses_bitset(), isolate);
355  Handle<Script> script(Script::cast(shared->script()), isolate);
356 
357  // Allocate the WasmModuleObject.
358  Handle<WasmModuleObject> module =
359  isolate->wasm_engine()->FinalizeTranslatedAsmJs(isolate, wasm_data,
360  script);
361 
362  // TODO(mstarzinger): The position currently points to the module definition
363  // but should instead point to the instantiation site (more intuitive).
364  int position = shared->StartPosition();
365 
366  // Check that all used stdlib members are valid.
367  bool stdlib_use_of_typed_array_present = false;
368  wasm::AsmJsParser::StdlibSet stdlib_uses(uses_bitset->value_as_bits());
369  if (!stdlib_uses.IsEmpty()) { // No checking needed if no uses.
370  if (stdlib.is_null()) {
371  ReportInstantiationFailure(script, position, "Requires standard library");
372  return MaybeHandle<Object>();
373  }
374  if (!AreStdlibMembersValid(isolate, stdlib, stdlib_uses,
375  &stdlib_use_of_typed_array_present)) {
376  ReportInstantiationFailure(script, position, "Unexpected stdlib member");
377  return MaybeHandle<Object>();
378  }
379  }
380 
381  // Check that a valid heap buffer is provided if required.
382  if (stdlib_use_of_typed_array_present) {
383  if (memory.is_null()) {
384  ReportInstantiationFailure(script, position, "Requires heap buffer");
385  return MaybeHandle<Object>();
386  }
387  memory->set_is_growable(false);
388  size_t size = memory->byte_length();
389  // Check the asm.js heap size against the valid limits.
390  if (!IsValidAsmjsMemorySize(size)) {
391  ReportInstantiationFailure(script, position, "Invalid heap size");
392  return MaybeHandle<Object>();
393  }
394  } else {
395  memory = Handle<JSArrayBuffer>::null();
396  }
397 
398  wasm::ErrorThrower thrower(isolate, "AsmJs::Instantiate");
399  MaybeHandle<Object> maybe_module_object =
400  isolate->wasm_engine()->SyncInstantiate(isolate, &thrower, module,
401  foreign, memory);
402  if (maybe_module_object.is_null()) {
403  // An exception caused by the module start function will be set as pending
404  // and bypass the {ErrorThrower}, this happens in case of a stack overflow.
405  if (isolate->has_pending_exception()) isolate->clear_pending_exception();
406  if (thrower.error()) {
407  ScopedVector<char> error_reason(100);
408  SNPrintF(error_reason, "Internal wasm failure: %s", thrower.error_msg());
409  ReportInstantiationFailure(script, position, error_reason.start());
410  } else {
411  ReportInstantiationFailure(script, position, "Internal wasm failure");
412  }
413  thrower.Reset(); // Ensure exceptions do not propagate.
414  return MaybeHandle<Object>();
415  }
416  DCHECK(!thrower.error());
417  Handle<Object> module_object = maybe_module_object.ToHandleChecked();
418 
419  ReportInstantiationSuccess(script, position,
420  instantiate_timer.Elapsed().InMillisecondsF());
421 
422  Handle<Name> single_function_name(
423  isolate->factory()->InternalizeUtf8String(AsmJs::kSingleFunctionName));
424  MaybeHandle<Object> single_function =
425  Object::GetProperty(isolate, module_object, single_function_name);
426  if (!single_function.is_null() &&
427  !single_function.ToHandleChecked()->IsUndefined(isolate)) {
428  return single_function;
429  }
430 
431  Handle<String> exports_name =
432  isolate->factory()->InternalizeUtf8String("exports");
433  return Object::GetProperty(isolate, module_object, exports_name);
434 }
435 
436 } // namespace internal
437 } // namespace v8
Definition: libplatform.h:13