load(libdir + 'bytecode-cache.js');
var test = "";
var checkAfter;

// code a function which has both used and unused inner functions.
test = (function () {
  function f(x) {
    function ifTrue() {
      return true;
    };
    function ifFalse() {
      return false;
    };

    if (x) return ifTrue();
    else return ifFalse();
  }

  return f.toSource() + "; f(true)";
})();
evalWithCache(test, { assertEqBytecode: true, assertEqResult : true });

// code a function which uses different inner functions based on the generation.
test = (function () {
  function f(x) {
    function ifTrue() {
      return true;
    };
    function ifFalse() {
      return false;
    };

    if (x) return ifTrue();
    else return ifFalse();
  }

  return f.toSource() + "; f((generation % 2) == 0)";
})();
evalWithCache(test, { });

// Code a function which has an enclosing scope.
test = (function () {
  function f() {
    var upvar = "";
    function g() { upvar += ""; return upvar; }
    return g;
  }

  return f.toSource() + "; f()();";
})();
evalWithCache(test, { assertEqBytecode: true, assertEqResult : true });

// Code a lazy function which has an enclosing scope.
test = (function () {
  function f() {
    var upvar = "";
    function g() { upvar += ""; return upvar; }
    return g;
  }

  return f.toSource() + "; f();";
})();
evalWithCache(test, { assertEqBytecode: true });

// (basic/bug535930) Code an enclosing scope which is a Call object.
test = (function () {
  return "(" + (function () {
    p = function () {
        Set()
    };
    var Set = function () {};
    for (var x = 0; x < 5; x++) {
      Set = function (z) {
        return function () {
          [z]
        }
      } (x)
    }
  }).toSource() + ")()";
})();
evalWithCache(test, { assertEqBytecode: true });

// Code an arrow function, and execute it.
test = (function () {
  function f() {
    var g = (a) => a + a;
    return g;
  }

  return f.toSource() + "; f()(1);";
})();
evalWithCache(test, { assertEqBytecode: true, assertEqResult : true });

// Code an arrow function, and do not execute it.
test = (function () {
  function f() {
    var g = (a) => a + a;
    return g;
  }

  return f.toSource() + "; f();";
})();
evalWithCache(test, { assertEqBytecode: true });

// Extra zeal GCs can cause isRelazifiableFunction() to become true after we
// record its value by throwing away JIT code for the function.
gczeal(0);

// Ensure that decoded functions can be relazified.
test = "function f() { }; f();"
     + "assertEq(isLazyFunction(f), false);"
     + "var expect = isRelazifiableFunction(f);";
checkAfter = function (ctx) {
  gc(ctx.global.f, "shrinking"); // relazify f, if possible.
  evaluate("assertEq(isLazyFunction(f), expect);", ctx);
};
evalWithCache(test, {
  assertEqBytecode: true,  // Check that we re-encode the same thing.
  assertEqResult: true,    // The function should remain relazifiable, if it was
                           // during the first run.
  checkAfter: checkAfter   // Check that relazifying the restored function works
                           // if the original was relazifiable.
});

// Ensure that decoded functions can be relazified, even if they have free
// variables.
test = "function f() { return isRelazifiableFunction(f) }; var expect = f();"
     + "assertEq(isLazyFunction(f), false);"
     + "expect";
checkAfter = function (ctx) {
  gc(ctx.global.f, "shrinking"); // relazify f, if possible.
  evaluate("assertEq(isLazyFunction(f), expect);", ctx);
};
evalWithCache(test, {
  assertEqBytecode: true,  // Check that we re-encode the same thing.
  assertEqResult: true,    // The function should remain relazifiable, if it was
                           // during the first run.
  checkAfter: checkAfter   // Check that relazifying the restored function works
                           // if the original was relazifiable.
});

// Ensure that if a function is encoded when non-lazy but relazifiable, then
// decoded, relazified, and then delazified, the result actually works.
test = `
  function f() { return true; };
  var canBeLazy = isRelazifiableFunction(f) || isLazyFunction(f);
  relazifyFunctions();
  assertEq(isLazyFunction(f), canBeLazy);
  f()`
evalWithCache(test, { assertEqBytecode: true, assertEqResult: true });

// And more of the same, in a slightly different way
var g1 = newGlobal({ cloneSingletons: true });
var g2 = newGlobal();
var res = "function f(){}";
var code = cacheEntry(res + "; f();");
evaluate(code, {global:g1, compileAndGo: true, saveBytecode: {value: true}});
evaluate(code, {global:g2, loadBytecode: true});
gc();
assertEq(g2.f.toString(), res);

// Another relazification case.
var src = "function f() { return 3; }; f(); relazifyFunctions(); 4";
evalWithCache(src, {assertEqBytecode: true, assertEqResult: true});