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