Bug 878399: Implement toSource / toString for asm.js functions; r=luke
authorBenjamin Bouvier <benj@benj.me>
Wed, 02 Apr 2014 19:52:00 +0200
changeset 195239 2b74aa0e829cbe363e0b0a2012c89ae862e60d93
parent 195238 7891eb941f804cc4fff7af87ce2337cb07c9c695
child 195240 4be0c027d10646c04947452737ed17e13be03008
push idunknown
push userunknown
push dateunknown
reviewersluke
bugs878399
milestone31.0a1
Bug 878399: Implement toSource / toString for asm.js functions; r=luke
js/src/jit-test/tests/asm.js/testSource.js
js/src/jit/AsmJS.cpp
js/src/jit/AsmJSLink.cpp
js/src/jit/AsmJSLink.h
js/src/jit/AsmJSModule.h
js/src/jsfun.cpp
--- a/js/src/jit-test/tests/asm.js/testSource.js
+++ b/js/src/jit-test/tests/asm.js/testSource.js
@@ -231,8 +231,81 @@ assertEq(f3.toSource(), "(function anony
 if (isAsmJSCompilationAvailable() && isCachingEnabled()) {
     var m = new Function('glob', 'ffi', 'heap', bodyOnly);
     assertEq(isAsmJSModuleLoadedFromCache(m), true);
     assertEq(m.toString(), "function anonymous(glob, ffi, heap) {\n" + bodyOnly + "\n}");
     assertEq(m.toSource(), "(function anonymous(glob, ffi, heap) {\n" + bodyOnly + "\n})");
 }
 
 })();
+
+/* Functions */
+(function() {
+
+var noSrc = "function noArgument() {\n\
+    return 42;\n\
+}"
+var oneSrc = "function oneArgument(x) {\n\
+    x = x | 0;\n\
+    return x + 1 | 0;\n\
+}";
+var twoSrc = "function twoArguments(x, y) {\n\
+    x = x | 0;\n\
+    y = y | 0;\n\
+    return x + y | 0;\n\
+}";
+var threeSrc = "function threeArguments(a, b, c) {\n\
+    a = +a;\n\
+    b = +b;\n\
+    c = +c;\n\
+    return +(+(a * b) + c);\n\
+}";
+
+var funcBody = '\n\
+    "use asm";\n'
+    + noSrc + '\n'
+    + oneSrc + '\n'
+    + twoSrc + '\n'
+    + threeSrc + '\n'
+    + 'return {\n\
+    no: noArgument,\n\
+    one: oneArgument,\n\
+    two: twoArguments,\n\
+    three: threeArguments\n\
+    }';
+
+var g = new Function(funcBody);
+var moduleG = g();
+
+function checkFuncSrc(m) {
+    assertEq(m.no.toString(), noSrc);
+    assertEq(m.no.toSource(), noSrc);
+
+    assertEq(m.one.toString(), oneSrc);
+    assertEq(m.one.toSource(), oneSrc);
+
+    assertEq(m.two.toString(), twoSrc);
+    assertEq(m.two.toSource(), twoSrc);
+
+    assertEq(m.three.toString(), threeSrc);
+    assertEq(m.three.toSource(), threeSrc);
+}
+checkFuncSrc(moduleG);
+
+if (isAsmJSCompilationAvailable() && isCachingEnabled()) {
+    var g2 = new Function(funcBody);
+    assertEq(isAsmJSModuleLoadedFromCache(g2), true);
+    m = g2();
+    checkFuncSrc(m);
+
+    var moduleDecl = 'function g3() {' + funcBody + '}';
+    eval(moduleDecl);
+    m = g3();
+    assertEq(isAsmJSModuleLoadedFromCache(g3), false);
+    checkFuncSrc(m);
+
+    eval('var x = 42;' + moduleDecl);
+    m = g3();
+    assertEq(isAsmJSModuleLoadedFromCache(g3), true);
+    checkFuncSrc(m);
+}
+
+})();
--- a/js/src/jit/AsmJS.cpp
+++ b/js/src/jit/AsmJS.cpp
@@ -824,29 +824,39 @@ typedef js::Vector<MBasicBlock*,8> Block
 class MOZ_STACK_CLASS ModuleCompiler
 {
   public:
     class Func
     {
         PropertyName *name_;
         bool defined_;
         uint32_t srcOffset_;
+        uint32_t endOffset_;
         Signature sig_;
         Label *code_;
         unsigned compileTime_;
 
       public:
         Func(PropertyName *name, Signature &&sig, Label *code)
-          : name_(name), defined_(false), srcOffset_(0), sig_(Move(sig)), code_(code), compileTime_(0)
+          : name_(name), defined_(false), srcOffset_(0), endOffset_(0), sig_(Move(sig)),
+            code_(code), compileTime_(0)
         {}
 
         PropertyName *name() const { return name_; }
+
         bool defined() const { return defined_; }
-        void define(uint32_t so) { JS_ASSERT(!defined_); defined_ = true; srcOffset_ = so; }
+        void finish(uint32_t start, uint32_t end) {
+            JS_ASSERT(!defined_);
+            defined_ = true;
+            srcOffset_ = start;
+            endOffset_ = end;
+        }
+
         uint32_t srcOffset() const { JS_ASSERT(defined_); return srcOffset_; }
+        uint32_t endOffset() const { JS_ASSERT(defined_); return endOffset_; }
         Signature &sig() { return sig_; }
         const Signature &sig() const { return sig_; }
         Label *code() const { return code_; }
         unsigned compileTime() const { return compileTime_; }
         void accumulateCompileTime(unsigned ms) { compileTime_ += ms; }
     };
 
     class Global
@@ -1218,16 +1228,17 @@ class MOZ_STACK_CLASS ModuleCompiler
 
     ExclusiveContext *cx() const { return cx_; }
     AsmJSParser &parser() const { return parser_; }
     MacroAssembler &masm() { return masm_; }
     Label &stackOverflowLabel() { return stackOverflowLabel_; }
     Label &interruptLabel() { return interruptLabel_; }
     bool hasError() const { return errorString_ != nullptr; }
     const AsmJSModule &module() const { return *module_.get(); }
+    uint32_t moduleStart() const { return module_->funcStart(); }
 
     ParseNode *moduleFunctionNode() const { return moduleFunctionNode_; }
     PropertyName *moduleFunctionName() const { return moduleFunctionName_; }
 
     const Global *lookupGlobal(PropertyName *name) const {
         if (GlobalMap::Ptr p = globals_.lookup(name))
             return p->value();
         return nullptr;
@@ -1387,17 +1398,18 @@ class MOZ_STACK_CLASS ModuleCompiler
         const VarTypeVector &args = func->sig().args();
         if (!argCoercions.resize(args.length()))
             return false;
         for (unsigned i = 0; i < args.length(); i++)
             argCoercions[i] = args[i].toCoercion();
         AsmJSModule::ReturnType retType = func->sig().retType().toModuleReturnType();
         uint32_t line, column;
         parser_.tokenStream.srcCoords.lineNumAndColumnIndex(func->srcOffset(), &line, &column);
-        return module_->addExportedFunction(func->name(), line, column, maybeFieldName,
+        return module_->addExportedFunction(func->name(), line, column,
+                                            func->srcOffset(), func->endOffset(), maybeFieldName,
                                             Move(argCoercions), retType);
     }
     bool addExit(unsigned ffiIndex, PropertyName *name, Signature &&sig, unsigned *exitIndex) {
         ExitDescriptor exitDescriptor(name, Move(sig));
         ExitMap::AddPtr p = exits_.lookupForAdd(exitDescriptor);
         if (p) {
             *exitIndex = p->value();
             return true;
@@ -5418,17 +5430,26 @@ CheckFunction(ModuleCompiler &m, LifoAll
     Signature sig(Move(argTypes), retType);
     ModuleCompiler::Func *func = nullptr;
     if (!CheckFunctionSignature(m, fn, Move(sig), FunctionName(fn), &func))
         return false;
 
     if (func->defined())
         return m.failName(fn, "function '%s' already defined", FunctionName(fn));
 
-    func->define(fn->pn_pos.begin);
+    uint32_t funcBegin = fn->pn_pos.begin;
+    uint32_t funcEnd = fn->pn_pos.end;
+    // The begin/end char range is relative to the beginning of the module,
+    // hence the assertions.
+    JS_ASSERT(funcBegin > m.moduleStart());
+    JS_ASSERT(funcEnd > m.moduleStart());
+    funcBegin -= m.moduleStart();
+    funcEnd -= m.moduleStart();
+    func->finish(funcBegin, funcEnd);
+
     func->accumulateCompileTime((PRMJ_Now() - before) / PRMJ_USEC_PER_MSEC);
 
     m.parser().release(mark);
 
     // Copy the cumulative minimum heap size constraint to the MIR for use in analysis.  The length
     // is also constrained to particular lengths, so firstly round up - a larger 'heap required
     // length' can help range analysis to prove that bounds checks are not needed.
     uint32_t len = js::RoundUpToNextValidAsmJSHeapLength(m.minHeapLength());
--- a/js/src/jit/AsmJSLink.cpp
+++ b/js/src/jit/AsmJSLink.cpp
@@ -319,35 +319,53 @@ DynamicallyLinkModule(JSContext *cx, Cal
         module.exitIndexToGlobalDatum(i).fun = &ffis[module.exit(i).ffiIndex()]->as<JSFunction>();
 
     return true;
 }
 
 static const unsigned ASM_MODULE_SLOT = 0;
 static const unsigned ASM_EXPORT_INDEX_SLOT = 1;
 
+static unsigned
+FunctionToExportedFunctionIndex(HandleFunction fun)
+{
+    Value v = fun->getExtendedSlot(ASM_EXPORT_INDEX_SLOT);
+    return v.toInt32();
+}
+
+static const AsmJSModule::ExportedFunction &
+FunctionToExportedFunction(HandleFunction fun, AsmJSModule &module)
+{
+    unsigned funIndex = FunctionToExportedFunctionIndex(fun);
+    return module.exportedFunction(funIndex);
+}
+
+static AsmJSModule &
+FunctionToEnclosingModule(HandleFunction fun)
+{
+    return fun->getExtendedSlot(ASM_MODULE_SLOT).toObject().as<AsmJSModuleObject>().module();
+}
+
 // The JSNative for the functions nested in an asm.js module. Calling this
 // native will trampoline into generated code.
 static bool
 CallAsmJS(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs callArgs = CallArgsFromVp(argc, vp);
     RootedFunction callee(cx, &callArgs.callee().as<JSFunction>());
 
     // An asm.js function stores, in its extended slots:
     //  - a pointer to the module from which it was returned
     //  - its index in the ordered list of exported functions
-    RootedObject moduleObj(cx, &callee->getExtendedSlot(ASM_MODULE_SLOT).toObject());
-    AsmJSModule &module = moduleObj->as<AsmJSModuleObject>().module();
+    AsmJSModule &module = FunctionToEnclosingModule(callee);
 
     // An exported function points to the code as well as the exported
     // function's signature, which implies the dynamic coercions performed on
     // the arguments.
-    unsigned exportIndex = callee->getExtendedSlot(ASM_EXPORT_INDEX_SLOT).toInt32();
-    const AsmJSModule::ExportedFunction &func = module.exportedFunction(exportIndex);
+    const AsmJSModule::ExportedFunction &func = FunctionToExportedFunction(callee, module);
 
     // An asm.js module is specialized to its heap's base address and length
     // which is normally immutable except for the neuter operation that occurs
     // when an ArrayBuffer is transfered. Throw an internal error if we try to
     // run with a neutered heap.
     if (module.maybeHeapBufferObject() && module.maybeHeapBufferObject()->isNeutered()) {
         js_ReportOverRecursed(cx);
         return false;
@@ -385,16 +403,17 @@ CallAsmJS(JSContext *cx, unsigned argc, 
     }
 
     {
         // Push an AsmJSActivation to describe the asm.js frames we're about to
         // push when running this module. Additionally, push a JitActivation so
         // that the optimized asm.js-to-Ion FFI call path (which we want to be
         // very fast) can avoid doing so. The JitActivation is marked as
         // inactive so stack iteration will skip over it.
+        unsigned exportIndex = FunctionToExportedFunctionIndex(callee);
         AsmJSActivation activation(cx, module, exportIndex);
         JitActivation jitActivation(cx, /* firstFrameIsConstructing = */ false, /* active */ false);
 
         // Call the per-exported-function trampoline created by GenerateEntry.
         AsmJSModule::CodePtr enter = module.entryTrampoline(func);
         if (!CALL_GENERATED_ASMJS(enter, coercedArgs.begin(), module.globalData()))
             return false;
     }
@@ -815,8 +834,42 @@ js::IsAsmJSModuleLoadedFromCache(JSConte
 bool
 js::IsAsmJSFunction(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     bool rval = args.hasDefined(0) && IsMaybeWrappedNativeFunction(args[0], CallAsmJS);
     args.rval().set(BooleanValue(rval));
     return true;
 }
+
+bool
+js::IsAsmJSFunction(HandleFunction fun)
+{
+    return fun->isNative() && fun->maybeNative() == CallAsmJS;
+}
+
+JSString *
+js::AsmJSFunctionToString(JSContext *cx, HandleFunction fun)
+{
+    AsmJSModule &module = FunctionToEnclosingModule(fun);
+    const AsmJSModule::ExportedFunction &f = FunctionToExportedFunction(fun, module);
+    uint32_t begin = module.funcStart() + f.startOffsetInModule();
+    uint32_t end = module.funcStart() + f.endOffsetInModule();
+
+    ScriptSource *source = module.scriptSource();
+    StringBuffer out(cx);
+
+    // asm.js functions cannot have been created with a Function constructor
+    // as they belong within a module.
+    JS_ASSERT(!(begin == 0 && end == source->length() && source->argumentsNotIncluded()));
+
+    if (!out.append("function "))
+        return nullptr;
+
+    Rooted<JSFlatString*> src(cx, source->substring(cx, begin, end));
+    if (!src)
+        return nullptr;
+
+    if (!out.append(src->chars(), src->length()))
+        return nullptr;
+
+    return out.finishString();
+}
--- a/js/src/jit/AsmJSLink.h
+++ b/js/src/jit/AsmJSLink.h
@@ -36,16 +36,21 @@ AsmJSModuleToString(JSContext *cx, Handl
 // loaded directly from the cache (and hence was validated previously).
 extern bool
 IsAsmJSModuleLoadedFromCache(JSContext *cx, unsigned argc, Value *vp);
 
 // Return whether the given value is a nested function in an asm.js module that
 // has been both compile- and link-time validated.
 extern bool
 IsAsmJSFunction(JSContext *cx, unsigned argc, JS::Value *vp);
+extern bool
+IsAsmJSFunction(HandleFunction fun);
+
+extern JSString *
+AsmJSFunctionToString(JSContext *cx, HandleFunction fun);
 
 #else // JS_ION
 
 inline bool
 IsAsmJSModuleNative(JSNative native)
 {
     return false;
 }
--- a/js/src/jit/AsmJSModule.h
+++ b/js/src/jit/AsmJSModule.h
@@ -218,33 +218,41 @@ class AsmJSModule
         PropertyName *name_;
         PropertyName *maybeFieldName_;
         ArgCoercionVector argCoercions_;
         struct Pod {
             ReturnType returnType_;
             uint32_t codeOffset_;
             uint32_t line_;
             uint32_t column_;
+            // These two fields are offsets to the beginning of the ScriptSource
+            // of the module, and thus invariant under serialization (unlike
+            // absolute offsets into ScriptSource).
+            uint32_t startOffsetInModule_;
+            uint32_t endOffsetInModule_;
         } pod;
 
         friend class AsmJSModule;
 
         ExportedFunction(PropertyName *name,
                          uint32_t line, uint32_t column,
+                         uint32_t startOffsetInModule, uint32_t endOffsetInModule,
                          PropertyName *maybeFieldName,
                          ArgCoercionVector &&argCoercions,
                          ReturnType returnType)
         {
             name_ = name;
             maybeFieldName_ = maybeFieldName;
             argCoercions_ = mozilla::Move(argCoercions);
             pod.returnType_ = returnType;
             pod.codeOffset_ = UINT32_MAX;
             pod.line_ = line;
             pod.column_ = column;
+            pod.startOffsetInModule_ = startOffsetInModule;
+            pod.endOffsetInModule_ = endOffsetInModule;
             JS_ASSERT_IF(maybeFieldName_, name_->isTenured());
         }
 
         void trace(JSTracer *trc) {
             MarkStringUnbarriered(trc, &name_, "asm.js export name");
             if (maybeFieldName_)
                 MarkStringUnbarriered(trc, &maybeFieldName_, "asm.js export field");
         }
@@ -267,16 +275,22 @@ class AsmJSModule
             return name_;
         }
         uint32_t line() const {
             return pod.line_;
         }
         uint32_t column() const {
             return pod.column_;
         }
+        uint32_t startOffsetInModule() const {
+            return pod.startOffsetInModule_;
+        }
+        uint32_t endOffsetInModule() const {
+            return pod.endOffsetInModule_;
+        }
         PropertyName *maybeFieldName() const {
             return maybeFieldName_;
         }
         unsigned numArgs() const {
             return argCoercions_.length();
         }
         AsmJSCoercion argCoercion(unsigned i) const {
             return argCoercions_[i];
@@ -494,17 +508,17 @@ class AsmJSModule
      */
     uint32_t funcStart() const {
         return funcStart_;
     }
     uint32_t offsetToEndOfUseAsm() const {
         return offsetToEndOfUseAsm_;
     }
     void initFuncEnd(uint32_t endBeforeCurly, uint32_t endAfterCurly) {
-        JS_ASSERT(endBeforeCurly>= offsetToEndOfUseAsm_);
+        JS_ASSERT(endBeforeCurly >= offsetToEndOfUseAsm_);
         JS_ASSERT(endAfterCurly >= offsetToEndOfUseAsm_);
         pod.funcLength_ = endBeforeCurly - funcStart_;
         pod.funcLengthWithRightBrace_ = endAfterCurly - funcStart_;
     }
     uint32_t funcEndBeforeCurly() const {
         return funcStart_ + pod.funcLength_;
     }
     uint32_t funcEndAfterCurly() const {
@@ -577,21 +591,25 @@ class AsmJSModule
         *exitIndex = unsigned(exits_.length());
         return exits_.append(Exit(ffiIndex, globalDataOffset));
     }
     bool addFunctionCounts(jit::IonScriptCounts *counts) {
         return functionCounts_.append(counts);
     }
 
     bool addExportedFunction(PropertyName *name, uint32_t line, uint32_t column,
+                             uint32_t srcStart, uint32_t srcEnd,
                              PropertyName *maybeFieldName,
                              ArgCoercionVector &&argCoercions,
                              ReturnType returnType)
     {
-        ExportedFunction func(name, line, column, maybeFieldName, mozilla::Move(argCoercions), returnType);
+        ExportedFunction func(name, line, column, srcStart, srcEnd, maybeFieldName,
+                              mozilla::Move(argCoercions), returnType);
+        if (exports_.length() >= UINT32_MAX)
+            return false;
         return exports_.append(mozilla::Move(func));
     }
     unsigned numExportedFunctions() const {
         return exports_.length();
     }
     const ExportedFunction &exportedFunction(unsigned i) const {
         return exports_[i];
     }
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -688,16 +688,18 @@ js::FunctionToString(JSContext *cx, Hand
         JSObject *target = fun->getBoundFunctionTarget();
         RootedFunction targetFun(cx, &target->as<JSFunction>());
         JS_ASSERT(targetFun->isArrow());
         return FunctionToString(cx, targetFun, bodyOnly, lambdaParen);
     }
 
     if (IsAsmJSModule(fun))
         return AsmJSModuleToString(cx, fun, !lambdaParen);
+    if (IsAsmJSFunction(fun))
+        return AsmJSFunctionToString(cx, fun);
 
     StringBuffer out(cx);
     RootedScript script(cx);
 
     if (fun->hasScript()) {
         script = fun->nonLazyScript();
         if (script->isGeneratorExp()) {
             if ((!bodyOnly && !out.append("function genexp() {")) ||