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/ast/ast.h"
10 #include "src/base/optional.h"
11 #include "src/base/platform/elapsed-timer.h"
12 #include "src/codegen/compiler.h"
13 #include "src/codegen/unoptimized-compilation-info.h"
14 #include "src/common/assert-scope.h"
15 #include "src/common/message-template.h"
16 #include "src/execution/execution.h"
17 #include "src/execution/isolate.h"
18 #include "src/handles/handles.h"
19 #include "src/heap/factory.h"
20 #include "src/logging/counters.h"
21 #include "src/objects/heap-number-inl.h"
22 #include "src/objects/objects-inl.h"
23 #include "src/parsing/parse-info.h"
24 #include "src/parsing/scanner-character-streams.h"
25 #include "src/parsing/scanner.h"
26 #include "src/utils/vector.h"
27
28 #include "src/wasm/wasm-engine.h"
29 #include "src/wasm/wasm-js.h"
30 #include "src/wasm/wasm-limits.h"
31 #include "src/wasm/wasm-module-builder.h"
32 #include "src/wasm/wasm-objects-inl.h"
33 #include "src/wasm/wasm-result.h"
34
35 namespace v8 {
36 namespace internal {
37
38 const char* const AsmJs::kSingleFunctionName = "__single_function__";
39
40 namespace {
41
StdlibMathMember(Isolate * isolate,Handle<JSReceiver> stdlib,Handle<Name> name)42 Handle<Object> StdlibMathMember(Isolate* isolate, Handle<JSReceiver> stdlib,
43 Handle<Name> name) {
44 Handle<Name> math_name(
45 isolate->factory()->InternalizeString(StaticCharVector("Math")));
46 Handle<Object> math = JSReceiver::GetDataProperty(stdlib, math_name);
47 if (!math->IsJSReceiver()) return isolate->factory()->undefined_value();
48 Handle<JSReceiver> math_receiver = Handle<JSReceiver>::cast(math);
49 Handle<Object> value = JSReceiver::GetDataProperty(math_receiver, name);
50 return value;
51 }
52
AreStdlibMembersValid(Isolate * isolate,Handle<JSReceiver> stdlib,wasm::AsmJsParser::StdlibSet members,bool * is_typed_array)53 bool AreStdlibMembersValid(Isolate* isolate, Handle<JSReceiver> stdlib,
54 wasm::AsmJsParser::StdlibSet members,
55 bool* is_typed_array) {
56 if (members.contains(wasm::AsmJsParser::StandardMember::kInfinity)) {
57 members.Remove(wasm::AsmJsParser::StandardMember::kInfinity);
58 Handle<Name> name = isolate->factory()->Infinity_string();
59 Handle<Object> value = JSReceiver::GetDataProperty(stdlib, name);
60 if (!value->IsNumber() || !std::isinf(value->Number())) return false;
61 }
62 if (members.contains(wasm::AsmJsParser::StandardMember::kNaN)) {
63 members.Remove(wasm::AsmJsParser::StandardMember::kNaN);
64 Handle<Name> name = isolate->factory()->NaN_string();
65 Handle<Object> value = JSReceiver::GetDataProperty(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 Handle<Name> name( \
72 isolate->factory()->InternalizeString(StaticCharVector(#fname))); \
73 Handle<Object> value = StdlibMathMember(isolate, stdlib, name); \
74 if (!value->IsJSFunction()) return false; \
75 SharedFunctionInfo shared = Handle<JSFunction>::cast(value)->shared(); \
76 if (!shared.HasBuiltinId() || \
77 shared.builtin_id() != Builtins::kMath##FName) { \
78 return false; \
79 } \
80 DCHECK_EQ(shared.GetCode(), \
81 isolate->builtins()->builtin(Builtins::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 Handle<Name> name( \
89 isolate->factory()->InternalizeString(StaticCharVector(#cname))); \
90 Handle<Object> value = StdlibMathMember(isolate, stdlib, name); \
91 if (!value->IsNumber() || value->Number() != const_value) return false; \
92 }
93 STDLIB_MATH_VALUE_LIST(STDLIB_MATH_CONST)
94 #undef STDLIB_MATH_CONST
95 #define STDLIB_ARRAY_TYPE(fname, FName) \
96 if (members.contains(wasm::AsmJsParser::StandardMember::k##FName)) { \
97 members.Remove(wasm::AsmJsParser::StandardMember::k##FName); \
98 *is_typed_array = true; \
99 Handle<Name> name( \
100 isolate->factory()->InternalizeString(StaticCharVector(#FName))); \
101 Handle<Object> value = JSReceiver::GetDataProperty(stdlib, name); \
102 if (!value->IsJSFunction()) return false; \
103 Handle<JSFunction> func = Handle<JSFunction>::cast(value); \
104 if (!func.is_identical_to(isolate->fname())) return false; \
105 }
106 STDLIB_ARRAY_TYPE(int8_array_fun, Int8Array)
107 STDLIB_ARRAY_TYPE(uint8_array_fun, Uint8Array)
108 STDLIB_ARRAY_TYPE(int16_array_fun, Int16Array)
109 STDLIB_ARRAY_TYPE(uint16_array_fun, Uint16Array)
110 STDLIB_ARRAY_TYPE(int32_array_fun, Int32Array)
111 STDLIB_ARRAY_TYPE(uint32_array_fun, Uint32Array)
112 STDLIB_ARRAY_TYPE(float32_array_fun, Float32Array)
113 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
Report(Handle<Script> script,int position,Vector<const char> text,MessageTemplate message_template,v8::Isolate::MessageErrorLevel level)120 void Report(Handle<Script> script, int position, Vector<const char> text,
121 MessageTemplate message_template,
122 v8::Isolate::MessageErrorLevel level) {
123 Isolate* isolate = script->GetIsolate();
124 MessageLocation location(script, position, position);
125 Handle<String> text_object = isolate->factory()->InternalizeUtf8String(text);
126 Handle<JSMessageObject> message = MessageHandler::MakeMessageObject(
127 isolate, message_template, &location, text_object,
128 Handle<FixedArray>::null());
129 message->set_error_level(level);
130 MessageHandler::ReportMessage(isolate, &location, message);
131 }
132
133 // Hook to report successful execution of {AsmJs::CompileAsmViaWasm} phase.
ReportCompilationSuccess(Handle<Script> script,int position,double compile_time,size_t module_size)134 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 EmbeddedVector<char, 100> text;
138 int length = SNPrintF(text, "success, compile time %0.3f ms, %zu bytes",
139 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.
ReportCompilationFailure(ParseInfo * parse_info,int position,const char * reason)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.
ReportInstantiationSuccess(Handle<Script> script,int position,double instantiate_time)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.
ReportInstantiationFailure(Handle<Script> script,int position,const char * reason)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.
185 class AsmJsCompilationJob final : public UnoptimizedCompilationJob {
186 public:
AsmJsCompilationJob(ParseInfo * parse_info,FunctionLiteral * literal,AccountingAllocator * allocator)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 compile_time_(0),
197 module_source_size_(0) {}
198
199 protected:
200 Status ExecuteJobImpl() final;
201 Status FinalizeJobImpl(Handle<SharedFunctionInfo> shared_info,
202 Isolate* isolate) final;
FinalizeJobImpl(Handle<SharedFunctionInfo> shared_info,LocalIsolate * isolate)203 Status FinalizeJobImpl(Handle<SharedFunctionInfo> shared_info,
204 LocalIsolate* isolate) final {
205 return CompilationJob::RETRY_ON_MAIN_THREAD;
206 }
207
208 private:
209 void RecordHistograms(Isolate* isolate);
210
211 AccountingAllocator* allocator_;
212 Zone zone_;
213 UnoptimizedCompilationInfo compilation_info_;
214 wasm::ZoneBuffer* module_;
215 wasm::ZoneBuffer* asm_offsets_;
216 wasm::AsmJsParser::StdlibSet stdlib_uses_;
217
218 double compile_time_; // Time (milliseconds) taken to execute step [2].
219 int module_source_size_; // Module source size in bytes.
220
221 DISALLOW_COPY_AND_ASSIGN(AsmJsCompilationJob);
222 };
223
ExecuteJobImpl()224 UnoptimizedCompilationJob::Status AsmJsCompilationJob::ExecuteJobImpl() {
225 // Step 1: Translate asm.js module to WebAssembly module.
226 Zone* compile_zone = &zone_;
227 Zone translate_zone(allocator_, ZONE_NAME);
228
229 Utf16CharacterStream* stream = parse_info()->character_stream();
230 base::Optional<AllowHandleDereference> allow_deref;
231 if (stream->can_access_heap()) {
232 allow_deref.emplace();
233 }
234 stream->Seek(compilation_info()->literal()->start_position());
235 wasm::AsmJsParser parser(&translate_zone, stack_limit(), stream);
236 if (!parser.Run()) {
237 if (!FLAG_suppress_asm_messages) {
238 ReportCompilationFailure(parse_info(), parser.failure_location(),
239 parser.failure_message());
240 }
241 return FAILED;
242 }
243 module_ = compile_zone->New<wasm::ZoneBuffer>(compile_zone);
244 parser.module_builder()->WriteTo(module_);
245 asm_offsets_ = compile_zone->New<wasm::ZoneBuffer>(compile_zone);
246 parser.module_builder()->WriteAsmJsOffsetTable(asm_offsets_);
247 stdlib_uses_ = *parser.stdlib_uses();
248
249 module_source_size_ = compilation_info()->literal()->end_position() -
250 compilation_info()->literal()->start_position();
251 return SUCCEEDED;
252 }
253
FinalizeJobImpl(Handle<SharedFunctionInfo> shared_info,Isolate * isolate)254 UnoptimizedCompilationJob::Status AsmJsCompilationJob::FinalizeJobImpl(
255 Handle<SharedFunctionInfo> shared_info, Isolate* isolate) {
256 // Step 2: Compile and decode the WebAssembly module.
257 base::ElapsedTimer compile_timer;
258 compile_timer.Start();
259
260 Handle<HeapNumber> uses_bitset =
261 isolate->factory()->NewHeapNumberFromBits(stdlib_uses_.ToIntegral());
262
263 // The result is a compiled module and serialized standard library uses.
264 wasm::ErrorThrower thrower(isolate, "AsmJs::Compile");
265 Handle<AsmWasmData> result =
266 isolate->wasm_engine()
267 ->SyncCompileTranslatedAsmJs(
268 isolate, &thrower,
269 wasm::ModuleWireBytes(module_->begin(), module_->end()),
270 VectorOf(*asm_offsets_), uses_bitset,
271 shared_info->language_mode())
272 .ToHandleChecked();
273 DCHECK(!thrower.error());
274 compile_time_ = compile_timer.Elapsed().InMillisecondsF();
275
276 compilation_info()->SetAsmWasmData(result);
277
278 RecordHistograms(isolate);
279 ReportCompilationSuccess(handle(Script::cast(shared_info->script()), isolate),
280 shared_info->StartPosition(), compile_time_,
281 module_->size());
282 return SUCCEEDED;
283 }
284
RecordHistograms(Isolate * isolate)285 void AsmJsCompilationJob::RecordHistograms(Isolate* isolate) {
286 isolate->counters()->asm_module_size_bytes()->AddSample(module_source_size_);
287 }
288
NewCompilationJob(ParseInfo * parse_info,FunctionLiteral * literal,AccountingAllocator * allocator)289 std::unique_ptr<UnoptimizedCompilationJob> AsmJs::NewCompilationJob(
290 ParseInfo* parse_info, FunctionLiteral* literal,
291 AccountingAllocator* allocator) {
292 return std::make_unique<AsmJsCompilationJob>(parse_info, literal, allocator);
293 }
294
295 namespace {
IsValidAsmjsMemorySize(size_t size)296 inline bool IsValidAsmjsMemorySize(size_t size) {
297 // Enforce asm.js spec minimum size.
298 if (size < (1u << 12u)) return false;
299 // Enforce engine-limited and flag-limited maximum allocation size.
300 if (size > wasm::max_mem_pages() * uint64_t{wasm::kWasmPageSize}) {
301 return false;
302 }
303 // Enforce power-of-2 sizes for 2^12 - 2^24.
304 if (size < (1u << 24u)) {
305 uint32_t size32 = static_cast<uint32_t>(size);
306 return base::bits::IsPowerOfTwo(size32);
307 }
308 // Enforce multiple of 2^24 for sizes >= 2^24
309 if ((size % (1u << 24u)) != 0) return false;
310 // All checks passed!
311 return true;
312 }
313 } // namespace
314
InstantiateAsmWasm(Isolate * isolate,Handle<SharedFunctionInfo> shared,Handle<AsmWasmData> wasm_data,Handle<JSReceiver> stdlib,Handle<JSReceiver> foreign,Handle<JSArrayBuffer> memory)315 MaybeHandle<Object> AsmJs::InstantiateAsmWasm(Isolate* isolate,
316 Handle<SharedFunctionInfo> shared,
317 Handle<AsmWasmData> wasm_data,
318 Handle<JSReceiver> stdlib,
319 Handle<JSReceiver> foreign,
320 Handle<JSArrayBuffer> memory) {
321 base::ElapsedTimer instantiate_timer;
322 instantiate_timer.Start();
323 Handle<HeapNumber> uses_bitset(wasm_data->uses_bitset(), isolate);
324 Handle<Script> script(Script::cast(shared->script()), isolate);
325 const auto& wasm_engine = isolate->wasm_engine();
326
327 // Allocate the WasmModuleObject.
328 Handle<WasmModuleObject> module =
329 wasm_engine->FinalizeTranslatedAsmJs(isolate, wasm_data, script);
330
331 // TODO(asmjs): The position currently points to the module definition
332 // but should instead point to the instantiation site (more intuitive).
333 int position = shared->StartPosition();
334
335 // Check that the module is not instantiated as a generator or async function.
336 if (IsResumableFunction(shared->scope_info().function_kind())) {
337 ReportInstantiationFailure(script, position,
338 "Cannot be instantiated as resumable function");
339 return MaybeHandle<Object>();
340 }
341
342 // Check that all used stdlib members are valid.
343 bool stdlib_use_of_typed_array_present = false;
344 wasm::AsmJsParser::StdlibSet stdlib_uses =
345 wasm::AsmJsParser::StdlibSet::FromIntegral(uses_bitset->value_as_bits());
346 if (!stdlib_uses.empty()) { // No checking needed if no uses.
347 if (stdlib.is_null()) {
348 ReportInstantiationFailure(script, position, "Requires standard library");
349 return MaybeHandle<Object>();
350 }
351 if (!AreStdlibMembersValid(isolate, stdlib, stdlib_uses,
352 &stdlib_use_of_typed_array_present)) {
353 ReportInstantiationFailure(script, position, "Unexpected stdlib member");
354 return MaybeHandle<Object>();
355 }
356 }
357
358 // Check that a valid heap buffer is provided if required.
359 if (stdlib_use_of_typed_array_present) {
360 if (memory.is_null()) {
361 ReportInstantiationFailure(script, position, "Requires heap buffer");
362 return MaybeHandle<Object>();
363 }
364 // AsmJs memory must be an ArrayBuffer.
365 if (memory->is_shared()) {
366 ReportInstantiationFailure(script, position,
367 "Invalid heap type: SharedArrayBuffer");
368 return MaybeHandle<Object>();
369 }
370 // Mark the buffer as being used as an asm.js memory. This implies two
371 // things: 1) if the buffer is from a Wasm memory, that memory can no longer
372 // be grown, since that would detach this buffer, and 2) the buffer cannot
373 // be postMessage()'d, as that also detaches the buffer.
374 memory->set_is_asmjs_memory(true);
375 memory->set_is_detachable(false);
376 size_t size = memory->byte_length();
377 // Check the asm.js heap size against the valid limits.
378 if (!IsValidAsmjsMemorySize(size)) {
379 ReportInstantiationFailure(script, position, "Invalid heap size");
380 return MaybeHandle<Object>();
381 }
382 } else {
383 memory = Handle<JSArrayBuffer>::null();
384 }
385
386 wasm::ErrorThrower thrower(isolate, "AsmJs::Instantiate");
387 MaybeHandle<WasmInstanceObject> maybe_instance =
388 wasm_engine->SyncInstantiate(isolate, &thrower, module, foreign, memory);
389 if (maybe_instance.is_null()) {
390 // An exception caused by the module start function will be set as pending
391 // and bypass the {ErrorThrower}, this happens in case of a stack overflow.
392 if (isolate->has_pending_exception()) isolate->clear_pending_exception();
393 if (thrower.error()) {
394 ScopedVector<char> error_reason(100);
395 SNPrintF(error_reason, "Internal wasm failure: %s", thrower.error_msg());
396 ReportInstantiationFailure(script, position, error_reason.begin());
397 } else {
398 ReportInstantiationFailure(script, position, "Internal wasm failure");
399 }
400 thrower.Reset(); // Ensure exceptions do not propagate.
401 return MaybeHandle<Object>();
402 }
403 DCHECK(!thrower.error());
404 Handle<WasmInstanceObject> instance = maybe_instance.ToHandleChecked();
405
406 ReportInstantiationSuccess(script, position,
407 instantiate_timer.Elapsed().InMillisecondsF());
408
409 Handle<Name> single_function_name(
410 isolate->factory()->InternalizeUtf8String(AsmJs::kSingleFunctionName));
411 MaybeHandle<Object> single_function =
412 Object::GetProperty(isolate, instance, single_function_name);
413 if (!single_function.is_null() &&
414 !single_function.ToHandleChecked()->IsUndefined(isolate)) {
415 return single_function;
416 }
417
418 // Here we rely on the fact that the exports object is eagerly created.
419 // The following check is a weak indicator for that. If this ever changes,
420 // then we'll have to call the "exports" getter, and be careful about
421 // handling possible stack overflow exceptions.
422 DCHECK(instance->exports_object().IsJSObject());
423 return handle(instance->exports_object(), isolate);
424 }
425
426 } // namespace internal
427 } // namespace v8
428