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