|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * vim: set ts=8 sts=4 et sw=4 tw=99: |
|
3 * This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "jit/AsmJSLink.h" |
|
8 |
|
9 #include "mozilla/BinarySearch.h" |
|
10 #include "mozilla/PodOperations.h" |
|
11 |
|
12 #ifdef MOZ_VTUNE |
|
13 # include "vtune/VTuneWrapper.h" |
|
14 #endif |
|
15 |
|
16 #include "jscntxt.h" |
|
17 #include "jsmath.h" |
|
18 #include "jsprf.h" |
|
19 #include "jswrapper.h" |
|
20 |
|
21 #include "frontend/BytecodeCompiler.h" |
|
22 #include "jit/AsmJSModule.h" |
|
23 #include "jit/Ion.h" |
|
24 #include "jit/JitCommon.h" |
|
25 #ifdef JS_ION_PERF |
|
26 # include "jit/PerfSpewer.h" |
|
27 #endif |
|
28 #include "vm/StringBuffer.h" |
|
29 |
|
30 #include "jsobjinlines.h" |
|
31 |
|
32 using namespace js; |
|
33 using namespace js::jit; |
|
34 |
|
35 using mozilla::BinarySearch; |
|
36 using mozilla::IsNaN; |
|
37 using mozilla::PodZero; |
|
38 |
|
39 AsmJSFrameIterator::AsmJSFrameIterator(const AsmJSActivation *activation) |
|
40 { |
|
41 if (!activation || activation->isInterruptedSP()) { |
|
42 PodZero(this); |
|
43 JS_ASSERT(done()); |
|
44 return; |
|
45 } |
|
46 |
|
47 module_ = &activation->module(); |
|
48 sp_ = activation->exitSP(); |
|
49 |
|
50 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) |
|
51 // For calls to Ion/C++ on x86/x64, the exitSP is the SP right before the call |
|
52 // to C++. Since the call instruction pushes the return address, we know |
|
53 // that the return address is 1 word below exitSP. |
|
54 returnAddress_ = *(uint8_t**)(sp_ - sizeof(void*)); |
|
55 #else |
|
56 // For calls to Ion/C++ on ARM, the *caller* pushes the return address on |
|
57 // the stack. For Ion, this is just part of the ABI. For C++, the return |
|
58 // address is explicitly pushed before the call since we cannot expect the |
|
59 // callee to immediately push lr. This means that exitSP points to the |
|
60 // return address. |
|
61 returnAddress_ = *(uint8_t**)sp_; |
|
62 #endif |
|
63 |
|
64 settle(); |
|
65 } |
|
66 |
|
67 struct GetCallSite |
|
68 { |
|
69 const AsmJSModule &module; |
|
70 GetCallSite(const AsmJSModule &module) : module(module) {} |
|
71 uint32_t operator[](size_t index) const { |
|
72 return module.callSite(index).returnAddressOffset(); |
|
73 } |
|
74 }; |
|
75 |
|
76 void |
|
77 AsmJSFrameIterator::popFrame() |
|
78 { |
|
79 // After adding stackDepth, sp points to the word before the return address, |
|
80 // on both ARM and x86/x64. |
|
81 sp_ += callsite_->stackDepth(); |
|
82 returnAddress_ = *(uint8_t**)(sp_ - sizeof(void*)); |
|
83 } |
|
84 |
|
85 void |
|
86 AsmJSFrameIterator::settle() |
|
87 { |
|
88 while (true) { |
|
89 uint32_t target = returnAddress_ - module_->codeBase(); |
|
90 size_t lowerBound = 0; |
|
91 size_t upperBound = module_->numCallSites(); |
|
92 |
|
93 size_t match; |
|
94 if (!BinarySearch(GetCallSite(*module_), lowerBound, upperBound, target, &match)) { |
|
95 callsite_ = nullptr; |
|
96 return; |
|
97 } |
|
98 |
|
99 callsite_ = &module_->callSite(match); |
|
100 |
|
101 if (callsite_->isExit()) { |
|
102 popFrame(); |
|
103 continue; |
|
104 } |
|
105 |
|
106 if (callsite_->isEntry()) { |
|
107 callsite_ = nullptr; |
|
108 return; |
|
109 } |
|
110 |
|
111 JS_ASSERT(callsite_->isNormal()); |
|
112 return; |
|
113 } |
|
114 } |
|
115 |
|
116 JSAtom * |
|
117 AsmJSFrameIterator::functionDisplayAtom() const |
|
118 { |
|
119 JS_ASSERT(!done()); |
|
120 return module_->functionName(callsite_->functionNameIndex()); |
|
121 } |
|
122 |
|
123 unsigned |
|
124 AsmJSFrameIterator::computeLine(uint32_t *column) const |
|
125 { |
|
126 JS_ASSERT(!done()); |
|
127 if (column) |
|
128 *column = callsite_->column(); |
|
129 return callsite_->line(); |
|
130 } |
|
131 |
|
132 static bool |
|
133 CloneModule(JSContext *cx, MutableHandle<AsmJSModuleObject*> moduleObj) |
|
134 { |
|
135 ScopedJSDeletePtr<AsmJSModule> module; |
|
136 if (!moduleObj->module().clone(cx, &module)) |
|
137 return false; |
|
138 |
|
139 module->staticallyLink(cx); |
|
140 |
|
141 AsmJSModuleObject *newModuleObj = AsmJSModuleObject::create(cx, &module); |
|
142 if (!newModuleObj) |
|
143 return false; |
|
144 |
|
145 moduleObj.set(newModuleObj); |
|
146 return true; |
|
147 } |
|
148 |
|
149 static bool |
|
150 LinkFail(JSContext *cx, const char *str) |
|
151 { |
|
152 JS_ReportErrorFlagsAndNumber(cx, JSREPORT_WARNING, js_GetErrorMessage, |
|
153 nullptr, JSMSG_USE_ASM_LINK_FAIL, str); |
|
154 return false; |
|
155 } |
|
156 |
|
157 static bool |
|
158 GetDataProperty(JSContext *cx, HandleValue objVal, HandlePropertyName field, MutableHandleValue v) |
|
159 { |
|
160 if (!objVal.isObject()) |
|
161 return LinkFail(cx, "accessing property of non-object"); |
|
162 |
|
163 Rooted<JSPropertyDescriptor> desc(cx); |
|
164 RootedObject obj(cx, &objVal.toObject()); |
|
165 RootedId id(cx, NameToId(field)); |
|
166 if (!JS_GetPropertyDescriptorById(cx, obj, id, &desc)) |
|
167 return false; |
|
168 |
|
169 if (!desc.object()) |
|
170 return LinkFail(cx, "property not present on object"); |
|
171 |
|
172 if (desc.hasGetterOrSetterObject()) |
|
173 return LinkFail(cx, "property is not a data property"); |
|
174 |
|
175 v.set(desc.value()); |
|
176 return true; |
|
177 } |
|
178 |
|
179 static bool |
|
180 ValidateGlobalVariable(JSContext *cx, const AsmJSModule &module, AsmJSModule::Global &global, |
|
181 HandleValue importVal) |
|
182 { |
|
183 JS_ASSERT(global.which() == AsmJSModule::Global::Variable); |
|
184 |
|
185 void *datum = module.globalVarIndexToGlobalDatum(global.varIndex()); |
|
186 |
|
187 switch (global.varInitKind()) { |
|
188 case AsmJSModule::Global::InitConstant: { |
|
189 const Value &v = global.varInitConstant(); |
|
190 switch (global.varInitCoercion()) { |
|
191 case AsmJS_ToInt32: |
|
192 *(int32_t *)datum = v.toInt32(); |
|
193 break; |
|
194 case AsmJS_ToNumber: |
|
195 *(double *)datum = v.toDouble(); |
|
196 break; |
|
197 case AsmJS_FRound: |
|
198 *(float *)datum = static_cast<float>(v.toDouble()); |
|
199 break; |
|
200 } |
|
201 break; |
|
202 } |
|
203 case AsmJSModule::Global::InitImport: { |
|
204 RootedPropertyName field(cx, global.varImportField()); |
|
205 RootedValue v(cx); |
|
206 if (!GetDataProperty(cx, importVal, field, &v)) |
|
207 return false; |
|
208 |
|
209 switch (global.varInitCoercion()) { |
|
210 case AsmJS_ToInt32: |
|
211 if (!ToInt32(cx, v, (int32_t *)datum)) |
|
212 return false; |
|
213 break; |
|
214 case AsmJS_ToNumber: |
|
215 if (!ToNumber(cx, v, (double *)datum)) |
|
216 return false; |
|
217 break; |
|
218 case AsmJS_FRound: |
|
219 if (!RoundFloat32(cx, v, (float *)datum)) |
|
220 return false; |
|
221 break; |
|
222 } |
|
223 break; |
|
224 } |
|
225 } |
|
226 |
|
227 return true; |
|
228 } |
|
229 |
|
230 static bool |
|
231 ValidateFFI(JSContext *cx, AsmJSModule::Global &global, HandleValue importVal, |
|
232 AutoObjectVector *ffis) |
|
233 { |
|
234 RootedPropertyName field(cx, global.ffiField()); |
|
235 RootedValue v(cx); |
|
236 if (!GetDataProperty(cx, importVal, field, &v)) |
|
237 return false; |
|
238 |
|
239 if (!v.isObject() || !v.toObject().is<JSFunction>()) |
|
240 return LinkFail(cx, "FFI imports must be functions"); |
|
241 |
|
242 (*ffis)[global.ffiIndex()] = &v.toObject().as<JSFunction>(); |
|
243 return true; |
|
244 } |
|
245 |
|
246 static bool |
|
247 ValidateArrayView(JSContext *cx, AsmJSModule::Global &global, HandleValue globalVal, |
|
248 HandleValue bufferVal) |
|
249 { |
|
250 RootedPropertyName field(cx, global.viewName()); |
|
251 RootedValue v(cx); |
|
252 if (!GetDataProperty(cx, globalVal, field, &v)) |
|
253 return false; |
|
254 |
|
255 if (!IsTypedArrayConstructor(v, global.viewType())) |
|
256 return LinkFail(cx, "bad typed array constructor"); |
|
257 |
|
258 return true; |
|
259 } |
|
260 |
|
261 static bool |
|
262 ValidateMathBuiltinFunction(JSContext *cx, AsmJSModule::Global &global, HandleValue globalVal) |
|
263 { |
|
264 RootedValue v(cx); |
|
265 if (!GetDataProperty(cx, globalVal, cx->names().Math, &v)) |
|
266 return false; |
|
267 RootedPropertyName field(cx, global.mathName()); |
|
268 if (!GetDataProperty(cx, v, field, &v)) |
|
269 return false; |
|
270 |
|
271 Native native = nullptr; |
|
272 switch (global.mathBuiltinFunction()) { |
|
273 case AsmJSMathBuiltin_sin: native = math_sin; break; |
|
274 case AsmJSMathBuiltin_cos: native = math_cos; break; |
|
275 case AsmJSMathBuiltin_tan: native = math_tan; break; |
|
276 case AsmJSMathBuiltin_asin: native = math_asin; break; |
|
277 case AsmJSMathBuiltin_acos: native = math_acos; break; |
|
278 case AsmJSMathBuiltin_atan: native = math_atan; break; |
|
279 case AsmJSMathBuiltin_ceil: native = math_ceil; break; |
|
280 case AsmJSMathBuiltin_floor: native = math_floor; break; |
|
281 case AsmJSMathBuiltin_exp: native = math_exp; break; |
|
282 case AsmJSMathBuiltin_log: native = math_log; break; |
|
283 case AsmJSMathBuiltin_pow: native = js_math_pow; break; |
|
284 case AsmJSMathBuiltin_sqrt: native = js_math_sqrt; break; |
|
285 case AsmJSMathBuiltin_min: native = js_math_min; break; |
|
286 case AsmJSMathBuiltin_max: native = js_math_max; break; |
|
287 case AsmJSMathBuiltin_abs: native = js_math_abs; break; |
|
288 case AsmJSMathBuiltin_atan2: native = math_atan2; break; |
|
289 case AsmJSMathBuiltin_imul: native = math_imul; break; |
|
290 case AsmJSMathBuiltin_fround: native = math_fround; break; |
|
291 } |
|
292 |
|
293 if (!IsNativeFunction(v, native)) |
|
294 return LinkFail(cx, "bad Math.* builtin function"); |
|
295 |
|
296 return true; |
|
297 } |
|
298 |
|
299 static bool |
|
300 ValidateConstant(JSContext *cx, AsmJSModule::Global &global, HandleValue globalVal) |
|
301 { |
|
302 RootedPropertyName field(cx, global.constantName()); |
|
303 RootedValue v(cx, globalVal); |
|
304 |
|
305 if (global.constantKind() == AsmJSModule::Global::MathConstant) { |
|
306 if (!GetDataProperty(cx, v, cx->names().Math, &v)) |
|
307 return false; |
|
308 } |
|
309 |
|
310 if (!GetDataProperty(cx, v, field, &v)) |
|
311 return false; |
|
312 if (!v.isNumber()) |
|
313 return LinkFail(cx, "math / global constant value needs to be a number"); |
|
314 |
|
315 // NaN != NaN |
|
316 if (IsNaN(global.constantValue())) { |
|
317 if (!IsNaN(v.toNumber())) |
|
318 return LinkFail(cx, "global constant value needs to be NaN"); |
|
319 } else { |
|
320 if (v.toNumber() != global.constantValue()) |
|
321 return LinkFail(cx, "global constant value mismatch"); |
|
322 } |
|
323 |
|
324 return true; |
|
325 } |
|
326 |
|
327 static bool |
|
328 LinkModuleToHeap(JSContext *cx, AsmJSModule &module, Handle<ArrayBufferObject*> heap) |
|
329 { |
|
330 uint32_t heapLength = heap->byteLength(); |
|
331 if (!IsValidAsmJSHeapLength(heapLength)) { |
|
332 ScopedJSFreePtr<char> msg( |
|
333 JS_smprintf("ArrayBuffer byteLength 0x%x is not a valid heap length. The next " |
|
334 "valid length is 0x%x", |
|
335 heapLength, |
|
336 RoundUpToNextValidAsmJSHeapLength(heapLength))); |
|
337 return LinkFail(cx, msg.get()); |
|
338 } |
|
339 |
|
340 // This check is sufficient without considering the size of the loaded datum because heap |
|
341 // loads and stores start on an aligned boundary and the heap byteLength has larger alignment. |
|
342 JS_ASSERT((module.minHeapLength() - 1) <= INT32_MAX); |
|
343 if (heapLength < module.minHeapLength()) { |
|
344 ScopedJSFreePtr<char> msg( |
|
345 JS_smprintf("ArrayBuffer byteLength of 0x%x is less than 0x%x (which is the" |
|
346 "largest constant heap access offset rounded up to the next valid " |
|
347 "heap size).", |
|
348 heapLength, |
|
349 module.minHeapLength())); |
|
350 return LinkFail(cx, msg.get()); |
|
351 } |
|
352 |
|
353 if (!ArrayBufferObject::prepareForAsmJS(cx, heap)) |
|
354 return LinkFail(cx, "Unable to prepare ArrayBuffer for asm.js use"); |
|
355 |
|
356 module.initHeap(heap, cx); |
|
357 return true; |
|
358 } |
|
359 |
|
360 static bool |
|
361 DynamicallyLinkModule(JSContext *cx, CallArgs args, AsmJSModule &module) |
|
362 { |
|
363 module.setIsDynamicallyLinked(); |
|
364 |
|
365 RootedValue globalVal(cx); |
|
366 if (args.length() > 0) |
|
367 globalVal = args[0]; |
|
368 |
|
369 RootedValue importVal(cx); |
|
370 if (args.length() > 1) |
|
371 importVal = args[1]; |
|
372 |
|
373 RootedValue bufferVal(cx); |
|
374 if (args.length() > 2) |
|
375 bufferVal = args[2]; |
|
376 |
|
377 Rooted<ArrayBufferObject*> heap(cx); |
|
378 if (module.hasArrayView()) { |
|
379 if (!IsTypedArrayBuffer(bufferVal)) |
|
380 return LinkFail(cx, "bad ArrayBuffer argument"); |
|
381 |
|
382 heap = &AsTypedArrayBuffer(bufferVal); |
|
383 if (!LinkModuleToHeap(cx, module, heap)) |
|
384 return false; |
|
385 } |
|
386 |
|
387 AutoObjectVector ffis(cx); |
|
388 if (!ffis.resize(module.numFFIs())) |
|
389 return false; |
|
390 |
|
391 for (unsigned i = 0; i < module.numGlobals(); i++) { |
|
392 AsmJSModule::Global &global = module.global(i); |
|
393 switch (global.which()) { |
|
394 case AsmJSModule::Global::Variable: |
|
395 if (!ValidateGlobalVariable(cx, module, global, importVal)) |
|
396 return false; |
|
397 break; |
|
398 case AsmJSModule::Global::FFI: |
|
399 if (!ValidateFFI(cx, global, importVal, &ffis)) |
|
400 return false; |
|
401 break; |
|
402 case AsmJSModule::Global::ArrayView: |
|
403 if (!ValidateArrayView(cx, global, globalVal, bufferVal)) |
|
404 return false; |
|
405 break; |
|
406 case AsmJSModule::Global::MathBuiltinFunction: |
|
407 if (!ValidateMathBuiltinFunction(cx, global, globalVal)) |
|
408 return false; |
|
409 break; |
|
410 case AsmJSModule::Global::Constant: |
|
411 if (!ValidateConstant(cx, global, globalVal)) |
|
412 return false; |
|
413 break; |
|
414 } |
|
415 } |
|
416 |
|
417 for (unsigned i = 0; i < module.numExits(); i++) |
|
418 module.exitIndexToGlobalDatum(i).fun = &ffis[module.exit(i).ffiIndex()]->as<JSFunction>(); |
|
419 |
|
420 return true; |
|
421 } |
|
422 |
|
423 static const unsigned ASM_MODULE_SLOT = 0; |
|
424 static const unsigned ASM_EXPORT_INDEX_SLOT = 1; |
|
425 |
|
426 static unsigned |
|
427 FunctionToExportedFunctionIndex(HandleFunction fun) |
|
428 { |
|
429 Value v = fun->getExtendedSlot(ASM_EXPORT_INDEX_SLOT); |
|
430 return v.toInt32(); |
|
431 } |
|
432 |
|
433 static const AsmJSModule::ExportedFunction & |
|
434 FunctionToExportedFunction(HandleFunction fun, AsmJSModule &module) |
|
435 { |
|
436 unsigned funIndex = FunctionToExportedFunctionIndex(fun); |
|
437 return module.exportedFunction(funIndex); |
|
438 } |
|
439 |
|
440 static AsmJSModule & |
|
441 FunctionToEnclosingModule(HandleFunction fun) |
|
442 { |
|
443 return fun->getExtendedSlot(ASM_MODULE_SLOT).toObject().as<AsmJSModuleObject>().module(); |
|
444 } |
|
445 |
|
446 // The JSNative for the functions nested in an asm.js module. Calling this |
|
447 // native will trampoline into generated code. |
|
448 static bool |
|
449 CallAsmJS(JSContext *cx, unsigned argc, Value *vp) |
|
450 { |
|
451 CallArgs callArgs = CallArgsFromVp(argc, vp); |
|
452 RootedFunction callee(cx, &callArgs.callee().as<JSFunction>()); |
|
453 |
|
454 // An asm.js function stores, in its extended slots: |
|
455 // - a pointer to the module from which it was returned |
|
456 // - its index in the ordered list of exported functions |
|
457 AsmJSModule &module = FunctionToEnclosingModule(callee); |
|
458 |
|
459 // An exported function points to the code as well as the exported |
|
460 // function's signature, which implies the dynamic coercions performed on |
|
461 // the arguments. |
|
462 const AsmJSModule::ExportedFunction &func = FunctionToExportedFunction(callee, module); |
|
463 |
|
464 // The calling convention for an external call into asm.js is to pass an |
|
465 // array of 8-byte values where each value contains either a coerced int32 |
|
466 // (in the low word) or double value, with the coercions specified by the |
|
467 // asm.js signature. The external entry point unpacks this array into the |
|
468 // system-ABI-specified registers and stack memory and then calls into the |
|
469 // internal entry point. The return value is stored in the first element of |
|
470 // the array (which, therefore, must have length >= 1). |
|
471 |
|
472 js::Vector<uint64_t, 8> coercedArgs(cx); |
|
473 if (!coercedArgs.resize(Max<size_t>(1, func.numArgs()))) |
|
474 return false; |
|
475 |
|
476 RootedValue v(cx); |
|
477 for (unsigned i = 0; i < func.numArgs(); ++i) { |
|
478 v = i < callArgs.length() ? callArgs[i] : UndefinedValue(); |
|
479 switch (func.argCoercion(i)) { |
|
480 case AsmJS_ToInt32: |
|
481 if (!ToInt32(cx, v, (int32_t*)&coercedArgs[i])) |
|
482 return false; |
|
483 break; |
|
484 case AsmJS_ToNumber: |
|
485 if (!ToNumber(cx, v, (double*)&coercedArgs[i])) |
|
486 return false; |
|
487 break; |
|
488 case AsmJS_FRound: |
|
489 if (!RoundFloat32(cx, v, (float *)&coercedArgs[i])) |
|
490 return false; |
|
491 break; |
|
492 } |
|
493 } |
|
494 |
|
495 // An asm.js module is specialized to its heap's base address and length |
|
496 // which is normally immutable except for the neuter operation that occurs |
|
497 // when an ArrayBuffer is transfered. Throw an internal error if we're |
|
498 // about to run with a neutered heap. |
|
499 if (module.maybeHeapBufferObject() && module.maybeHeapBufferObject()->isNeutered()) { |
|
500 js_ReportOverRecursed(cx); |
|
501 return false; |
|
502 } |
|
503 |
|
504 { |
|
505 // Push an AsmJSActivation to describe the asm.js frames we're about to |
|
506 // push when running this module. Additionally, push a JitActivation so |
|
507 // that the optimized asm.js-to-Ion FFI call path (which we want to be |
|
508 // very fast) can avoid doing so. The JitActivation is marked as |
|
509 // inactive so stack iteration will skip over it. |
|
510 AsmJSActivation activation(cx, module); |
|
511 JitActivation jitActivation(cx, /* firstFrameIsConstructing = */ false, /* active */ false); |
|
512 |
|
513 // Call the per-exported-function trampoline created by GenerateEntry. |
|
514 AsmJSModule::CodePtr enter = module.entryTrampoline(func); |
|
515 if (!CALL_GENERATED_ASMJS(enter, coercedArgs.begin(), module.globalData())) |
|
516 return false; |
|
517 } |
|
518 |
|
519 switch (func.returnType()) { |
|
520 case AsmJSModule::Return_Void: |
|
521 callArgs.rval().set(UndefinedValue()); |
|
522 break; |
|
523 case AsmJSModule::Return_Int32: |
|
524 callArgs.rval().set(Int32Value(*(int32_t*)&coercedArgs[0])); |
|
525 break; |
|
526 case AsmJSModule::Return_Double: |
|
527 callArgs.rval().set(NumberValue(*(double*)&coercedArgs[0])); |
|
528 break; |
|
529 } |
|
530 |
|
531 return true; |
|
532 } |
|
533 |
|
534 static JSFunction * |
|
535 NewExportedFunction(JSContext *cx, const AsmJSModule::ExportedFunction &func, |
|
536 HandleObject moduleObj, unsigned exportIndex) |
|
537 { |
|
538 RootedPropertyName name(cx, func.name()); |
|
539 JSFunction *fun = NewFunction(cx, NullPtr(), CallAsmJS, func.numArgs(), |
|
540 JSFunction::NATIVE_FUN, cx->global(), name, |
|
541 JSFunction::ExtendedFinalizeKind); |
|
542 if (!fun) |
|
543 return nullptr; |
|
544 |
|
545 fun->setExtendedSlot(ASM_MODULE_SLOT, ObjectValue(*moduleObj)); |
|
546 fun->setExtendedSlot(ASM_EXPORT_INDEX_SLOT, Int32Value(exportIndex)); |
|
547 return fun; |
|
548 } |
|
549 |
|
550 static bool |
|
551 HandleDynamicLinkFailure(JSContext *cx, CallArgs args, AsmJSModule &module, HandlePropertyName name) |
|
552 { |
|
553 if (cx->isExceptionPending()) |
|
554 return false; |
|
555 |
|
556 uint32_t begin = module.offsetToEndOfUseAsm(); |
|
557 uint32_t end = module.funcEndBeforeCurly(); |
|
558 Rooted<JSFlatString*> src(cx, module.scriptSource()->substring(cx, begin, end)); |
|
559 if (!src) |
|
560 return false; |
|
561 |
|
562 RootedFunction fun(cx, NewFunction(cx, NullPtr(), nullptr, 0, JSFunction::INTERPRETED, |
|
563 cx->global(), name, JSFunction::FinalizeKind, |
|
564 TenuredObject)); |
|
565 if (!fun) |
|
566 return false; |
|
567 |
|
568 AutoNameVector formals(cx); |
|
569 formals.reserve(3); |
|
570 if (module.globalArgumentName()) |
|
571 formals.infallibleAppend(module.globalArgumentName()); |
|
572 if (module.importArgumentName()) |
|
573 formals.infallibleAppend(module.importArgumentName()); |
|
574 if (module.bufferArgumentName()) |
|
575 formals.infallibleAppend(module.bufferArgumentName()); |
|
576 |
|
577 CompileOptions options(cx); |
|
578 options.setOriginPrincipals(module.scriptSource()->originPrincipals()) |
|
579 .setCompileAndGo(false) |
|
580 .setNoScriptRval(false); |
|
581 |
|
582 SourceBufferHolder srcBuf(src->chars(), end - begin, SourceBufferHolder::NoOwnership); |
|
583 if (!frontend::CompileFunctionBody(cx, &fun, options, formals, srcBuf)) |
|
584 return false; |
|
585 |
|
586 // Call the function we just recompiled. |
|
587 args.setCallee(ObjectValue(*fun)); |
|
588 return Invoke(cx, args); |
|
589 } |
|
590 |
|
591 #ifdef MOZ_VTUNE |
|
592 static bool |
|
593 SendFunctionsToVTune(JSContext *cx, AsmJSModule &module) |
|
594 { |
|
595 uint8_t *base = module.codeBase(); |
|
596 |
|
597 for (unsigned i = 0; i < module.numProfiledFunctions(); i++) { |
|
598 const AsmJSModule::ProfiledFunction &func = module.profiledFunction(i); |
|
599 |
|
600 uint8_t *start = base + func.pod.startCodeOffset; |
|
601 uint8_t *end = base + func.pod.endCodeOffset; |
|
602 JS_ASSERT(end >= start); |
|
603 |
|
604 unsigned method_id = iJIT_GetNewMethodID(); |
|
605 if (method_id == 0) |
|
606 return false; |
|
607 |
|
608 JSAutoByteString bytes; |
|
609 const char *method_name = AtomToPrintableString(cx, func.name, &bytes); |
|
610 if (!method_name) |
|
611 return false; |
|
612 |
|
613 iJIT_Method_Load method; |
|
614 method.method_id = method_id; |
|
615 method.method_name = const_cast<char *>(method_name); |
|
616 method.method_load_address = (void *)start; |
|
617 method.method_size = unsigned(end - start); |
|
618 method.line_number_size = 0; |
|
619 method.line_number_table = nullptr; |
|
620 method.class_id = 0; |
|
621 method.class_file_name = nullptr; |
|
622 method.source_file_name = nullptr; |
|
623 |
|
624 iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED, (void *)&method); |
|
625 } |
|
626 |
|
627 return true; |
|
628 } |
|
629 #endif |
|
630 |
|
631 #ifdef JS_ION_PERF |
|
632 static bool |
|
633 SendFunctionsToPerf(JSContext *cx, AsmJSModule &module) |
|
634 { |
|
635 if (!PerfFuncEnabled()) |
|
636 return true; |
|
637 |
|
638 uintptr_t base = (uintptr_t) module.codeBase(); |
|
639 const char *filename = module.scriptSource()->filename(); |
|
640 |
|
641 for (unsigned i = 0; i < module.numProfiledFunctions(); i++) { |
|
642 const AsmJSModule::ProfiledFunction &func = module.profiledFunction(i); |
|
643 uintptr_t start = base + (unsigned long) func.pod.startCodeOffset; |
|
644 uintptr_t end = base + (unsigned long) func.pod.endCodeOffset; |
|
645 JS_ASSERT(end >= start); |
|
646 size_t size = end - start; |
|
647 |
|
648 JSAutoByteString bytes; |
|
649 const char *name = AtomToPrintableString(cx, func.name, &bytes); |
|
650 if (!name) |
|
651 return false; |
|
652 |
|
653 writePerfSpewerAsmJSFunctionMap(start, size, filename, func.pod.lineno, |
|
654 func.pod.columnIndex, name); |
|
655 } |
|
656 |
|
657 return true; |
|
658 } |
|
659 |
|
660 static bool |
|
661 SendBlocksToPerf(JSContext *cx, AsmJSModule &module) |
|
662 { |
|
663 if (!PerfBlockEnabled()) |
|
664 return true; |
|
665 |
|
666 unsigned long funcBaseAddress = (unsigned long) module.codeBase(); |
|
667 const char *filename = module.scriptSource()->filename(); |
|
668 |
|
669 for (unsigned i = 0; i < module.numPerfBlocksFunctions(); i++) { |
|
670 const AsmJSModule::ProfiledBlocksFunction &func = module.perfProfiledBlocksFunction(i); |
|
671 |
|
672 size_t size = func.pod.endCodeOffset - func.pod.startCodeOffset; |
|
673 |
|
674 JSAutoByteString bytes; |
|
675 const char *name = AtomToPrintableString(cx, func.name, &bytes); |
|
676 if (!name) |
|
677 return false; |
|
678 |
|
679 writePerfSpewerAsmJSBlocksMap(funcBaseAddress, func.pod.startCodeOffset, |
|
680 func.endInlineCodeOffset, size, filename, name, func.blocks); |
|
681 } |
|
682 |
|
683 return true; |
|
684 } |
|
685 #endif |
|
686 |
|
687 static bool |
|
688 SendModuleToAttachedProfiler(JSContext *cx, AsmJSModule &module) |
|
689 { |
|
690 #if defined(MOZ_VTUNE) |
|
691 if (IsVTuneProfilingActive() && !SendFunctionsToVTune(cx, module)) |
|
692 return false; |
|
693 #endif |
|
694 |
|
695 #if defined(JS_ION_PERF) |
|
696 if (module.numExportedFunctions() > 0) { |
|
697 size_t firstEntryCode = (size_t) module.entryTrampoline(module.exportedFunction(0)); |
|
698 writePerfSpewerAsmJSEntriesAndExits(firstEntryCode, (size_t) module.globalData() - firstEntryCode); |
|
699 } |
|
700 if (!SendBlocksToPerf(cx, module)) |
|
701 return false; |
|
702 if (!SendFunctionsToPerf(cx, module)) |
|
703 return false; |
|
704 #endif |
|
705 |
|
706 return true; |
|
707 } |
|
708 |
|
709 |
|
710 static JSObject * |
|
711 CreateExportObject(JSContext *cx, Handle<AsmJSModuleObject*> moduleObj) |
|
712 { |
|
713 AsmJSModule &module = moduleObj->module(); |
|
714 |
|
715 if (module.numExportedFunctions() == 1) { |
|
716 const AsmJSModule::ExportedFunction &func = module.exportedFunction(0); |
|
717 if (!func.maybeFieldName()) |
|
718 return NewExportedFunction(cx, func, moduleObj, 0); |
|
719 } |
|
720 |
|
721 gc::AllocKind allocKind = gc::GetGCObjectKind(module.numExportedFunctions()); |
|
722 RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_, allocKind)); |
|
723 if (!obj) |
|
724 return nullptr; |
|
725 |
|
726 for (unsigned i = 0; i < module.numExportedFunctions(); i++) { |
|
727 const AsmJSModule::ExportedFunction &func = module.exportedFunction(i); |
|
728 |
|
729 RootedFunction fun(cx, NewExportedFunction(cx, func, moduleObj, i)); |
|
730 if (!fun) |
|
731 return nullptr; |
|
732 |
|
733 JS_ASSERT(func.maybeFieldName() != nullptr); |
|
734 RootedId id(cx, NameToId(func.maybeFieldName())); |
|
735 RootedValue val(cx, ObjectValue(*fun)); |
|
736 if (!DefineNativeProperty(cx, obj, id, val, nullptr, nullptr, JSPROP_ENUMERATE)) |
|
737 return nullptr; |
|
738 } |
|
739 |
|
740 return obj; |
|
741 } |
|
742 |
|
743 static const unsigned MODULE_FUN_SLOT = 0; |
|
744 |
|
745 static AsmJSModuleObject & |
|
746 ModuleFunctionToModuleObject(JSFunction *fun) |
|
747 { |
|
748 return fun->getExtendedSlot(MODULE_FUN_SLOT).toObject().as<AsmJSModuleObject>(); |
|
749 } |
|
750 |
|
751 // Implements the semantics of an asm.js module function that has been successfully validated. |
|
752 static bool |
|
753 LinkAsmJS(JSContext *cx, unsigned argc, JS::Value *vp) |
|
754 { |
|
755 CallArgs args = CallArgsFromVp(argc, vp); |
|
756 |
|
757 // The LinkAsmJS builtin (created by NewAsmJSModuleFunction) is an extended |
|
758 // function and stores its module in an extended slot. |
|
759 RootedFunction fun(cx, &args.callee().as<JSFunction>()); |
|
760 Rooted<AsmJSModuleObject*> moduleObj(cx, &ModuleFunctionToModuleObject(fun)); |
|
761 |
|
762 // All ICache flushing of the module being linked has been inhibited under the |
|
763 // assumption that the module is flushed after dynamic linking (when the last code |
|
764 // mutation occurs). Thus, enter an AutoFlushICache context for the entire module |
|
765 // now. The module range is set below. |
|
766 AutoFlushICache afc("LinkAsmJS"); |
|
767 |
|
768 // When a module is linked, it is dynamically specialized to the given |
|
769 // arguments (buffer, ffis). Thus, if the module is linked again (it is just |
|
770 // a function so it can be called multiple times), we need to clone a new |
|
771 // module. |
|
772 if (moduleObj->module().isDynamicallyLinked()) { |
|
773 if (!CloneModule(cx, &moduleObj)) |
|
774 return false; |
|
775 } else { |
|
776 // CloneModule already calls setAutoFlushICacheRange internally before patching |
|
777 // the cloned module, so avoid calling twice. |
|
778 moduleObj->module().setAutoFlushICacheRange(); |
|
779 } |
|
780 |
|
781 AsmJSModule &module = moduleObj->module(); |
|
782 |
|
783 // Link the module by performing the link-time validation checks in the |
|
784 // asm.js spec and then patching the generated module to associate it with |
|
785 // the given heap (ArrayBuffer) and a new global data segment (the closure |
|
786 // state shared by the inner asm.js functions). |
|
787 if (!DynamicallyLinkModule(cx, args, module)) { |
|
788 // Linking failed, so reparse the entire asm.js module from scratch to |
|
789 // get normal interpreted bytecode which we can simply Invoke. Very slow. |
|
790 RootedPropertyName name(cx, fun->name()); |
|
791 return HandleDynamicLinkFailure(cx, args, module, name); |
|
792 } |
|
793 |
|
794 // Notify profilers so that asm.js generated code shows up with JS function |
|
795 // names and lines in native (i.e., not SPS) profilers. |
|
796 if (!SendModuleToAttachedProfiler(cx, module)) |
|
797 return false; |
|
798 |
|
799 // Link-time validation succeeded, so wrap all the exported functions with |
|
800 // CallAsmJS builtins that trampoline into the generated code. |
|
801 JSObject *obj = CreateExportObject(cx, moduleObj); |
|
802 if (!obj) |
|
803 return false; |
|
804 |
|
805 args.rval().set(ObjectValue(*obj)); |
|
806 return true; |
|
807 } |
|
808 |
|
809 JSFunction * |
|
810 js::NewAsmJSModuleFunction(ExclusiveContext *cx, JSFunction *origFun, HandleObject moduleObj) |
|
811 { |
|
812 RootedPropertyName name(cx, origFun->name()); |
|
813 |
|
814 JSFunction::Flags flags = origFun->isLambda() ? JSFunction::NATIVE_LAMBDA_FUN |
|
815 : JSFunction::NATIVE_FUN; |
|
816 JSFunction *moduleFun = NewFunction(cx, NullPtr(), LinkAsmJS, origFun->nargs(), |
|
817 flags, NullPtr(), name, |
|
818 JSFunction::ExtendedFinalizeKind, TenuredObject); |
|
819 if (!moduleFun) |
|
820 return nullptr; |
|
821 |
|
822 moduleFun->setExtendedSlot(MODULE_FUN_SLOT, ObjectValue(*moduleObj)); |
|
823 return moduleFun; |
|
824 } |
|
825 |
|
826 bool |
|
827 js::IsAsmJSModuleNative(js::Native native) |
|
828 { |
|
829 return native == LinkAsmJS; |
|
830 } |
|
831 |
|
832 static bool |
|
833 IsMaybeWrappedNativeFunction(const Value &v, Native native, JSFunction **fun = nullptr) |
|
834 { |
|
835 if (!v.isObject()) |
|
836 return false; |
|
837 |
|
838 JSObject *obj = CheckedUnwrap(&v.toObject()); |
|
839 if (!obj) |
|
840 return false; |
|
841 |
|
842 if (!obj->is<JSFunction>()) |
|
843 return false; |
|
844 |
|
845 if (fun) |
|
846 *fun = &obj->as<JSFunction>(); |
|
847 |
|
848 return obj->as<JSFunction>().maybeNative() == native; |
|
849 } |
|
850 |
|
851 bool |
|
852 js::IsAsmJSModule(JSContext *cx, unsigned argc, Value *vp) |
|
853 { |
|
854 CallArgs args = CallArgsFromVp(argc, vp); |
|
855 bool rval = args.hasDefined(0) && IsMaybeWrappedNativeFunction(args.get(0), LinkAsmJS); |
|
856 args.rval().set(BooleanValue(rval)); |
|
857 return true; |
|
858 } |
|
859 |
|
860 bool |
|
861 js::IsAsmJSModule(HandleFunction fun) |
|
862 { |
|
863 return fun->isNative() && fun->maybeNative() == LinkAsmJS; |
|
864 } |
|
865 |
|
866 JSString * |
|
867 js::AsmJSModuleToString(JSContext *cx, HandleFunction fun, bool addParenToLambda) |
|
868 { |
|
869 AsmJSModule &module = ModuleFunctionToModuleObject(fun).module(); |
|
870 |
|
871 uint32_t begin = module.funcStart(); |
|
872 uint32_t end = module.funcEndAfterCurly(); |
|
873 ScriptSource *source = module.scriptSource(); |
|
874 StringBuffer out(cx); |
|
875 |
|
876 // Whether the function has been created with a Function ctor |
|
877 bool funCtor = begin == 0 && end == source->length() && source->argumentsNotIncluded(); |
|
878 |
|
879 if (addParenToLambda && fun->isLambda() && !out.append("(")) |
|
880 return nullptr; |
|
881 |
|
882 if (!out.append("function ")) |
|
883 return nullptr; |
|
884 |
|
885 if (fun->atom() && !out.append(fun->atom())) |
|
886 return nullptr; |
|
887 |
|
888 if (funCtor) { |
|
889 // Functions created with the function constructor don't have arguments in their source. |
|
890 if (!out.append("(")) |
|
891 return nullptr; |
|
892 |
|
893 if (PropertyName *argName = module.globalArgumentName()) { |
|
894 if (!out.append(argName->chars(), argName->length())) |
|
895 return nullptr; |
|
896 } |
|
897 if (PropertyName *argName = module.importArgumentName()) { |
|
898 if (!out.append(", ") || !out.append(argName->chars(), argName->length())) |
|
899 return nullptr; |
|
900 } |
|
901 if (PropertyName *argName = module.bufferArgumentName()) { |
|
902 if (!out.append(", ") || !out.append(argName->chars(), argName->length())) |
|
903 return nullptr; |
|
904 } |
|
905 |
|
906 if (!out.append(") {\n")) |
|
907 return nullptr; |
|
908 } |
|
909 |
|
910 Rooted<JSFlatString*> src(cx, source->substring(cx, begin, end)); |
|
911 if (!src) |
|
912 return nullptr; |
|
913 |
|
914 if (module.strict()) { |
|
915 // We need to add "use strict" in the body right after the opening |
|
916 // brace. |
|
917 size_t bodyStart = 0, bodyEnd; |
|
918 |
|
919 // No need to test for functions created with the Function ctor as |
|
920 // these doesn't implicitly inherit the "use strict" context. Strict mode is |
|
921 // enabled for functions created with the Function ctor only if they begin with |
|
922 // the "use strict" directive, but these functions won't validate as asm.js |
|
923 // modules. |
|
924 |
|
925 ConstTwoByteChars chars(src->chars(), src->length()); |
|
926 if (!FindBody(cx, fun, chars, src->length(), &bodyStart, &bodyEnd)) |
|
927 return nullptr; |
|
928 |
|
929 if (!out.append(chars, bodyStart) || |
|
930 !out.append("\n\"use strict\";\n") || |
|
931 !out.append(chars + bodyStart, src->length() - bodyStart)) |
|
932 { |
|
933 return nullptr; |
|
934 } |
|
935 } else { |
|
936 if (!out.append(src->chars(), src->length())) |
|
937 return nullptr; |
|
938 } |
|
939 |
|
940 if (funCtor && !out.append("\n}")) |
|
941 return nullptr; |
|
942 |
|
943 if (addParenToLambda && fun->isLambda() && !out.append(")")) |
|
944 return nullptr; |
|
945 |
|
946 return out.finishString(); |
|
947 } |
|
948 |
|
949 bool |
|
950 js::IsAsmJSModuleLoadedFromCache(JSContext *cx, unsigned argc, Value *vp) |
|
951 { |
|
952 CallArgs args = CallArgsFromVp(argc, vp); |
|
953 |
|
954 JSFunction *fun; |
|
955 if (!args.hasDefined(0) || !IsMaybeWrappedNativeFunction(args[0], LinkAsmJS, &fun)) { |
|
956 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_USE_ASM_TYPE_FAIL, |
|
957 "argument passed to isAsmJSModuleLoadedFromCache is not a " |
|
958 "validated asm.js module"); |
|
959 return false; |
|
960 } |
|
961 |
|
962 bool loadedFromCache = ModuleFunctionToModuleObject(fun).module().loadedFromCache(); |
|
963 |
|
964 args.rval().set(BooleanValue(loadedFromCache)); |
|
965 return true; |
|
966 } |
|
967 |
|
968 bool |
|
969 js::IsAsmJSFunction(JSContext *cx, unsigned argc, Value *vp) |
|
970 { |
|
971 CallArgs args = CallArgsFromVp(argc, vp); |
|
972 bool rval = args.hasDefined(0) && IsMaybeWrappedNativeFunction(args[0], CallAsmJS); |
|
973 args.rval().set(BooleanValue(rval)); |
|
974 return true; |
|
975 } |
|
976 |
|
977 bool |
|
978 js::IsAsmJSFunction(HandleFunction fun) |
|
979 { |
|
980 return fun->isNative() && fun->maybeNative() == CallAsmJS; |
|
981 } |
|
982 |
|
983 JSString * |
|
984 js::AsmJSFunctionToString(JSContext *cx, HandleFunction fun) |
|
985 { |
|
986 AsmJSModule &module = FunctionToEnclosingModule(fun); |
|
987 const AsmJSModule::ExportedFunction &f = FunctionToExportedFunction(fun, module); |
|
988 uint32_t begin = module.funcStart() + f.startOffsetInModule(); |
|
989 uint32_t end = module.funcStart() + f.endOffsetInModule(); |
|
990 |
|
991 ScriptSource *source = module.scriptSource(); |
|
992 StringBuffer out(cx); |
|
993 |
|
994 // asm.js functions cannot have been created with a Function constructor |
|
995 // as they belong within a module. |
|
996 JS_ASSERT(!(begin == 0 && end == source->length() && source->argumentsNotIncluded())); |
|
997 |
|
998 if (!out.append("function ")) |
|
999 return nullptr; |
|
1000 |
|
1001 Rooted<JSFlatString*> src(cx, source->substring(cx, begin, end)); |
|
1002 if (!src) |
|
1003 return nullptr; |
|
1004 |
|
1005 if (!out.append(src->chars(), src->length())) |
|
1006 return nullptr; |
|
1007 |
|
1008 return out.finishString(); |
|
1009 } |