Bug 1459067 - Part 2: Implement LazyFunctionExpression and LazyFunctionDeclaration for BinAST. (r=arai)
☠☠ backed out by 63ea63571271 ☠ ☠
authorEric Faust <efaustbmo@gmail.com>
Mon, 01 Oct 2018 20:41:48 -0700
changeset 494879 b3dbe062fb3dcdbb33067352cc80ab85fd0249a6
parent 494878 edada9212b5b707c60cc3989588561f3df03f525
child 494880 211ea8e33f7ec9046580e5f1caaefdb04bb535d6
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersarai
bugs1459067
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1459067 - Part 2: Implement LazyFunctionExpression and LazyFunctionDeclaration for BinAST. (r=arai)
js/src/frontend/BinSource-auto.cpp
js/src/frontend/BinSource.cpp
js/src/frontend/BinSource.h
js/src/frontend/BinSource.yaml
js/src/frontend/BinTokenReaderBase.cpp
js/src/frontend/BinTokenReaderBase.h
js/src/frontend/BinTokenReaderMultipart.cpp
js/src/frontend/BinTokenReaderTester.cpp
js/src/frontend/BytecodeCompiler.cpp
js/src/frontend/BytecodeCompiler.h
js/src/frontend/BytecodeEmitter.cpp
js/src/jsapi-tests/testBinASTReader.cpp
js/src/shell/js.cpp
js/src/vm/JSFunction.cpp
js/src/vm/JSScript.cpp
js/src/vm/JSScript.h
--- a/js/src/frontend/BinSource-auto.cpp
+++ b/js/src/frontend/BinSource-auto.cpp
@@ -6045,17 +6045,65 @@ BinASTParser<Tok>::parseLazyFunctionDecl
     MOZ_TRY(guard.done());
 
     return result;
 }
 
 template<typename Tok> JS::Result<ParseNode*>
 BinASTParser<Tok>::parseInterfaceLazyFunctionDeclaration(const size_t start, const BinKind kind, const BinFields& fields)
 {
-    return raiseError("FIXME: Not implemented yet (LazyFunctionDeclaration)");
+    MOZ_ASSERT(kind == BinKind::LazyFunctionDeclaration);
+    BINJS_TRY(CheckRecursionLimit(cx_));
+
+#if defined(DEBUG)
+    const BinField expected_fields[7] = { BinField::IsAsync, BinField::IsGenerator, BinField::Name, BinField::Length, BinField::Directives, BinField::ContentsSkip, BinField::Contents };
+    MOZ_TRY(tokenizer_->checkFields(kind, fields, expected_fields));
+#endif // defined(DEBUG)
+    const auto syntax = FunctionSyntaxKind::Statement;
+
+    BINJS_MOZ_TRY_DECL(isAsync, tokenizer_->readBool());
+
+    BINJS_MOZ_TRY_DECL(isGenerator, tokenizer_->readBool());
+
+    BINJS_MOZ_TRY_DECL(name, parseBindingIdentifier());
+
+    BINJS_MOZ_TRY_DECL(length, tokenizer_->readUnsignedLong());
+
+    BINJS_MOZ_TRY_DECL(directives, parseListOfDirective());
+
+    BINJS_MOZ_TRY_DECL(contentsSkip, tokenizer_->readSkippableSubTree());
+    // Don't parse the contents until we delazify.
+
+    BINJS_MOZ_TRY_DECL(funbox, buildFunctionBox(
+        isGenerator ? GeneratorKind::Generator
+                    : GeneratorKind::NotGenerator,
+        isAsync ? FunctionAsyncKind::AsyncFunction
+                : FunctionAsyncKind::SyncFunction,
+        syntax, name));
+
+    forceStrictIfNecessary(funbox, directives);
+
+    RootedFunction fun(cx_, funbox->function());
+
+    // TODO: This will become incorrect in the face of ES6 features.
+    fun->setArgCount(length);
+
+    auto skipStart = contentsSkip.startOffset();
+    BINJS_TRY_DECL(lazy, LazyScript::Create(cx_, fun, sourceObject_, parseContext_->closedOverBindingsForLazy(), parseContext_->innerFunctionsForLazy,
+                                            skipStart, skipStart + contentsSkip.length(),
+                                            skipStart, 0, skipStart, ParseGoal::Script));
+
+    if (funbox->strict()) {
+        lazy->setStrict();
+    }
+    lazy->setIsBinAST();
+    funbox->function()->initLazyScript(lazy);
+
+    BINJS_MOZ_TRY_DECL(result, makeEmptyFunctionNode(skipStart, kind, funbox));
+    return result;
 }
 
 
 /*
  interface LazyFunctionExpression : Node {
     bool isAsync;
     bool isGenerator;
     BindingIdentifier? name;
@@ -6080,17 +6128,65 @@ BinASTParser<Tok>::parseLazyFunctionExpr
     MOZ_TRY(guard.done());
 
     return result;
 }
 
 template<typename Tok> JS::Result<ParseNode*>
 BinASTParser<Tok>::parseInterfaceLazyFunctionExpression(const size_t start, const BinKind kind, const BinFields& fields)
 {
-    return raiseError("FIXME: Not implemented yet (LazyFunctionExpression)");
+    MOZ_ASSERT(kind == BinKind::LazyFunctionExpression);
+    BINJS_TRY(CheckRecursionLimit(cx_));
+
+#if defined(DEBUG)
+    const BinField expected_fields[7] = { BinField::IsAsync, BinField::IsGenerator, BinField::Name, BinField::Length, BinField::Directives, BinField::ContentsSkip, BinField::Contents };
+    MOZ_TRY(tokenizer_->checkFields(kind, fields, expected_fields));
+#endif // defined(DEBUG)
+    const auto syntax = FunctionSyntaxKind::Expression;
+
+    BINJS_MOZ_TRY_DECL(isAsync, tokenizer_->readBool());
+
+    BINJS_MOZ_TRY_DECL(isGenerator, tokenizer_->readBool());
+
+    BINJS_MOZ_TRY_DECL(name, parseOptionalBindingIdentifier());
+
+    BINJS_MOZ_TRY_DECL(length, tokenizer_->readUnsignedLong());
+
+    BINJS_MOZ_TRY_DECL(directives, parseListOfDirective());
+
+    BINJS_MOZ_TRY_DECL(contentsSkip, tokenizer_->readSkippableSubTree());
+    // Don't parse the contents until we delazify.
+
+    BINJS_MOZ_TRY_DECL(funbox, buildFunctionBox(
+        isGenerator ? GeneratorKind::Generator
+                    : GeneratorKind::NotGenerator,
+        isAsync ? FunctionAsyncKind::AsyncFunction
+                : FunctionAsyncKind::SyncFunction,
+        syntax, name));
+
+    forceStrictIfNecessary(funbox, directives);
+
+    RootedFunction fun(cx_, funbox->function());
+
+    // TODO: This will become incorrect in the face of ES6 features.
+    fun->setArgCount(length);
+
+    auto skipStart = contentsSkip.startOffset();
+    BINJS_TRY_DECL(lazy, LazyScript::Create(cx_, fun, sourceObject_, parseContext_->closedOverBindingsForLazy(), parseContext_->innerFunctionsForLazy,
+                                            skipStart, skipStart + contentsSkip.length(),
+                                            skipStart, 0, skipStart, ParseGoal::Script));
+
+    if (funbox->strict()) {
+        lazy->setStrict();
+    }
+    lazy->setIsBinAST();
+    funbox->function()->initLazyScript(lazy);
+
+    BINJS_MOZ_TRY_DECL(result, makeEmptyFunctionNode(skipStart, kind, funbox));
+    return result;
 }
 
 
 /*
  interface LazyGetter : Node {
     PropertyName name;
     FrozenArray<Directive> directives;
     GetterContents contents;
--- a/js/src/frontend/BinSource.cpp
+++ b/js/src/frontend/BinSource.cpp
@@ -6,16 +6,17 @@
 
 #include "frontend/BinSource.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Casting.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Move.h"
 #include "mozilla/PodOperations.h"
+#include "mozilla/ScopeExit.h"
 #include "mozilla/Vector.h"
 
 #include "frontend/BinSource-macros.h"
 #include "frontend/BinTokenReaderTester.h"
 #include "frontend/FullParseHandler.h"
 #include "frontend/Parser.h"
 #include "frontend/SharedContext.h"
 
@@ -70,27 +71,31 @@
 //
 // They should be treated lazily (whenever we open a subscope), like bindings.
 
 namespace js {
 namespace frontend {
 
 using UsedNamePtr = UsedNameTracker::UsedNameMap::Ptr;
 
-BinASTParserBase::BinASTParserBase(JSContext* cx, LifoAlloc& alloc, UsedNameTracker& usedNames)
+BinASTParserBase::BinASTParserBase(JSContext* cx, LifoAlloc& alloc, UsedNameTracker& usedNames,
+                                   HandleScriptSourceObject sourceObject, Handle<LazyScript*> lazyScript)
   : AutoGCRooter(cx, AutoGCRooter::Tag::BinParser)
   , cx_(cx)
   , alloc_(alloc)
   , traceListHead_(nullptr)
   , usedNames_(usedNames)
   , nodeAlloc_(cx, alloc)
   , keepAtoms_(cx)
+  , sourceObject_(cx, sourceObject)
+  , lazyScript_(cx, lazyScript)
   , parseContext_(nullptr)
   , factory_(cx, alloc, nullptr, SourceKind::Binary)
 {
+    MOZ_ASSERT_IF(lazyScript, lazyScript->isBinAST());
     cx->frontendCollectionPool().addActiveCompilation();
     tempPoolMark_ = alloc.mark();
 }
 
 BinASTParserBase::~BinASTParserBase()
 {
     alloc_.release(tempPoolMark_);
 
@@ -149,16 +154,64 @@ BinASTParser<Tok>::parseAux(GlobalShared
     if (!bindings) {
         return cx_->alreadyReportedError();
     }
     globalsc->bindings = *bindings;
 
     return result; // Magic conversion to Ok.
 }
 
+template<typename Tok> JS::Result<ParseNode*>
+BinASTParser<Tok>::parseLazyFunction(const uint8_t* start, const size_t firstOffset, const size_t len)
+{
+    MOZ_ASSERT(lazyScript_);
+
+    tokenizer_.emplace(cx_, this, start, len);
+
+    // Re-initialize the tokenizer
+    mozilla::DebugOnly<bool> success = tokenizer_->readHeader().isOk();
+    MOZ_ASSERT(success);
+
+    tokenizer_->seek(firstOffset);
+
+    // For now, only function declarations and function expression are supported.
+    JSFunction* func = lazyScript_->functionNonDelazifying();
+    bool isExpr = func->isLambda();
+    MOZ_ASSERT(func->kind() == JSFunction::FunctionKind::NormalFunction);
+
+    // Poison the tokenizer when we leave to ensure that it's not used again by accident.
+    auto onExit = mozilla::MakeScopeExit([&]() { poison(); });
+
+    // TODO: This should be actually shared with the auto-generated version.
+
+    auto syntaxKind = isExpr ? FunctionSyntaxKind::Expression : FunctionSyntaxKind::Statement;
+    BINJS_MOZ_TRY_DECL(funbox, buildFunctionBox(lazyScript_->generatorKind(),
+                                                lazyScript_->asyncKind(), syntaxKind, nullptr));
+
+    // Push a new ParseContext. It will be used to parse `scope`, the arguments, the function.
+    BinParseContext funpc(cx_, this, funbox, /* newDirectives = */ nullptr);
+    BINJS_TRY(funpc.init());
+    parseContext_->functionScope().useAsVarScope(parseContext_);
+    MOZ_ASSERT(parseContext_->isFunctionBox());
+
+    ParseContext::Scope lexicalScope(cx_, parseContext_, usedNames_);
+    BINJS_TRY(lexicalScope.init(parseContext_));
+    ListNode* params;
+    ListNode* tmpBody;
+    auto parseFunc = isExpr ? &BinASTParser::parseFunctionExpressionContents
+                            : &BinASTParser::parseFunctionOrMethodContents;
+    MOZ_TRY((this->*parseFunc)(func->nargs(), &params, &tmpBody));
+
+    BINJS_TRY_DECL(lexicalScopeData, NewLexicalScopeData(cx_, lexicalScope, alloc_, parseContext_));
+    BINJS_TRY_DECL(body, factory_.newLexicalScope(*lexicalScopeData, tmpBody));
+
+    auto binKind = isExpr ? BinKind::LazyFunctionExpression : BinKind::LazyFunctionDeclaration;
+    return buildFunction(firstOffset, binKind, nullptr, params, body, funbox);
+}
+
 template<typename Tok> void
 BinASTParser<Tok>::forceStrictIfNecessary(FunctionBox* funbox, ListNode* directives)
 {
     JSAtom* useStrict = cx_->names().useStrict;
 
     for (const ParseNode* directive : directives->contents()) {
         if (directive->as<NameNode>().atom() == useStrict) {
             funbox->strictScript = true;
@@ -168,64 +221,91 @@ BinASTParser<Tok>::forceStrictIfNecessar
 }
 
 template<typename Tok> JS::Result<FunctionBox*>
 BinASTParser<Tok>::buildFunctionBox(GeneratorKind generatorKind,
     FunctionAsyncKind functionAsyncKind,
     FunctionSyntaxKind syntax,
     ParseNode* name)
 {
+    MOZ_ASSERT_IF(!parseContext_, lazyScript_);
+
     RootedAtom atom(cx_);
     if (name) {
         atom = name->name();
     }
 
     if (parseContext_ && syntax == FunctionSyntaxKind::Statement) {
         auto ptr = parseContext_->varScope().lookupDeclaredName(atom);
         MOZ_ASSERT(ptr);
         ptr->value()->alterKind(DeclarationKind::BodyLevelFunction);
     }
 
     // Allocate the function before walking down the tree.
     RootedFunction fun(cx_);
-    BINJS_TRY_VAR(fun, AllocNewFunction(cx_, atom, syntax, generatorKind, functionAsyncKind, nullptr));
+    BINJS_TRY_VAR(fun, !parseContext_
+        ? lazyScript_->functionNonDelazifying()
+        : AllocNewFunction(cx_, atom, syntax, generatorKind, functionAsyncKind, nullptr));
+    MOZ_ASSERT_IF(parseContext_, fun->explicitName() == atom);
+
+    mozilla::Maybe<Directives> directives;
+    if (parseContext_) {
+        directives.emplace(parseContext_);
+    } else {
+        directives.emplace(lazyScript_->strict());
+    }
 
     auto* funbox = alloc_.new_<FunctionBox>(cx_, traceListHead_, fun, /* toStringStart = */ 0,
-                                            Directives(parseContext_), /* extraWarning = */ false,
+                                            *directives, /* extraWarning = */ false,
                                             generatorKind, functionAsyncKind);
     if (!funbox) {
         return raiseOOM();
     }
 
     traceListHead_ = funbox;
-    funbox->initWithEnclosingParseContext(parseContext_, syntax);
+    if (parseContext_) {
+        funbox->initWithEnclosingParseContext(parseContext_, syntax);
+    } else {
+        funbox->initFromLazyFunction();
+    }
     return funbox;
 }
 
-template<typename Tok> JS::Result<ParseNode*>
-BinASTParser<Tok>::buildFunction(const size_t start, const BinKind kind, ParseNode* name,
-                                 ListNode* params, ParseNode* body, FunctionBox* funbox)
+template<typename Tok> JS::Result<CodeNode*>
+BinASTParser<Tok>::makeEmptyFunctionNode(const size_t start, const BinKind kind, FunctionBox* funbox)
 {
+    // LazyScript compilation requires basically none of the fields filled out.
     TokenPos pos = tokenizer_->pos(start);
-
-    // Set the argument count for building argument packets. Function.length is handled
-    // by setting the appropriate funbox field during argument parsing.
-    funbox->function()->setArgCount(params ? uint16_t(params->count()) : 0);
-
-    // ParseNode represents the body as concatenated after the params.
-    params->appendWithoutOrderAssumption(body);
-
     bool isStatement = kind == BinKind::EagerFunctionDeclaration ||
                        kind == BinKind::LazyFunctionDeclaration;
 
     BINJS_TRY_DECL(result, isStatement
                      ? factory_.newFunctionStatement(pos)
                      : factory_.newFunctionExpression(pos));
 
     factory_.setFunctionBox(result, funbox);
+
+    return result;
+}
+
+template<typename Tok> JS::Result<ParseNode*>
+BinASTParser<Tok>::buildFunction(const size_t start, const BinKind kind, ParseNode* name,
+                                 ListNode* params, ParseNode* body, FunctionBox* funbox)
+{
+    // Set the argument count for building argument packets. Function.length is handled
+    // by setting the appropriate funbox field during argument parsing.
+    if (!lazyScript_ || lazyScript_->functionNonDelazifying() != funbox->function()) {
+        funbox->function()->setArgCount(params ? uint16_t(params->count()) : 0);
+    }
+
+    // ParseNode represents the body as concatenated after the params.
+    params->appendWithoutOrderAssumption(body);
+
+    BINJS_MOZ_TRY_DECL(result, makeEmptyFunctionNode(start, kind, funbox));
+
     factory_.setFunctionFormalParametersAndBody(result, params);
 
     HandlePropertyName dotThis = cx_->names().dotThis;
     const bool declareThis = hasUsedName(dotThis) ||
                              funbox->bindingsAccessedDynamically() ||
                              funbox->isDerivedClassConstructor();
 
     if (declareThis) {
--- a/js/src/frontend/BinSource.h
+++ b/js/src/frontend/BinSource.h
@@ -31,17 +31,18 @@
 #include "js/Result.h"
 
 namespace js {
 namespace frontend {
 
 class BinASTParserBase: private JS::AutoGCRooter
 {
   public:
-    BinASTParserBase(JSContext* cx, LifoAlloc& alloc, UsedNameTracker& usedNames);
+    BinASTParserBase(JSContext* cx, LifoAlloc& alloc, UsedNameTracker& usedNames,
+                     HandleScriptSourceObject sourceObject, Handle<LazyScript*> lazyScript);
     ~BinASTParserBase();
 
   public:
     // Names
 
 
     bool hasUsedName(HandlePropertyName name);
 
@@ -75,16 +76,18 @@ class BinASTParserBase: private JS::Auto
     LifoAlloc::Mark tempPoolMark_;
     ParseNodeAllocator nodeAlloc_;
 
     // ---- Parsing-related stuff
   protected:
     // Root atoms and objects allocated for the parse tree.
     AutoKeepAtoms keepAtoms_;
 
+    RootedScriptSourceObject sourceObject_;
+    Rooted<LazyScript*> lazyScript_;
     ParseContext* parseContext_;
     FullParseHandler factory_;
 
     friend class BinParseContext;
 };
 
 /**
  * The parser for a Binary AST.
@@ -100,18 +103,19 @@ class BinASTParser : public BinASTParser
 
     using AutoList = typename Tokenizer::AutoList;
     using AutoTaggedTuple = typename Tokenizer::AutoTaggedTuple;
     using AutoTuple = typename Tokenizer::AutoTuple;
     using BinFields = typename Tokenizer::BinFields;
     using Chars = typename Tokenizer::Chars;
 
   public:
-    BinASTParser(JSContext* cx, LifoAlloc& alloc, UsedNameTracker& usedNames, const JS::ReadOnlyCompileOptions& options)
-        : BinASTParserBase(cx, alloc, usedNames)
+    BinASTParser(JSContext* cx, LifoAlloc& alloc, UsedNameTracker& usedNames, const JS::ReadOnlyCompileOptions& options,
+                 HandleScriptSourceObject sourceObject, Handle<LazyScript*> lazyScript = nullptr)
+        : BinASTParserBase(cx, alloc, usedNames, sourceObject, lazyScript)
         , options_(options)
         , variableDeclarationKind_(VariableDeclarationKind::Var)
     {
     }
     ~BinASTParser()
     {
     }
 
@@ -123,16 +127,18 @@ class BinASTParser : public BinASTParser
      * destruction of the `BinASTParser` will also destroy the `ParseNode`.
      *
      * In case of error, the parser reports the JS error.
      */
     JS::Result<ParseNode*> parse(GlobalSharedContext* globalsc,
                                  const uint8_t* start, const size_t length);
     JS::Result<ParseNode*> parse(GlobalSharedContext* globalsc, const Vector<uint8_t>& data);
 
+    JS::Result<ParseNode*> parseLazyFunction(const uint8_t* start, const size_t firstOffset, const size_t length);
+
   private:
     MOZ_MUST_USE JS::Result<ParseNode*> parseAux(GlobalSharedContext* globalsc,
                                                  const uint8_t* start, const size_t length);
 
     // --- Raise errors.
     //
     // These methods return a (failed) JS::Result for convenience.
 
@@ -165,19 +171,21 @@ class BinASTParser : public BinASTParser
     };
 
     // Auto-generated methods
 #include "frontend/BinSource-auto.h"
 
     // --- Auxiliary parsing functions
 
     // Build a function object for a function-producing production. Called AFTER creating the scope.
+    JS::Result<CodeNode*>
+    makeEmptyFunctionNode(const size_t start, const BinKind kind, FunctionBox* funbox);
     JS::Result<ParseNode*>
     buildFunction(const size_t start, const BinKind kind, ParseNode* name, ListNode* params,
-        ParseNode* body, FunctionBox* funbox);
+                  ParseNode* body, FunctionBox* funbox);
     JS::Result<FunctionBox*>
     buildFunctionBox(GeneratorKind generatorKind, FunctionAsyncKind functionAsyncKind, FunctionSyntaxKind syntax, ParseNode* name);
 
     // Add name to a given scope.
     MOZ_MUST_USE JS::Result<Ok> addScopeName(AssertedScopeKind scopeKind, HandleAtom name,
                                              ParseContext::Scope* scope,
                                              DeclarationKind declKind,
                                              bool isCaptured);
--- a/js/src/frontend/BinSource.yaml
+++ b/js/src/frontend/BinSource.yaml
@@ -625,16 +625,21 @@ DoWhileStatement:
     build:
         BINJS_TRY_DECL(result, factory_.newDoWhileStatement(body, test, tokenizer_->pos(start)));
 
 EagerFunctionDeclaration:
     init: |
         const auto syntax = FunctionSyntaxKind::Statement;
     inherits: EagerFunctionExpression
 
+LazyFunctionDeclaration:
+    init: |
+        const auto syntax = FunctionSyntaxKind::Statement;
+    inherits: LazyFunctionExpression
+
 FunctionExpressionContents:
     type-ok:
         Ok
     extra-params: |
         uint32_t funLength,
         ListNode** paramsOut,
         ListNode** bodyOut
     extra-args: |
@@ -718,16 +723,52 @@ EagerFunctionExpression:
                 length, &params, &tmpBody
             after: |
                 BINJS_MOZ_TRY_DECL(body, appendDirectivesToBody(tmpBody, directives));
     build: |
         BINJS_TRY_DECL(lexicalScopeData, NewLexicalScopeData(cx_, lexicalScope, alloc_, parseContext_));
         BINJS_TRY_VAR(body, factory_.newLexicalScope(*lexicalScopeData, body));
         BINJS_MOZ_TRY_DECL(result, buildFunction(start, kind, name, params, body, funbox));
 
+LazyFunctionExpression:
+    init: |
+        const auto syntax = FunctionSyntaxKind::Expression;
+    fields:
+        contents:
+            block:
+                replace:
+                    // Don't parse the contents until we delazify.
+    build: |
+        BINJS_MOZ_TRY_DECL(funbox, buildFunctionBox(
+            isGenerator ? GeneratorKind::Generator
+                        : GeneratorKind::NotGenerator,
+            isAsync ? FunctionAsyncKind::AsyncFunction
+                    : FunctionAsyncKind::SyncFunction,
+            syntax, name));
+
+        forceStrictIfNecessary(funbox, directives);
+
+        RootedFunction fun(cx_, funbox->function());
+
+        // TODO: This will become incorrect in the face of ES6 features.
+        fun->setArgCount(length);
+
+        auto skipStart = contentsSkip.startOffset();
+        BINJS_TRY_DECL(lazy, LazyScript::Create(cx_, fun, sourceObject_, parseContext_->closedOverBindingsForLazy(), parseContext_->innerFunctionsForLazy,
+                                                skipStart, skipStart + contentsSkip.length(),
+                                                skipStart, 0, skipStart, ParseGoal::Script));
+
+        if (funbox->strict()) {
+            lazy->setStrict();
+        }
+        lazy->setIsBinAST();
+        funbox->function()->initLazyScript(lazy);
+
+        BINJS_MOZ_TRY_DECL(result, makeEmptyFunctionNode(skipStart, kind, funbox));
+
 EagerGetter:
     init: |
         const auto syntax = FunctionSyntaxKind::Setter;
         const bool isGenerator = false;
         const bool isAsync = false;
         const auto accessorType = AccessorType::Getter;
         const uint32_t length = 0;
     inherits: EagerMethod
--- a/js/src/frontend/BinTokenReaderBase.cpp
+++ b/js/src/frontend/BinTokenReaderBase.cpp
@@ -93,16 +93,24 @@ BinTokenReaderBase::pos(size_t start)
 {
     TokenPos pos;
     pos.begin = start;
     pos.end = current_ - start_;
     MOZ_ASSERT(pos.end >= pos.begin);
     return pos;
 }
 
+void
+BinTokenReaderBase::seek(size_t offset)
+{
+    MOZ_ASSERT(start_ + offset >= start_ &&
+               start_ + offset < stop_);
+    current_ = start_ + offset;
+}
+
 JS::Result<Ok>
 BinTokenReaderBase::readBuf(uint8_t* bytes, uint32_t len)
 {
     MOZ_ASSERT(!cx_->isExceptionPending());
     MOZ_ASSERT(len > 0);
 
     if (stop_ < current_ + len) {
         return raiseError("Buffer exceeds length");
--- a/js/src/frontend/BinTokenReaderBase.h
+++ b/js/src/frontend/BinTokenReaderBase.h
@@ -23,44 +23,47 @@ extern const uint64_t NULL_FLOAT_REPRESE
 class MOZ_STACK_CLASS BinTokenReaderBase
 {
   public:
     template<typename T> using ErrorResult = mozilla::GenericErrorResult<T>;
 
     // The information needed to skip a subtree.
     class SkippableSubTree {
       public:
-        SkippableSubTree(const uint8_t* start, const size_t length)
-          : start_(start)
+        SkippableSubTree(const size_t startOffset, const size_t length)
+          : startOffset_(startOffset)
           , length_(length)
         { }
 
         // The position in the source buffer at which the subtree starts.
         //
         // `SkippableSubTree` does *not* attempt to keep anything alive.
-        const uint8_t* start() const {
-            return start_;
+        size_t startOffset() const {
+            return startOffset_;
         }
 
         // The length of the subtree.
         size_t length() const {
             return length_;
         }
       private:
-        const uint8_t* start_;
+        const size_t startOffset_;
         const size_t length_;
     };
 
     /**
      * Return the position of the latest token.
      */
     TokenPos pos();
     TokenPos pos(size_t startOffset);
     size_t offset() const;
 
+    // Set the tokenizer's cursor in the file. Use with caution.
+    void seek(size_t offset);
+
      /**
       * Poison this tokenizer.
       */
     void poison();
 
     /**
      * Raise an error.
      *
--- a/js/src/frontend/BinTokenReaderMultipart.cpp
+++ b/js/src/frontend/BinTokenReaderMultipart.cpp
@@ -305,17 +305,17 @@ BinTokenReaderMultipart::readSkippableSu
 {
     updateLatestKnownGood();
     BINJS_MOZ_TRY_DECL(byteLen, readInternalUint32());
 
     if (current_ + byteLen > stop_ || current_ + byteLen < current_) {
         return raiseError("Invalid byte length in readSkippableSubTree");
     }
 
-    const auto start = current_;
+    const auto start = offset();
 
     current_ += byteLen;
 
     return BinTokenReaderBase::SkippableSubTree(start, byteLen);
 }
 
 // Untagged tuple:
 // - contents (specified by the higher-level grammar);
--- a/js/src/frontend/BinTokenReaderTester.cpp
+++ b/js/src/frontend/BinTokenReaderTester.cpp
@@ -235,17 +235,17 @@ BinTokenReaderTester::readSkippableSubTr
 {
     updateLatestKnownGood();
     BINJS_MOZ_TRY_DECL(byteLen, readInternalUint32());
 
     if (current_ + byteLen > stop_ || current_ + byteLen < current_) {
         return raiseError("Invalid byte length in readSkippableSubTree");
     }
 
-    const auto start = current_;
+    const auto start = offset();
 
     current_ += byteLen;
 
     return BinTokenReaderBase::SkippableSubTree(start, byteLen);
 }
 
 // Untagged tuple:
 // - "<tuple>";
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -683,17 +683,17 @@ frontend::CompileGlobalBinASTScript(JSCo
 
     if (!script) {
         return nullptr;
     }
 
     Directives directives(options.strictOption);
     GlobalSharedContext globalsc(cx, ScopeKind::Global, directives, options.extraWarningsOption);
 
-    frontend::BinASTParser<BinTokenReaderMultipart> parser(cx, alloc, usedNames, options);
+    frontend::BinASTParser<BinTokenReaderMultipart> parser(cx, alloc, usedNames, options, sourceObj);
 
     auto parsed = parser.parse(&globalsc, src, len);
 
     if (parsed.isErr()) {
         return nullptr;
     }
 
     BytecodeEmitter bce(nullptr, &parser, &globalsc, script, nullptr, 0);
@@ -832,21 +832,24 @@ class MOZ_STACK_CLASS AutoAssertFunction
 #endif
     }
 };
 
 bool
 frontend::CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length)
 {
     MOZ_ASSERT(cx->compartment() == lazy->functionNonDelazifying()->compartment());
+
     // We can only compile functions whose parents have previously been
     // compiled, because compilation requires full information about the
     // function's immediately enclosing scope.
     MOZ_ASSERT(lazy->enclosingScriptHasEverBeenCompiled());
 
+    MOZ_ASSERT(!lazy->isBinAST());
+
     AutoAssertReportedException assertException(cx);
     Rooted<JSFunction*> fun(cx, lazy->functionNonDelazifying());
     AutoAssertFunctionDelazificationCompletion delazificationCompletion(cx, fun);
 
     JS::CompileOptions options(cx);
     options.setMutedErrors(lazy->mutedErrors())
            .setFileAndLine(lazy->filename(), lazy->lineno())
            .setColumn(lazy->column())
@@ -914,16 +917,77 @@ frontend::CompileLazyFunction(JSContext*
     }
 
     delazificationCompletion.complete();
     assertException.reset();
     return true;
 }
 
 bool
+frontend::CompileLazyBinASTFunction(JSContext* cx, Handle<LazyScript*> lazy, const uint8_t* buf, size_t length)
+{
+    MOZ_ASSERT(cx->compartment() == lazy->functionNonDelazifying()->compartment());
+
+    // We can only compile functions whose parents have previously been
+    // compiled, because compilation requires full information about the
+    // function's immediately enclosing scope.
+    MOZ_ASSERT(lazy->enclosingScriptHasEverBeenCompiled());
+    MOZ_ASSERT(lazy->isBinAST());
+
+    CompileOptions options(cx);
+    options.setMutedErrors(lazy->mutedErrors())
+           .setFileAndLine(lazy->filename(), lazy->lineno())
+           .setColumn(lazy->column())
+           .setScriptSourceOffset(lazy->sourceStart())
+           .setNoScriptRval(false)
+           .setSelfHostingMode(false);
+
+    UsedNameTracker usedNames(cx);
+
+    RootedScriptSourceObject sourceObj(cx, &lazy->sourceObject());
+    MOZ_ASSERT(sourceObj);
+
+    RootedScript script(cx, JSScript::Create(cx, options, sourceObj, lazy->sourceStart(), lazy->sourceEnd(),
+                                             lazy->sourceStart(), lazy->sourceEnd()));
+
+    if (!script)
+        return false;
+
+    if (lazy->hasBeenCloned())
+        script->setHasBeenCloned();
+
+    frontend::BinASTParser<BinTokenReaderMultipart> parser(cx, cx->tempLifoAlloc(),
+                                                           usedNames, options, sourceObj,
+                                                           lazy);
+
+    auto parsed = parser.parseLazyFunction(lazy->scriptSource()->binASTSource(),
+                                           lazy->sourceStart(),
+                                           lazy->scriptSource()->length());
+
+    if (parsed.isErr())
+        return false;
+
+    ParseNode *pn = parsed.unwrap();
+
+    BytecodeEmitter bce(nullptr, &parser, pn->as<CodeNode>().funbox(), script,
+                        lazy, pn->pn_pos, BytecodeEmitter::LazyFunction);
+
+    if (!bce.init())
+        return false;
+
+    if (!bce.emitFunctionScript(&pn->as<CodeNode>(), BytecodeEmitter::TopLevelFunction::Yes))
+        return false;
+
+    if (!NameFunctions(cx, pn))
+        return false;
+
+    return script;
+}
+
+bool
 frontend::CompileStandaloneFunction(JSContext* cx, MutableHandleFunction fun,
                                     const JS::ReadOnlyCompileOptions& options,
                                     JS::SourceBufferHolder& srcBuf,
                                     const Maybe<uint32_t>& parameterListEnd,
                                     HandleScope enclosingScope /* = nullptr */)
 {
     AutoAssertReportedException assertException(cx);
 
--- a/js/src/frontend/BytecodeCompiler.h
+++ b/js/src/frontend/BytecodeCompiler.h
@@ -59,16 +59,19 @@ CompileModule(JSContext* cx, const JS::R
 JSScript*
 CompileModule(JSContext* cx, const JS::ReadOnlyCompileOptions& options,
               JS::SourceBufferHolder& srcBuf, LifoAlloc& alloc,
               ScriptSourceObject** sourceObjectOut = nullptr);
 
 MOZ_MUST_USE bool
 CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length);
 
+MOZ_MUST_USE bool
+CompileLazyBinASTFunction(JSContext* cx, Handle<LazyScript*> lazy, const uint8_t* buf, size_t length);
+
 //
 // Compile a single function. The source in srcBuf must match the ECMA-262
 // FunctionExpression production.
 //
 // If nonzero, parameterListEnd is the offset within srcBuf where the parameter
 // list is expected to end. During parsing, if we find that it ends anywhere
 // else, it's a SyntaxError. This is used to implement the Function constructor;
 // it's how we detect that these weird cases are SyntaxErrors:
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -5748,18 +5748,24 @@ BytecodeEmitter::emitFunction(CodeNode* 
             Rooted<JSScript*> script(cx, JSScript::Create(cx, options, sourceObject,
                                                           funbox->bufStart, funbox->bufEnd,
                                                           funbox->toStringStart,
                                                           funbox->toStringEnd));
             if (!script) {
                 return false;
             }
 
+            EmitterMode nestedMode = emitterMode;
+            if (nestedMode == BytecodeEmitter::LazyFunction) {
+                MOZ_ASSERT(lazyScript->isBinAST());
+                nestedMode = BytecodeEmitter::Normal;
+            }
+
             BytecodeEmitter bce2(this, parser, funbox, script, /* lazyScript = */ nullptr,
-                                 funNode->pn_pos, emitterMode);
+                                 funNode->pn_pos, nestedMode);
             if (!bce2.init()) {
                 return false;
             }
 
             /* We measured the max scope depth when we parsed the function. */
             if (!bce2.emitFunctionScript(funNode, TopLevelFunction::No)) {
                 return false;
             }
--- a/js/src/jsapi-tests/testBinASTReader.cpp
+++ b/js/src/jsapi-tests/testBinASTReader.cpp
@@ -223,17 +223,23 @@ runTestFromPath(JSContext* cx, const cha
         CompileOptions binOptions(cx);
         binOptions.setFileAndLine(binPath.begin(), 0);
 
         frontend::UsedNameTracker binUsedNames(cx);
 
         frontend::Directives directives(false);
         frontend::GlobalSharedContext globalsc(cx, ScopeKind::Global, directives, false);
 
-        frontend::BinASTParser<Tok> binParser(cx, allocScope.alloc(), binUsedNames, binOptions);
+        RootedScriptSourceObject sourceObj(cx, frontend::CreateScriptSourceObject(cx, binOptions,
+                                                   mozilla::Nothing()));
+        if (!sourceObj) {
+            MOZ_CRASH();
+        }
+
+        frontend::BinASTParser<Tok> binParser(cx, allocScope.alloc(), binUsedNames, binOptions, sourceObj);
 
         auto binParsed = binParser.parse(&globalsc, binSource); // Will be deallocated once `reader` goes out of scope.
         RootedValue binExn(cx);
         if (binParsed.isErr()) {
             // Save exception for more detailed error message, if necessary.
             if (!js::GetAndClearException(cx, &binExn)) {
                 MOZ_CRASH("Couldn't clear binExn");
             }
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -5072,22 +5072,23 @@ using js::frontend::Directives;
 using js::frontend::GlobalSharedContext;
 using js::frontend::ParseNode;
 using js::frontend::UsedNameTracker;
 
 template <typename Tok>
 static bool
 ParseBinASTData(JSContext* cx, uint8_t* buf_data, uint32_t buf_length,
                 GlobalSharedContext* globalsc, UsedNameTracker& usedNames,
-                const JS::ReadOnlyCompileOptions& options)
+                const JS::ReadOnlyCompileOptions& options,
+                HandleScriptSourceObject sourceObj)
 {
     MOZ_ASSERT(globalsc);
 
     // Note: We need to keep `reader` alive as long as we can use `parsed`.
-    BinASTParser<Tok> reader(cx, cx->tempLifoAlloc(), usedNames, options);
+    BinASTParser<Tok> reader(cx, cx->tempLifoAlloc(), usedNames, options, sourceObj);
 
     JS::Result<ParseNode*> parsed = reader.parse(globalsc, buf_data, buf_length);
 
     if (parsed.isErr())
         return false;
 
 #ifdef DEBUG
     Fprinter out(stderr);
@@ -5178,22 +5179,27 @@ BinParse(JSContext* cx, unsigned argc, V
 
 
     CompileOptions options(cx);
     options.setIntroductionType("js shell bin parse")
            .setFileAndLine("<ArrayBuffer>", 1);
 
     UsedNameTracker usedNames(cx);
 
+    RootedScriptSourceObject sourceObj(cx, frontend::CreateScriptSourceObject(cx, options, Nothing()));
+    if (!sourceObj) {
+        return false;
+    }
+
     Directives directives(false);
     GlobalSharedContext globalsc(cx, ScopeKind::Global, directives, false);
 
     auto parseFunc = useMultipart ? ParseBinASTData<frontend::BinTokenReaderMultipart>
                                   : ParseBinASTData<frontend::BinTokenReaderTester>;
-    if (!parseFunc(cx, buf_data, buf_length, &globalsc, usedNames, options)) {
+    if (!parseFunc(cx, buf_data, buf_length, &globalsc, usedNames, options, sourceObj)) {
         return false;
     }
 
     args.rval().setUndefined();
     return true;
 }
 
 #endif // defined(JS_BUILD_BINAST)
--- a/js/src/vm/JSFunction.cpp
+++ b/js/src/vm/JSFunction.cpp
@@ -1745,16 +1745,17 @@ JSFunction::createScriptForLazilyInterpr
 
         // Only functions without inner functions or direct eval are
         // re-lazified. Functions with either of those are on the static scope
         // chain of their inner functions, or in the case of eval, possibly
         // eval'd inner functions. This prohibits re-lazification as
         // StaticScopeIter queries needsCallObject of those functions, which
         // requires a non-lazy script.  Note that if this ever changes,
         // XDRRelazificationInfo will have to be fixed.
+        bool isBinAST = lazy->scriptSource()->hasBinASTSource();
         bool canRelazify = !lazy->numInnerFunctions() && !lazy->hasDirectEval();
 
         if (script) {
             // This function is non-canonical function, and the canonical
             // function is already delazified.
             fun->setUnlazifiedScript(script);
             // Remember the lazy script on the compiled script, so it can be
             // stored on the function again in case of re-lazification.
@@ -1778,34 +1779,48 @@ JSFunction::createScriptForLazilyInterpr
             }
 
             fun->setUnlazifiedScript(script);
             return true;
         }
 
         // This is lazy canonical-function.
 
-        MOZ_ASSERT(lazy->scriptSource()->hasSourceText());
-
-        // Parse and compile the script from source.
         size_t lazyLength = lazy->sourceEnd() - lazy->sourceStart();
-        UncompressedSourceCache::AutoHoldEntry holder;
-        ScriptSource::PinnedChars chars(cx, lazy->scriptSource(), holder,
-                                        lazy->sourceStart(), lazyLength);
-        if (!chars.get()) {
-            return false;
-        }
-
-        if (!frontend::CompileLazyFunction(cx, lazy, chars.get(), lazyLength)) {
-            // The frontend shouldn't fail after linking the function and the
-            // non-lazy script together.
-            MOZ_ASSERT(fun->isInterpretedLazy());
-            MOZ_ASSERT(fun->lazyScript() == lazy);
-            MOZ_ASSERT(!lazy->hasScript());
-            return false;
+        if (isBinAST) {
+#if defined(JS_BUILD_BINAST)
+            if (!frontend::CompileLazyBinASTFunction(cx, lazy,
+                    lazy->scriptSource()->binASTSource() + lazy->sourceStart(), lazyLength))
+            {
+                MOZ_ASSERT(fun->isInterpretedLazy());
+                MOZ_ASSERT(fun->lazyScript() == lazy);
+                MOZ_ASSERT(!lazy->hasScript());
+                return false;
+            }
+#else
+            MOZ_CRASH("Trying to delazify BinAST function in non-BinAST build");
+#endif /*JS_BUILD_BINAST */
+        } else {
+            MOZ_ASSERT(lazy->scriptSource()->hasSourceText());
+
+            // Parse and compile the script from source.
+            UncompressedSourceCache::AutoHoldEntry holder;
+            ScriptSource::PinnedChars chars(cx, lazy->scriptSource(), holder,
+                                            lazy->sourceStart(), lazyLength);
+            if (!chars.get())
+                return false;
+
+            if (!frontend::CompileLazyFunction(cx, lazy, chars.get(), lazyLength)) {
+		// The frontend shouldn't fail after linking the function and the
+		// non-lazy script together.
+                MOZ_ASSERT(fun->isInterpretedLazy());
+                MOZ_ASSERT(fun->lazyScript() == lazy);
+                MOZ_ASSERT(!lazy->hasScript());
+                return false;
+            }
         }
 
         script = fun->nonLazyScript();
 
         // Remember the compiled script on the lazy script itself, in case
         // there are clones of the function still pointing to the lazy script.
         if (!lazy->maybeScript()) {
             lazy->initScript(script);
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -4659,16 +4659,17 @@ LazyScript::Create(JSContext* cx, Handle
     p.isGenerator = false;
     p.strict = false;
     p.bindingsAccessedDynamically = false;
     p.hasDebuggerStatement = false;
     p.hasDirectEval = false;
     p.isLikelyConstructorWrapper = false;
     p.isDerivedClassConstructor = false;
     p.needsHomeObject = false;
+    p.isBinAST = false;
     p.parseGoal = uint32_t(parseGoal);
 
     LazyScript* res = LazyScript::CreateRaw(cx, fun, sourceObject, packedFields,
                                             sourceStart, sourceEnd,
                                             toStringStart, lineno, column);
     if (!res) {
         return nullptr;
     }
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -2337,16 +2337,17 @@ class LazyScript : public gc::TenuredCel
   private:
     static const uint32_t NumClosedOverBindingsBits = 20;
     static const uint32_t NumInnerFunctionsBits = 20;
 
     struct PackedView {
         uint32_t shouldDeclareArguments : 1;
         uint32_t hasThisBinding : 1;
         uint32_t isAsync : 1;
+        uint32_t isBinAST : 1;
 
         uint32_t numClosedOverBindings : NumClosedOverBindingsBits;
 
         // -- 32bit boundary --
 
         uint32_t numInnerFunctions : NumInnerFunctionsBits;
 
         // N.B. These are booleans but need to be uint32_t to pack correctly on MSVC.
@@ -2525,16 +2526,23 @@ class LazyScript : public gc::TenuredCel
     void setHasRest() {
         p_.hasRest = true;
     }
 
     frontend::ParseGoal parseGoal() const {
         return frontend::ParseGoal(p_.parseGoal);
     }
 
+    bool isBinAST() const {
+        return p_.isBinAST;
+    }
+    void setIsBinAST() {
+        p_.isBinAST = true;
+    }
+
     bool strict() const {
         return p_.strict;
     }
     void setStrict() {
         p_.strict = true;
     }
 
     bool bindingsAccessedDynamically() const {