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/compiler.h"
14 : #include "src/counters.h"
15 : #include "src/execution.h"
16 : #include "src/handles.h"
17 : #include "src/heap/factory.h"
18 : #include "src/isolate.h"
19 : #include "src/message-template.h"
20 : #include "src/objects-inl.h"
21 : #include "src/objects/heap-number-inl.h"
22 : #include "src/parsing/parse-info.h"
23 : #include "src/parsing/scanner-character-streams.h"
24 : #include "src/parsing/scanner.h"
25 : #include "src/unoptimized-compilation-info.h"
26 : #include "src/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 :
42 4862 : Handle<Object> StdlibMathMember(Isolate* isolate, Handle<JSReceiver> stdlib,
43 : Handle<Name> name) {
44 : Handle<Name> math_name(
45 4862 : isolate->factory()->InternalizeOneByteString(StaticCharVector("Math")));
46 4862 : Handle<Object> math = JSReceiver::GetDataProperty(stdlib, math_name);
47 4866 : if (!math->IsJSReceiver()) return isolate->factory()->undefined_value();
48 4858 : Handle<JSReceiver> math_receiver = Handle<JSReceiver>::cast(math);
49 4858 : Handle<Object> value = JSReceiver::GetDataProperty(math_receiver, name);
50 4858 : return value;
51 : }
52 :
53 2152 : bool AreStdlibMembersValid(Isolate* isolate, Handle<JSReceiver> stdlib,
54 : wasm::AsmJsParser::StdlibSet members,
55 : bool* is_typed_array) {
56 2152 : if (members.contains(wasm::AsmJsParser::StandardMember::kInfinity)) {
57 : members.Remove(wasm::AsmJsParser::StandardMember::kInfinity);
58 : Handle<Name> name = isolate->factory()->Infinity_string();
59 8 : Handle<Object> value = JSReceiver::GetDataProperty(stdlib, name);
60 16 : if (!value->IsNumber() || !std::isinf(value->Number())) return false;
61 : }
62 2152 : if (members.contains(wasm::AsmJsParser::StandardMember::kNaN)) {
63 : members.Remove(wasm::AsmJsParser::StandardMember::kNaN);
64 : Handle<Name> name = isolate->factory()->NaN_string();
65 16 : Handle<Object> value = JSReceiver::GetDataProperty(stdlib, name);
66 16 : 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()->InternalizeOneByteString( \
72 : 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 60016 : 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()->InternalizeOneByteString( \
89 : StaticCharVector(#cname))); \
90 : Handle<Object> value = StdlibMathMember(isolate, stdlib, name); \
91 : if (!value->IsNumber() || value->Number() != const_value) return false; \
92 : }
93 17516 : 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()->InternalizeOneByteString( \
100 : 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 2676 : STDLIB_ARRAY_TYPE(int8_array_fun, Int8Array)
107 2908 : STDLIB_ARRAY_TYPE(uint8_array_fun, Uint8Array)
108 2628 : STDLIB_ARRAY_TYPE(int16_array_fun, Int16Array)
109 2532 : STDLIB_ARRAY_TYPE(uint16_array_fun, Uint16Array)
110 6116 : STDLIB_ARRAY_TYPE(int32_array_fun, Int32Array)
111 2644 : STDLIB_ARRAY_TYPE(uint32_array_fun, Uint32Array)
112 2756 : STDLIB_ARRAY_TYPE(float32_array_fun, Float32Array)
113 2566 : 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 172 : 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 172 : MessageLocation location(script, position, position);
125 172 : Handle<String> text_object = isolate->factory()->InternalizeUtf8String(text);
126 : Handle<JSMessageObject> message = MessageHandler::MakeMessageObject(
127 : isolate, message_template, &location, text_object,
128 172 : Handle<FixedArray>::null());
129 172 : message->set_error_level(level);
130 172 : MessageHandler::ReportMessage(isolate, &location, message);
131 172 : }
132 :
133 : // Hook to report successful execution of {AsmJs::CompileAsmViaWasm} phase.
134 2464 : void ReportCompilationSuccess(Handle<Script> script, int position,
135 : double translate_time, double compile_time,
136 : size_t module_size) {
137 4928 : if (FLAG_suppress_asm_messages || !FLAG_trace_asm_time) return;
138 : EmbeddedVector<char, 100> text;
139 : int length = SNPrintF(
140 : text, "success, asm->wasm: %0.3f ms, compile: %0.3f ms, %" PRIuS " bytes",
141 0 : translate_time, compile_time, module_size);
142 0 : CHECK_NE(-1, length);
143 0 : text.Truncate(length);
144 : Report(script, position, text, MessageTemplate::kAsmJsCompiled,
145 0 : v8::Isolate::kMessageInfo);
146 : }
147 :
148 : // Hook to report failed execution of {AsmJs::CompileAsmViaWasm} phase.
149 : void ReportCompilationFailure(ParseInfo* parse_info, int position,
150 : const char* reason) {
151 1266 : if (FLAG_suppress_asm_messages) return;
152 : parse_info->pending_error_handler()->ReportWarningAt(
153 1266 : position, position, MessageTemplate::kAsmJsInvalid, reason);
154 : }
155 :
156 : // Hook to report successful execution of {AsmJs::InstantiateAsmWasm} phase.
157 4847 : void ReportInstantiationSuccess(Handle<Script> script, int position,
158 : double instantiate_time) {
159 9694 : 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 172 : void ReportInstantiationFailure(Handle<Script> script, int position,
170 : const char* reason) {
171 172 : if (FLAG_suppress_asm_messages) return;
172 172 : Vector<const char> text = CStrVector(reason);
173 : Report(script, position, text, MessageTemplate::kAsmJsLinkingFailed,
174 172 : 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 11278 : class AsmJsCompilationJob final : public UnoptimizedCompilationJob {
188 : public:
189 3760 : explicit AsmJsCompilationJob(ParseInfo* parse_info, FunctionLiteral* literal,
190 : AccountingAllocator* allocator)
191 : : UnoptimizedCompilationJob(parse_info->stack_limit(), parse_info,
192 : &compilation_info_),
193 : allocator_(allocator),
194 : zone_(allocator, ZONE_NAME),
195 : compilation_info_(&zone_, parse_info, literal),
196 : module_(nullptr),
197 : asm_offsets_(nullptr),
198 : translate_time_(0),
199 : compile_time_(0),
200 : module_source_size_(0),
201 : translate_time_micro_(0),
202 11280 : translate_zone_size_(0) {}
203 :
204 : protected:
205 : Status ExecuteJobImpl() final;
206 : Status FinalizeJobImpl(Handle<SharedFunctionInfo> shared_info,
207 : Isolate* isolate) final;
208 :
209 : private:
210 : void RecordHistograms(Isolate* isolate);
211 :
212 : AccountingAllocator* allocator_;
213 : Zone zone_;
214 : UnoptimizedCompilationInfo compilation_info_;
215 : wasm::ZoneBuffer* module_;
216 : wasm::ZoneBuffer* asm_offsets_;
217 : wasm::AsmJsParser::StdlibSet stdlib_uses_;
218 :
219 : double translate_time_; // Time (milliseconds) taken to execute step [1].
220 : double compile_time_; // Time (milliseconds) taken to execute step [2].
221 : int module_source_size_; // Module source size in bytes.
222 : int64_t translate_time_micro_; // Time (microseconds) taken to translate.
223 : size_t translate_zone_size_;
224 :
225 : DISALLOW_COPY_AND_ASSIGN(AsmJsCompilationJob);
226 : };
227 :
228 3760 : UnoptimizedCompilationJob::Status AsmJsCompilationJob::ExecuteJobImpl() {
229 : // Step 1: Translate asm.js module to WebAssembly module.
230 : size_t compile_zone_start = compilation_info()->zone()->allocation_size();
231 : base::ElapsedTimer translate_timer;
232 : translate_timer.Start();
233 :
234 : Zone* compile_zone = compilation_info()->zone();
235 7520 : Zone translate_zone(allocator_, ZONE_NAME);
236 :
237 : Utf16CharacterStream* stream = parse_info()->character_stream();
238 : base::Optional<AllowHandleDereference> allow_deref;
239 3760 : if (stream->can_access_heap()) {
240 : allow_deref.emplace();
241 : }
242 3760 : stream->Seek(compilation_info()->literal()->start_position());
243 3760 : wasm::AsmJsParser parser(&translate_zone, stack_limit(), stream);
244 3759 : if (!parser.Run()) {
245 1265 : if (!FLAG_suppress_asm_messages) {
246 : ReportCompilationFailure(parse_info(), parser.failure_location(),
247 : parser.failure_message());
248 : }
249 : return FAILED;
250 : }
251 2494 : module_ = new (compile_zone) wasm::ZoneBuffer(compile_zone);
252 2494 : parser.module_builder()->WriteTo(*module_);
253 2494 : asm_offsets_ = new (compile_zone) wasm::ZoneBuffer(compile_zone);
254 2494 : parser.module_builder()->WriteAsmJsOffsetTable(*asm_offsets_);
255 2494 : stdlib_uses_ = *parser.stdlib_uses();
256 :
257 : size_t compile_zone_size =
258 2494 : compilation_info()->zone()->allocation_size() - compile_zone_start;
259 2494 : translate_zone_size_ = translate_zone.allocation_size();
260 2494 : translate_time_ = translate_timer.Elapsed().InMillisecondsF();
261 2494 : translate_time_micro_ = translate_timer.Elapsed().InMicroseconds();
262 4988 : module_source_size_ = compilation_info()->literal()->end_position() -
263 4988 : compilation_info()->literal()->start_position();
264 2494 : if (FLAG_trace_asm_parser) {
265 0 : PrintF(
266 : "[asm.js translation successful: time=%0.3fms, "
267 : "translate_zone=%" PRIuS "KB, compile_zone+=%" PRIuS "KB]\n",
268 0 : translate_time_, translate_zone_size_ / KB, compile_zone_size / KB);
269 : }
270 : return SUCCEEDED;
271 : }
272 :
273 2464 : UnoptimizedCompilationJob::Status AsmJsCompilationJob::FinalizeJobImpl(
274 : Handle<SharedFunctionInfo> shared_info, Isolate* isolate) {
275 : // Step 2: Compile and decode the WebAssembly module.
276 : base::ElapsedTimer compile_timer;
277 : compile_timer.Start();
278 :
279 : Handle<HeapNumber> uses_bitset =
280 2464 : isolate->factory()->NewHeapNumberFromBits(stdlib_uses_.ToIntegral());
281 :
282 : // The result is a compiled module and serialized standard library uses.
283 2464 : wasm::ErrorThrower thrower(isolate, "AsmJs::Compile");
284 : Handle<AsmWasmData> result =
285 : isolate->wasm_engine()
286 4928 : ->SyncCompileTranslatedAsmJs(
287 : isolate, &thrower,
288 2464 : wasm::ModuleWireBytes(module_->begin(), module_->end()),
289 2464 : Vector<const byte>(asm_offsets_->begin(), asm_offsets_->size()),
290 2464 : uses_bitset)
291 : .ToHandleChecked();
292 : DCHECK(!thrower.error());
293 2464 : compile_time_ = compile_timer.Elapsed().InMillisecondsF();
294 :
295 : compilation_info()->SetAsmWasmData(result);
296 :
297 2464 : RecordHistograms(isolate);
298 2464 : ReportCompilationSuccess(parse_info()->script(),
299 : compilation_info()->literal()->position(),
300 4928 : translate_time_, compile_time_, module_->size());
301 2464 : return SUCCEEDED;
302 : }
303 :
304 2464 : void AsmJsCompilationJob::RecordHistograms(Isolate* isolate) {
305 : Counters* counters = isolate->counters();
306 2464 : counters->asm_wasm_translation_time()->AddSample(
307 4928 : static_cast<int>(translate_time_micro_));
308 2464 : counters->asm_wasm_translation_peak_memory_bytes()->AddSample(
309 4928 : static_cast<int>(translate_zone_size_));
310 4928 : counters->asm_module_size_bytes()->AddSample(module_source_size_);
311 : // translation_throughput is not exact (assumes MB == 1000000). But that is ok
312 : // since the metric is stored in buckets that lose some precision anyways.
313 : int translation_throughput =
314 2464 : translate_time_micro_ != 0
315 2464 : ? static_cast<int>(static_cast<int64_t>(module_source_size_) /
316 : translate_time_micro_)
317 4928 : : 0;
318 : counters->asm_wasm_translation_throughput()->AddSample(
319 2464 : translation_throughput);
320 2464 : }
321 :
322 3760 : UnoptimizedCompilationJob* AsmJs::NewCompilationJob(
323 : ParseInfo* parse_info, FunctionLiteral* literal,
324 : AccountingAllocator* allocator) {
325 3760 : return new AsmJsCompilationJob(parse_info, literal, allocator);
326 : }
327 :
328 : namespace {
329 1324 : inline bool IsValidAsmjsMemorySize(size_t size) {
330 : // Enforce asm.js spec minimum size.
331 1324 : if (size < (1u << 12u)) return false;
332 : // Enforce engine-limited and flag-limited maximum allocation size.
333 1269 : if (size > wasm::max_mem_pages() * uint64_t{wasm::kWasmPageSize}) {
334 : return false;
335 : }
336 : // Enforce power-of-2 sizes for 2^12 - 2^24.
337 1261 : if (size < (1u << 24u)) {
338 1154 : uint32_t size32 = static_cast<uint32_t>(size);
339 1154 : return base::bits::IsPowerOfTwo(size32);
340 : }
341 : // Enforce multiple of 2^24 for sizes >= 2^24
342 107 : if ((size % (1u << 24u)) != 0) return false;
343 : // All checks passed!
344 98 : return true;
345 : }
346 : } // namespace
347 :
348 5019 : MaybeHandle<Object> AsmJs::InstantiateAsmWasm(Isolate* isolate,
349 : Handle<SharedFunctionInfo> shared,
350 : Handle<AsmWasmData> wasm_data,
351 : Handle<JSReceiver> stdlib,
352 : Handle<JSReceiver> foreign,
353 : Handle<JSArrayBuffer> memory) {
354 : base::ElapsedTimer instantiate_timer;
355 : instantiate_timer.Start();
356 : Handle<HeapNumber> uses_bitset(wasm_data->uses_bitset(), isolate);
357 10038 : Handle<Script> script(Script::cast(shared->script()), isolate);
358 : const auto& wasm_engine = isolate->wasm_engine();
359 :
360 : // Allocate the WasmModuleObject.
361 : Handle<WasmModuleObject> module =
362 5019 : wasm_engine->FinalizeTranslatedAsmJs(isolate, wasm_data, script);
363 :
364 : // TODO(mstarzinger): The position currently points to the module definition
365 : // but should instead point to the instantiation site (more intuitive).
366 5019 : int position = shared->StartPosition();
367 :
368 : // Check that all used stdlib members are valid.
369 5019 : bool stdlib_use_of_typed_array_present = false;
370 : wasm::AsmJsParser::StdlibSet stdlib_uses =
371 : wasm::AsmJsParser::StdlibSet::FromIntegral(uses_bitset->value_as_bits());
372 5019 : if (!stdlib_uses.empty()) { // No checking needed if no uses.
373 2168 : if (stdlib.is_null()) {
374 16 : ReportInstantiationFailure(script, position, "Requires standard library");
375 16 : return MaybeHandle<Object>();
376 : }
377 2152 : if (!AreStdlibMembersValid(isolate, stdlib, stdlib_uses,
378 : &stdlib_use_of_typed_array_present)) {
379 12 : ReportInstantiationFailure(script, position, "Unexpected stdlib member");
380 12 : return MaybeHandle<Object>();
381 : }
382 : }
383 :
384 : // Check that a valid heap buffer is provided if required.
385 4991 : if (stdlib_use_of_typed_array_present) {
386 1336 : if (memory.is_null()) {
387 12 : ReportInstantiationFailure(script, position, "Requires heap buffer");
388 12 : return MaybeHandle<Object>();
389 : }
390 1324 : wasm_engine->memory_tracker()->MarkWasmMemoryNotGrowable(memory);
391 : size_t size = memory->byte_length();
392 : // Check the asm.js heap size against the valid limits.
393 1324 : if (!IsValidAsmjsMemorySize(size)) {
394 84 : ReportInstantiationFailure(script, position, "Invalid heap size");
395 84 : return MaybeHandle<Object>();
396 : }
397 : } else {
398 : memory = Handle<JSArrayBuffer>::null();
399 : }
400 :
401 4895 : wasm::ErrorThrower thrower(isolate, "AsmJs::Instantiate");
402 : MaybeHandle<Object> maybe_module_object =
403 4895 : wasm_engine->SyncInstantiate(isolate, &thrower, module, foreign, memory);
404 4895 : if (maybe_module_object.is_null()) {
405 : // An exception caused by the module start function will be set as pending
406 : // and bypass the {ErrorThrower}, this happens in case of a stack overflow.
407 48 : if (isolate->has_pending_exception()) isolate->clear_pending_exception();
408 48 : if (thrower.error()) {
409 : ScopedVector<char> error_reason(100);
410 44 : SNPrintF(error_reason, "Internal wasm failure: %s", thrower.error_msg());
411 44 : ReportInstantiationFailure(script, position, error_reason.start());
412 : } else {
413 4 : ReportInstantiationFailure(script, position, "Internal wasm failure");
414 : }
415 48 : thrower.Reset(); // Ensure exceptions do not propagate.
416 48 : return MaybeHandle<Object>();
417 : }
418 : DCHECK(!thrower.error());
419 4847 : Handle<Object> module_object = maybe_module_object.ToHandleChecked();
420 :
421 4847 : ReportInstantiationSuccess(script, position,
422 9694 : instantiate_timer.Elapsed().InMillisecondsF());
423 :
424 : Handle<Name> single_function_name(
425 4847 : isolate->factory()->InternalizeUtf8String(AsmJs::kSingleFunctionName));
426 : MaybeHandle<Object> single_function =
427 4847 : Object::GetProperty(isolate, module_object, single_function_name);
428 9694 : if (!single_function.is_null() &&
429 : !single_function.ToHandleChecked()->IsUndefined(isolate)) {
430 256 : return single_function;
431 : }
432 :
433 : Handle<String> exports_name =
434 4591 : isolate->factory()->InternalizeUtf8String("exports");
435 4591 : return Object::GetProperty(isolate, module_object, exports_name);
436 : }
437 :
438 : } // namespace internal
439 122036 : } // namespace v8
|