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/base/vector.h"
13 #include "src/codegen/compiler.h"
14 #include "src/codegen/unoptimized-compilation-info.h"
15 #include "src/common/assert-scope.h"
16 #include "src/common/message-template.h"
17 #include "src/execution/execution.h"
18 #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 #include "src/parsing/scanner-character-streams.h"
26 #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 #include "src/wasm/wasm-result.h"
33 
34 namespace v8 {
35 namespace internal {
36 
37 const char* const AsmJs::kSingleFunctionName = "__single_function__";
38 
39 namespace {
40 
StdlibMathMember(Isolate* isolate, Handle<JSReceiver> stdlib, Handle<Name> name)41 Handle<Object> StdlibMathMember(Isolate* isolate, Handle<JSReceiver> stdlib,
42                                 Handle<Name> name) {
43   Handle<Name> math_name(
44       isolate->factory()->InternalizeString(base::StaticCharVector("Math")));
45   Handle<Object> math = JSReceiver::GetDataProperty(isolate, stdlib, math_name);
46   if (!math->IsJSReceiver()) return isolate->factory()->undefined_value();
47   Handle<JSReceiver> math_receiver = Handle<JSReceiver>::cast(math);
48   Handle<Object> value =
49       JSReceiver::GetDataProperty(isolate, 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(isolate, 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(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     Handle<Name> name(isolate->factory()->InternalizeString(               \
72         base::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() != Builtin::kMath##FName) {                    \
78       return false;                                                        \
79     }                                                                      \
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     Handle<Name> name(isolate->factory()->InternalizeString(                \
89         base::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(isolate->factory()->InternalizeString(                   \
100         base::StaticCharVector(#FName)));                                      \
101     Handle<Object> value = JSReceiver::GetDataProperty(isolate, 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, base::Vector<const char> text, MessageTemplate message_template, v8::Isolate::MessageErrorLevel level)120 void Report(Handle<Script> script, int position, base::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   base::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   base::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   base::Vector<const char> text = base::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   AsmJsCompilationJob(const AsmJsCompilationJob&) = delete;
200   AsmJsCompilationJob& operator=(const AsmJsCompilationJob&) = delete;
201 
202  protected:
203   Status ExecuteJobImpl() final;
204   Status FinalizeJobImpl(Handle<SharedFunctionInfo> shared_info,
205                          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   void RecordHistograms(Isolate* isolate);
213 
214   AccountingAllocator* allocator_;
215   Zone zone_;
216   UnoptimizedCompilationInfo compilation_info_;
217   wasm::ZoneBuffer* module_;
218   wasm::ZoneBuffer* asm_offsets_;
219   wasm::AsmJsParser::StdlibSet stdlib_uses_;
220 
221   double compile_time_;     // Time (milliseconds) taken to execute step [2].
222   int module_source_size_;  // Module source size in bytes.
223 };
224 
ExecuteJobImpl()225 UnoptimizedCompilationJob::Status AsmJsCompilationJob::ExecuteJobImpl() {
226   DisallowHeapAccess no_heap_access;
227 
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   base::Optional<AllowHandleDereference> allow_deref;
234   if (stream->can_access_heap()) {
235     allow_deref.emplace();
236   }
237   stream->Seek(compilation_info()->literal()->start_position());
238   wasm::AsmJsParser parser(&translate_zone, stack_limit(), stream);
239   if (!parser.Run()) {
240     if (!FLAG_suppress_asm_messages) {
241       ReportCompilationFailure(parse_info(), parser.failure_location(),
242                                parser.failure_message());
243     }
244     return FAILED;
245   }
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   module_source_size_ = compilation_info()->literal()->end_position() -
253                         compilation_info()->literal()->start_position();
254   return SUCCEEDED;
255 }
256 
FinalizeJobImpl( Handle<SharedFunctionInfo> shared_info, Isolate* isolate)257 UnoptimizedCompilationJob::Status AsmJsCompilationJob::FinalizeJobImpl(
258     Handle<SharedFunctionInfo> shared_info, Isolate* isolate) {
259   // Step 2: Compile and decode the WebAssembly module.
260   base::ElapsedTimer compile_timer;
261   compile_timer.Start();
262 
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           ->SyncCompileTranslatedAsmJs(
271               isolate, &thrower,
272               wasm::ModuleWireBytes(module_->begin(), module_->end()),
273               base::VectorOf(*asm_offsets_), uses_bitset,
274               shared_info->language_mode())
275           .ToHandleChecked();
276   DCHECK(!thrower.error());
277   compile_time_ = compile_timer.Elapsed().InMillisecondsF();
278 
279   compilation_info()->SetAsmWasmData(result);
280 
281   RecordHistograms(isolate);
282   ReportCompilationSuccess(handle(Script::cast(shared_info->script()), isolate),
283                            shared_info->StartPosition(), compile_time_,
284                            module_->size());
285   return SUCCEEDED;
286 }
287 
RecordHistograms(Isolate* isolate)288 void AsmJsCompilationJob::RecordHistograms(Isolate* isolate) {
289   isolate->counters()->asm_module_size_bytes()->AddSample(module_source_size_);
290 }
291 
NewCompilationJob( ParseInfo* parse_info, FunctionLiteral* literal, AccountingAllocator* allocator)292 std::unique_ptr<UnoptimizedCompilationJob> AsmJs::NewCompilationJob(
293     ParseInfo* parse_info, FunctionLiteral* literal,
294     AccountingAllocator* allocator) {
295   return std::make_unique<AsmJsCompilationJob>(parse_info, literal, allocator);
296 }
297 
298 namespace {
IsValidAsmjsMemorySize(size_t size)299 inline bool IsValidAsmjsMemorySize(size_t size) {
300   // Enforce asm.js spec minimum size.
301   if (size < (1u << 12u)) return false;
302   // Enforce engine-limited and flag-limited maximum allocation size.
303   if (size > wasm::max_mem_bytes()) return false;
304   // Enforce power-of-2 sizes for 2^12 - 2^24.
305   if (size < (1u << 24u)) {
306     uint32_t size32 = static_cast<uint32_t>(size);
307     return base::bits::IsPowerOfTwo(size32);
308   }
309   // Enforce multiple of 2^24 for sizes >= 2^24
310   if ((size % (1u << 24u)) != 0) return false;
311   // All checks passed!
312   return true;
313 }
314 }  // namespace
315 
InstantiateAsmWasm(Isolate* isolate, Handle<SharedFunctionInfo> shared, Handle<AsmWasmData> wasm_data, Handle<JSReceiver> stdlib, Handle<JSReceiver> foreign, Handle<JSArrayBuffer> memory)316 MaybeHandle<Object> AsmJs::InstantiateAsmWasm(Isolate* isolate,
317                                               Handle<SharedFunctionInfo> shared,
318                                               Handle<AsmWasmData> wasm_data,
319                                               Handle<JSReceiver> stdlib,
320                                               Handle<JSReceiver> foreign,
321                                               Handle<JSArrayBuffer> memory) {
322   base::ElapsedTimer instantiate_timer;
323   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   // Allocate the WasmModuleObject.
329   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   // Check that the module is not instantiated as a generator or async function.
337   if (IsResumableFunction(shared->scope_info().function_kind())) {
338     ReportInstantiationFailure(script, position,
339                                "Cannot be instantiated as resumable function");
340     return MaybeHandle<Object>();
341   }
342 
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       wasm::AsmJsParser::StdlibSet::FromIntegral(
347           uses_bitset->value_as_bits(kRelaxedLoad));
348   if (!stdlib_uses.empty()) {  // No checking needed if no uses.
349     if (stdlib.is_null()) {
350       ReportInstantiationFailure(script, position, "Requires standard library");
351       return MaybeHandle<Object>();
352     }
353     if (!AreStdlibMembersValid(isolate, stdlib, stdlib_uses,
354                                &stdlib_use_of_typed_array_present)) {
355       ReportInstantiationFailure(script, position, "Unexpected stdlib member");
356       return MaybeHandle<Object>();
357     }
358   }
359 
360   // Check that a valid heap buffer is provided if required.
361   if (stdlib_use_of_typed_array_present) {
362     if (memory.is_null()) {
363       ReportInstantiationFailure(script, position, "Requires heap buffer");
364       return MaybeHandle<Object>();
365     }
366     // AsmJs memory must be an ArrayBuffer.
367     if (memory->is_shared()) {
368       ReportInstantiationFailure(script, position,
369                                  "Invalid heap type: SharedArrayBuffer");
370       return MaybeHandle<Object>();
371     }
372     // Mark the buffer as being used as an asm.js memory. This implies two
373     // things: 1) if the buffer is from a Wasm memory, that memory can no longer
374     // be grown, since that would detach this buffer, and 2) the buffer cannot
375     // be postMessage()'d, as that also detaches the buffer.
376     memory->set_is_asmjs_memory(true);
377     memory->set_is_detachable(false);
378     size_t size = memory->byte_length();
379     // Check the asm.js heap size against the valid limits.
380     if (!IsValidAsmjsMemorySize(size)) {
381       ReportInstantiationFailure(script, position, "Invalid heap size");
382       return MaybeHandle<Object>();
383     }
384   } else {
385     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     // An exception caused by the module start function will be set as pending
393     // and bypass the {ErrorThrower}, this happens in case of a stack overflow.
394     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     } else {
400       ReportInstantiationFailure(script, position, "Internal wasm failure");
401     }
402     thrower.Reset();  // Ensure exceptions do not propagate.
403     return MaybeHandle<Object>();
404   }
405   DCHECK(!thrower.error());
406   Handle<WasmInstanceObject> instance = maybe_instance.ToHandleChecked();
407 
408   ReportInstantiationSuccess(script, position,
409                              instantiate_timer.Elapsed().InMillisecondsF());
410 
411   Handle<Name> single_function_name(
412       isolate->factory()->InternalizeUtf8String(AsmJs::kSingleFunctionName));
413   MaybeHandle<Object> single_function =
414       Object::GetProperty(isolate, instance, single_function_name);
415   if (!single_function.is_null() &&
416       !single_function.ToHandleChecked()->IsUndefined(isolate)) {
417     return single_function;
418   }
419 
420   // Here we rely on the fact that the exports object is eagerly created.
421   // The following check is a weak indicator for that. If this ever changes,
422   // then we'll have to call the "exports" getter, and be careful about
423   // handling possible stack overflow exceptions.
424   DCHECK(instance->exports_object().IsJSObject());
425   return handle(instance->exports_object(), isolate);
426 }
427 
428 }  // namespace internal
429 }  // namespace v8
430