asm-js.cc 18.4 KB
Newer Older
1 2 3 4 5 6
// 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"

7
#include "src/asmjs/asm-names.h"
8
#include "src/asmjs/asm-parser.h"
9
#include "src/ast/ast.h"
10
#include "src/base/optional.h"
11
#include "src/base/platform/elapsed-timer.h"
12
#include "src/base/vector.h"
13 14
#include "src/codegen/compiler.h"
#include "src/codegen/unoptimized-compilation-info.h"
15
#include "src/common/assert-scope.h"
16
#include "src/common/message-template.h"
17 18
#include "src/execution/execution.h"
#include "src/execution/isolate.h"
19
#include "src/handles/handles.h"
20
#include "src/heap/factory.h"
21
#include "src/logging/counters.h"
22
#include "src/objects/heap-number-inl.h"
23
#include "src/objects/objects-inl.h"
24
#include "src/parsing/parse-info.h"
25 26
#include "src/parsing/scanner-character-streams.h"
#include "src/parsing/scanner.h"
27
#include "src/wasm/wasm-engine.h"
28
#include "src/wasm/wasm-js.h"
29
#include "src/wasm/wasm-limits.h"
30
#include "src/wasm/wasm-module-builder.h"
31
#include "src/wasm/wasm-objects-inl.h"
32 33 34 35 36
#include "src/wasm/wasm-result.h"

namespace v8 {
namespace internal {

37 38
const char* const AsmJs::kSingleFunctionName = "__single_function__";

39
namespace {
40

41 42 43
Handle<Object> StdlibMathMember(Isolate* isolate, Handle<JSReceiver> stdlib,
                                Handle<Name> name) {
  Handle<Name> math_name(
44
      isolate->factory()->InternalizeString(base::StaticCharVector("Math")));
45
  Handle<Object> math = JSReceiver::GetDataProperty(isolate, stdlib, math_name);
46 47
  if (!math->IsJSReceiver()) return isolate->factory()->undefined_value();
  Handle<JSReceiver> math_receiver = Handle<JSReceiver>::cast(math);
48 49
  Handle<Object> value =
      JSReceiver::GetDataProperty(isolate, math_receiver, name);
50
  return value;
51 52
}

53 54 55
bool AreStdlibMembersValid(Isolate* isolate, Handle<JSReceiver> stdlib,
                           wasm::AsmJsParser::StdlibSet members,
                           bool* is_typed_array) {
56
  if (members.contains(wasm::AsmJsParser::StandardMember::kInfinity)) {
57 58
    members.Remove(wasm::AsmJsParser::StandardMember::kInfinity);
    Handle<Name> name = isolate->factory()->Infinity_string();
59
    Handle<Object> value = JSReceiver::GetDataProperty(isolate, stdlib, name);
60 61
    if (!value->IsNumber() || !std::isinf(value->Number())) return false;
  }
62
  if (members.contains(wasm::AsmJsParser::StandardMember::kNaN)) {
63 64
    members.Remove(wasm::AsmJsParser::StandardMember::kNaN);
    Handle<Name> name = isolate->factory()->NaN_string();
65
    Handle<Object> value = JSReceiver::GetDataProperty(isolate, stdlib, name);
66
    if (!value->IsNaN()) return false;
67
  }
68
#define STDLIB_MATH_FUNC(fname, FName, ignore1, ignore2)                   \
69
  if (members.contains(wasm::AsmJsParser::StandardMember::kMath##FName)) { \
70
    members.Remove(wasm::AsmJsParser::StandardMember::kMath##FName);       \
71 72
    Handle<Name> name(isolate->factory()->InternalizeString(               \
        base::StaticCharVector(#fname)));                                  \
73 74 75
    Handle<Object> value = StdlibMathMember(isolate, stdlib, name);        \
    if (!value->IsJSFunction()) return false;                              \
    SharedFunctionInfo shared = Handle<JSFunction>::cast(value)->shared(); \
76
    if (!shared.HasBuiltinId() ||                                          \
77
        shared.builtin_id() != Builtin::kMath##FName) {                    \
78 79
      return false;                                                        \
    }                                                                      \
80
    DCHECK_EQ(shared.GetCode(),                                            \
81
              isolate->builtins()->code(Builtin::kMath##FName));           \
82 83
  }
  STDLIB_MATH_FUNCTION_LIST(STDLIB_MATH_FUNC)
84
#undef STDLIB_MATH_FUNC
85
#define STDLIB_MATH_CONST(cname, const_value)                               \
86
  if (members.contains(wasm::AsmJsParser::StandardMember::kMath##cname)) {  \
87
    members.Remove(wasm::AsmJsParser::StandardMember::kMath##cname);        \
88 89
    Handle<Name> name(isolate->factory()->InternalizeString(                \
        base::StaticCharVector(#cname)));                                   \
90 91
    Handle<Object> value = StdlibMathMember(isolate, stdlib, name);         \
    if (!value->IsNumber() || value->Number() != const_value) return false; \
92
  }
93
  STDLIB_MATH_VALUE_LIST(STDLIB_MATH_CONST)
94
#undef STDLIB_MATH_CONST
95 96 97 98 99 100 101 102 103 104
#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(                   \
        base::StaticCharVector(#FName)));                                      \
    Handle<Object> value = JSReceiver::GetDataProperty(isolate, stdlib, name); \
    if (!value->IsJSFunction()) return false;                                  \
    Handle<JSFunction> func = Handle<JSFunction>::cast(value);                 \
    if (!func.is_identical_to(isolate->fname())) return false;                 \
105
  }
106 107 108 109 110 111 112 113
  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)
114
#undef STDLIB_ARRAY_TYPE
115
  // All members accounted for.
116
  DCHECK(members.empty());
117
  return true;
118 119
}

120
void Report(Handle<Script> script, int position, base::Vector<const char> text,
121
            MessageTemplate message_template,
122 123 124 125 126 127 128 129 130 131 132 133 134
            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,
135
                              double compile_time, size_t module_size) {
136
  if (FLAG_suppress_asm_messages || !FLAG_trace_asm_time) return;
137
  base::EmbeddedVector<char, 100> text;
138 139
  int length = SNPrintF(text, "success, compile time %0.3f ms, %zu bytes",
                        compile_time, module_size);
140 141 142 143 144 145 146
  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.
147
void ReportCompilationFailure(ParseInfo* parse_info, int position,
148 149
                              const char* reason) {
  if (FLAG_suppress_asm_messages) return;
150 151
  parse_info->pending_error_handler()->ReportWarningAt(
      position, position, MessageTemplate::kAsmJsInvalid, reason);
152 153 154 155 156 157
}

// 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;
158
  base::EmbeddedVector<char, 50> text;
159
  int length = SNPrintF(text, "success, %0.3f ms", instantiate_time);
160 161 162 163 164 165
  CHECK_NE(-1, length);
  text.Truncate(length);
  Report(script, position, text, MessageTemplate::kAsmJsInstantiated,
         v8::Isolate::kMessageInfo);
}

166 167 168 169
// 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;
170
  base::Vector<const char> text = base::CStrVector(reason);
171 172 173 174
  Report(script, position, text, MessageTemplate::kAsmJsLinkingFailed,
         v8::Isolate::kMessageWarning);
}

175 176
}  // namespace

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 180
//      translated to a valid WebAssembly module. The result are two vectors
//      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 184
//      into an internal representation and eventually compiles it to machine
//      code.
185
class AsmJsCompilationJob final : public UnoptimizedCompilationJob {
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 192 193
        allocator_(allocator),
        zone_(allocator, ZONE_NAME),
        compilation_info_(&zone_, parse_info, literal),
194 195
        module_(nullptr),
        asm_offsets_(nullptr),
196
        compile_time_(0),
197
        module_source_size_(0) {}
198

199 200 201
  AsmJsCompilationJob(const AsmJsCompilationJob&) = delete;
  AsmJsCompilationJob& operator=(const AsmJsCompilationJob&) = delete;

202 203
 protected:
  Status ExecuteJobImpl() final;
204 205
  Status FinalizeJobImpl(Handle<SharedFunctionInfo> shared_info,
                         Isolate* isolate) final;
206
  Status FinalizeJobImpl(Handle<SharedFunctionInfo> shared_info,
207
                         LocalIsolate* isolate) final {
208
    return CompilationJob::RETRY_ON_MAIN_THREAD;
209
  }
210

211
 private:
212 213 214
  void RecordHistograms(Isolate* isolate);

  AccountingAllocator* allocator_;
215
  Zone zone_;
216
  UnoptimizedCompilationInfo compilation_info_;
217 218 219 220
  wasm::ZoneBuffer* module_;
  wasm::ZoneBuffer* asm_offsets_;
  wasm::AsmJsParser::StdlibSet stdlib_uses_;

221 222
  double compile_time_;     // Time (milliseconds) taken to execute step [2].
  int module_source_size_;  // Module source size in bytes.
223 224
};

225
UnoptimizedCompilationJob::Status AsmJsCompilationJob::ExecuteJobImpl() {
226 227
  DisallowHeapAccess no_heap_access;

228
  // Step 1: Translate asm.js module to WebAssembly module.
229
  Zone* compile_zone = &zone_;
230
  Zone translate_zone(allocator_, ZONE_NAME);
231

232
  Utf16CharacterStream* stream = parse_info()->character_stream();
233 234 235 236
  base::Optional<AllowHandleDereference> allow_deref;
  if (stream->can_access_heap()) {
    allow_deref.emplace();
  }
237 238
  stream->Seek(compilation_info()->literal()->start_position());
  wasm::AsmJsParser parser(&translate_zone, stack_limit(), stream);
239
  if (!parser.Run()) {
240 241 242 243
    if (!FLAG_suppress_asm_messages) {
      ReportCompilationFailure(parse_info(), parser.failure_location(),
                               parser.failure_message());
    }
244 245
    return FAILED;
  }
246
  module_ = compile_zone->New<wasm::ZoneBuffer>(compile_zone);
247
  parser.module_builder()->WriteTo(module_);
248
  asm_offsets_ = compile_zone->New<wasm::ZoneBuffer>(compile_zone);
249
  parser.module_builder()->WriteAsmJsOffsetTable(asm_offsets_);
250
  stdlib_uses_ = *parser.stdlib_uses();
251

252 253
  module_source_size_ = compilation_info()->literal()->end_position() -
                        compilation_info()->literal()->start_position();
254 255
  return SUCCEEDED;
}
256

257 258
UnoptimizedCompilationJob::Status AsmJsCompilationJob::FinalizeJobImpl(
    Handle<SharedFunctionInfo> shared_info, Isolate* isolate) {
259
  // Step 2: Compile and decode the WebAssembly module.
260 261 262
  base::ElapsedTimer compile_timer;
  compile_timer.Start();

263
  Handle<HeapNumber> uses_bitset =
264
      isolate->factory()->NewHeapNumberFromBits(stdlib_uses_.ToIntegral());
265

266
  // The result is a compiled module and serialized standard library uses.
267
  wasm::ErrorThrower thrower(isolate, "AsmJs::Compile");
268
  Handle<AsmWasmData> result =
269
      wasm::GetWasmEngine()
270 271 272
          ->SyncCompileTranslatedAsmJs(
              isolate, &thrower,
              wasm::ModuleWireBytes(module_->begin(), module_->end()),
273
              base::VectorOf(*asm_offsets_), uses_bitset,
274
              shared_info->language_mode())
275 276 277 278
          .ToHandleChecked();
  DCHECK(!thrower.error());
  compile_time_ = compile_timer.Elapsed().InMillisecondsF();

279
  compilation_info()->SetAsmWasmData(result);
280

281
  RecordHistograms(isolate);
282
  ReportCompilationSuccess(handle(Script::cast(shared_info->script()), isolate),
283 284
                           shared_info->StartPosition(), compile_time_,
                           module_->size());
285 286 287
  return SUCCEEDED;
}

288
void AsmJsCompilationJob::RecordHistograms(Isolate* isolate) {
289
  isolate->counters()->asm_module_size_bytes()->AddSample(module_source_size_);
290 291
}

292
std::unique_ptr<UnoptimizedCompilationJob> AsmJs::NewCompilationJob(
293 294
    ParseInfo* parse_info, FunctionLiteral* literal,
    AccountingAllocator* allocator) {
295
  return std::make_unique<AsmJsCompilationJob>(parse_info, literal, allocator);
296 297
}

298 299 300 301
namespace {
inline bool IsValidAsmjsMemorySize(size_t size) {
  // Enforce asm.js spec minimum size.
  if (size < (1u << 12u)) return false;
302
  // Enforce engine-limited and flag-limited maximum allocation size.
303
  if (size > wasm::max_mem32_bytes()) return false;
304 305 306 307 308 309 310 311 312 313 314 315
  // 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

316
MaybeHandle<Object> AsmJs::InstantiateAsmWasm(Isolate* isolate,
317
                                              Handle<SharedFunctionInfo> shared,
318
                                              Handle<AsmWasmData> wasm_data,
319 320 321
                                              Handle<JSReceiver> stdlib,
                                              Handle<JSReceiver> foreign,
                                              Handle<JSArrayBuffer> memory) {
322 323
  base::ElapsedTimer instantiate_timer;
  instantiate_timer.Start();
324
  Handle<HeapNumber> uses_bitset(wasm_data->uses_bitset(), isolate);
325
  Handle<Script> script(Script::cast(shared->script()), isolate);
326
  auto* wasm_engine = wasm::GetWasmEngine();
327 328 329

  // Allocate the WasmModuleObject.
  Handle<WasmModuleObject> module =
330
      wasm_engine->FinalizeTranslatedAsmJs(isolate, wasm_data, script);
331

332
  // TODO(asmjs): The position currently points to the module definition
333
  // but should instead point to the instantiation site (more intuitive).
334
  int position = shared->StartPosition();
335

336 337 338 339 340 341 342
  // 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>();
  }

343
  // Check that all used stdlib members are valid.
344
  bool stdlib_use_of_typed_array_present = false;
345
  wasm::AsmJsParser::StdlibSet stdlib_uses =
346 347
      wasm::AsmJsParser::StdlibSet::FromIntegral(
          uses_bitset->value_as_bits(kRelaxedLoad));
348
  if (!stdlib_uses.empty()) {  // No checking needed if no uses.
349 350 351 352
    if (stdlib.is_null()) {
      ReportInstantiationFailure(script, position, "Requires standard library");
      return MaybeHandle<Object>();
    }
353 354
    if (!AreStdlibMembersValid(isolate, stdlib, stdlib_uses,
                               &stdlib_use_of_typed_array_present)) {
355
      ReportInstantiationFailure(script, position, "Unexpected stdlib member");
356 357 358 359
      return MaybeHandle<Object>();
    }
  }

360 361
  // Check that a valid heap buffer is provided if required.
  if (stdlib_use_of_typed_array_present) {
362 363 364 365
    if (memory.is_null()) {
      ReportInstantiationFailure(script, position, "Requires heap buffer");
      return MaybeHandle<Object>();
    }
366 367 368 369 370 371
    // AsmJs memory must be an ArrayBuffer.
    if (memory->is_shared()) {
      ReportInstantiationFailure(script, position,
                                 "Invalid heap type: SharedArrayBuffer");
      return MaybeHandle<Object>();
    }
372 373 374 375 376 377
    // 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);
378
    size_t size = memory->byte_length();
379 380 381
    // Check the asm.js heap size against the valid limits.
    if (!IsValidAsmjsMemorySize(size)) {
      ReportInstantiationFailure(script, position, "Invalid heap size");
382 383 384 385
      return MaybeHandle<Object>();
    }
  } else {
    memory = Handle<JSArrayBuffer>::null();
386 387
  }

388
  wasm::ErrorThrower thrower(isolate, "AsmJs::Instantiate");
389
  MaybeHandle<WasmInstanceObject> maybe_instance =
390
      wasm_engine->SyncInstantiate(isolate, &thrower, module, foreign, memory);
391
  if (maybe_instance.is_null()) {
392 393 394
    // 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();
395
    if (thrower.error()) {
396
      base::ScopedVector<char> error_reason(100);
397
      SNPrintF(error_reason, "Internal wasm failure: %s", thrower.error_msg());
398
      ReportInstantiationFailure(script, position, error_reason.begin());
399 400 401
    } else {
      ReportInstantiationFailure(script, position, "Internal wasm failure");
    }
402
    thrower.Reset();  // Ensure exceptions do not propagate.
403 404
    return MaybeHandle<Object>();
  }
405
  DCHECK(!thrower.error());
406
  Handle<WasmInstanceObject> instance = maybe_instance.ToHandleChecked();
407

408 409 410
  ReportInstantiationSuccess(script, position,
                             instantiate_timer.Elapsed().InMillisecondsF());

411 412
  Handle<Name> single_function_name(
      isolate->factory()->InternalizeUtf8String(AsmJs::kSingleFunctionName));
413
  MaybeHandle<Object> single_function =
414
      Object::GetProperty(isolate, instance, single_function_name);
415 416 417 418
  if (!single_function.is_null() &&
      !single_function.ToHandleChecked()->IsUndefined(isolate)) {
    return single_function;
  }
419

420 421 422 423 424 425
  // 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);
426 427 428 429
}

}  // namespace internal
}  // namespace v8