Lazy objects v0 for raw objects

Summary:
Introduce lazy objects feature for raw objects in Prepack with supported runtime.
Lazy object is a collaboration feature between the Prepack and new runtime that supports this feature. With this feature, object can be created with a special LazyObjectsRuntime.createLazyObject() API. The object created with this API can be a light-weight placeholder object. Once the lazy object is *touched/used* the first time, runtime is responsible to callback the registered initialization function to hydrate the object into a real object.
By doing this we will save both memory and object creation speed.

Currently, Prepack only supports lazy for raw objects.

The APIs that runtime needs to support:
1. LazyObjectsRuntime.createLazyObject(<lazy_object_id>);
2. LazyObjectsRuntime.setLazyObjectInitializer(<well-known-callback>).

Under lazy objects mode, Prepack will serialize all lazy objects into two parts:
 1. All lazy objects are created via lightweight LazyObjectsRuntime.createLazyObject() call.
 2. Lazy objects' property assignments are delayed in a callback function which is registered with the runtime.

TODO:
1. Test-runner support for lazy mode.
2. Support more object types for this feature
3. Remove "reference count === 1" objects' root and directly serialize into callback function.
4. Figure out "reference count > 1" objects that are safe to remove root from global anonymous function and directly serialize into callback function.
Closes https://github.com/facebook/prepack/pull/1129

Differential Revision: D6234286

Pulled By: yinghuitan

fbshipit-source-id: fb8f3c0ff2e4189b27b5f98a20a30bdea045f67a
This commit is contained in:
Jeffrey Tan 2017-11-03 12:17:37 -07:00 committed by Facebook Github Bot
parent 3334237ab7
commit b6050b06b9
7 changed files with 337 additions and 89 deletions

View File

@ -17,6 +17,7 @@ export const CompatibilityValues = ["browser", "jsc-600-1-4-17", "node-source-ma
export type RealmOptions = {
compatibility?: Compatibility,
debugNames?: boolean,
lazyObjectsRuntime?: string,
errorHandler?: ErrorHandler,
mathRandomSeed?: string,
omitInvariants?: boolean,
@ -31,6 +32,7 @@ export type RealmOptions = {
export type SerializerOptions = {
additionalFunctions?: Array<string>,
lazyObjectsRuntime?: string,
delayInitializations?: boolean,
delayUnsupportedRequires?: boolean,
initializeMoreModules?: boolean,

View File

@ -44,6 +44,7 @@ function run(
--maxStackDepth Specify the maximum call stack depth.
--timeout The amount of time in seconds until Prepack should time out.
--additionalFunctions Additional functions that should be prepacked (comma separated).
--lazyObjectsRuntime Enable lazy objects feature and specify the JS runtime that support this feature.
--debugNames Changes the output of Prepack so that for named functions and variables that get emitted into
Prepack's output, the original name is appended as a suffix to Prepack's generated identifier.
--speculate Enable speculative initialization of modules (for the module system Prepack has builtin
@ -70,6 +71,7 @@ function run(
let maxStackDepth: number;
let timeout: number;
let additionalFunctions: Array<string>;
let lazyObjectsRuntime: string;
let debugInFilePath: string;
let debugOutFilePath: string;
let flags = {
@ -146,9 +148,12 @@ function run(
case "debugOutFilePath":
debugOutFilePath = args.shift();
break;
case "lazyObjectsRuntime":
lazyObjectsRuntime = args.shift();
break;
case "help":
console.log(
"Usage: prepack.js [ -- | input.js ] [ --out output.js ] [ --compatibility jsc ] [ --mathRandomSeed seedvalue ] [ --srcmapIn inputMap ] [ --srcmapOut outputMap ] [ --maxStackDepth depthValue ] [ --timeout seconds ] [ --additionalFunctions fnc1,fnc2,... ]" +
"Usage: prepack.js [ -- | input.js ] [ --out output.js ] [ --compatibility jsc ] [ --mathRandomSeed seedvalue ] [ --srcmapIn inputMap ] [ --srcmapOut outputMap ] [ --maxStackDepth depthValue ] [ --timeout seconds ] [ --additionalFunctions fnc1,fnc2,... ] [ --lazyObjectsRuntime lazyObjectsRuntimeName]" +
Object.keys(flags).map(s => "[ --" + s + "]").join(" ") +
"\n" +
HELP_STR
@ -177,12 +182,22 @@ function run(
maxStackDepth: maxStackDepth,
timeout: timeout,
additionalFunctions: additionalFunctions,
lazyObjectsRuntime: lazyObjectsRuntime,
enableDebugger: false, //always turn off debugger until debugger is fully built
debugInFilePath: debugInFilePath,
debugOutFilePath: debugOutFilePath,
},
flags
);
if (
lazyObjectsRuntime &&
(resolvedOptions.additionalFunctions || resolvedOptions.delayInitializations || resolvedOptions.inlineExpressions)
) {
console.error(
"lazy objects feature is incompatible with additionalFunctions, delayInitializations and inlineExpressions options"
);
process.exit(1);
}
let errors: Map<BabelNodeSourceLocation, CompilerDiagnostic> = new Map();
function errorHandler(diagnostic: CompilerDiagnostic): ErrorHandlerResult {

View File

@ -17,6 +17,7 @@ import invariant from "./invariant.js";
export type PrepackOptions = {|
additionalGlobals?: Realm => void,
additionalFunctions?: Array<string>,
lazyObjectsRuntime?: string,
compatibility?: Compatibility,
debugNames?: boolean,
delayInitializations?: boolean,
@ -50,6 +51,7 @@ export type PrepackOptions = {|
export function getRealmOptions({
compatibility = "browser",
debugNames = false,
lazyObjectsRuntime,
errorHandler,
mathRandomSeed,
omitInvariants = false,
@ -64,6 +66,7 @@ export function getRealmOptions({
return {
compatibility,
debugNames,
lazyObjectsRuntime,
errorHandler,
mathRandomSeed,
omitInvariants,
@ -79,6 +82,7 @@ export function getRealmOptions({
export function getSerializerOptions({
additionalFunctions,
lazyObjectsRuntime,
delayInitializations = false,
delayUnsupportedRequires = false,
internalDebug = false,
@ -101,6 +105,9 @@ export function getSerializerOptions({
trace,
};
if (additionalFunctions) result.additionalFunctions = additionalFunctions;
if (lazyObjectsRuntime !== undefined) {
result.lazyObjectsRuntime = lazyObjectsRuntime;
}
return result;
}

View File

@ -0,0 +1,203 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
/* @flow */
import { Realm } from "../realm.js";
import { AbstractValue, FunctionValue, Value, ObjectValue } from "../values/index.js";
import * as t from "babel-types";
import type {
BabelNodeExpression,
BabelNodeStatement,
BabelNodeIdentifier,
BabelNodeBlockStatement,
BabelNodeSwitchCase,
} from "babel-types";
import type {
SerializedBody,
FunctionInfo,
FunctionInstance,
AdditionalFunctionInfo,
ReactSerializerState,
} from "./types.js";
import type { SerializerOptions } from "../options.js";
import invariant from "../invariant.js";
import { SerializerStatistics } from "./types.js";
import { Logger } from "./logger.js";
import { Modules } from "./modules.js";
import { ResidualHeapInspector } from "./ResidualHeapInspector.js";
import type { Scope } from "./ResidualHeapVisitor.js";
import { ResidualHeapValueIdentifiers } from "./ResidualHeapValueIdentifiers.js";
import type { Effects } from "../realm.js";
import { ResidualHeapSerializer } from "./ResidualHeapSerializer.js";
import { getOrDefault } from "./utils.js";
const LAZY_OBJECTS_SERIALIZER_BODY_TYPE = "LazyObjectInitializer";
/**
* Serialize objects in lazy mode by leveraging the JS runtime that support this feature.
* Objects are serialized into two parts:
* 1. All lazy objects are created via lightweight LazyObjectsRuntime.createLazyObject() call.
* 2. Lazy objects' property assignments are delayed in a callback function which is registered with the runtime.
* lazy objects rutnime will execute this callback to hydrate the lazy objects.
*
* Currently only the raw objects are taking part in the lazy objects feature.
* TODO: suppor for other objects, like array, regex etc...
*/
export class LazyObjectsSerializer extends ResidualHeapSerializer {
constructor(
realm: Realm,
logger: Logger,
modules: Modules,
residualHeapValueIdentifiers: ResidualHeapValueIdentifiers,
residualHeapInspector: ResidualHeapInspector,
residualValues: Map<Value, Set<Scope>>,
residualFunctionInstances: Map<FunctionValue, FunctionInstance>,
residualFunctionInfos: Map<BabelNodeBlockStatement, FunctionInfo>,
options: SerializerOptions,
referencedDeclaredValues: Set<AbstractValue>,
additionalFunctionValuesAndEffects: Map<FunctionValue, Effects> | void,
additionalFunctionValueInfos: Map<FunctionValue, AdditionalFunctionInfo>,
statistics: SerializerStatistics,
react: ReactSerializerState
) {
super(
realm,
logger,
modules,
residualHeapValueIdentifiers,
residualHeapInspector,
residualValues,
residualFunctionInstances,
residualFunctionInfos,
options,
referencedDeclaredValues,
additionalFunctionValuesAndEffects,
additionalFunctionValueInfos,
statistics,
react
);
this._lazyObjectIdSeed = 0;
this._valueLazyIds = new Map();
this._lazyObjectInitializers = new Map();
this._callbackLazyObjectParam = t.identifier("obj");
invariant(this._options.lazyObjectsRuntime != null);
this._lazyObjectJSRuntimeName = t.identifier(this._options.lazyObjectsRuntime);
this._initializationCallbackName = t.identifier("__initializerCallback");
}
_lazyObjectIdSeed: number;
_valueLazyIds: Map<ObjectValue, number>;
// Holds object's lazy initializer bodies.
// These bodies will be combined into a well-known callback after generator serialization is done and registered with the runtime.
_lazyObjectInitializers: Map<ObjectValue, SerializedBody>;
_currentSerializeLazyObject: void | ObjectValue;
_lazyObjectJSRuntimeName: BabelNodeIdentifier;
_callbackLazyObjectParam: BabelNodeIdentifier;
_initializationCallbackName: BabelNodeIdentifier;
_getValueLazyId(obj: ObjectValue): number {
return getOrDefault(this._valueLazyIds, obj, () => this._lazyObjectIdSeed++);
}
// TODO: change to use _getTarget() to get the lazy objects initializer body.
_serializeLazyObjectInitializer(obj: ObjectValue): SerializedBody {
const prevLazyObject = this._currentSerializeLazyObject;
this._currentSerializeLazyObject = obj;
const initializerBody = { type: LAZY_OBJECTS_SERIALIZER_BODY_TYPE, entries: [] };
let oldBody = this.emitter.beginEmitting(LAZY_OBJECTS_SERIALIZER_BODY_TYPE, initializerBody);
this._emitObjectProperties(obj);
this.emitter.endEmitting(LAZY_OBJECTS_SERIALIZER_BODY_TYPE, oldBody);
this._currentSerializeLazyObject = prevLazyObject;
return initializerBody;
}
_serializeLazyObjectInitializerSwitchCase(obj: ObjectValue, initializer: SerializedBody): BabelNodeSwitchCase {
// TODO: only serialize this switch case if the initializer(property assignment) is not empty.
const caseBody = initializer.entries.concat(t.breakStatement());
const lazyId = this._getValueLazyId(obj);
return t.switchCase(t.numericLiteral(lazyId), caseBody);
}
_serializeInitializationCallback(): BabelNodeStatement {
const body = [];
const switchCases = [];
for (const [obj, initializer] of this._lazyObjectInitializers) {
switchCases.push(this._serializeLazyObjectInitializerSwitchCase(obj, initializer));
}
// Default case.
switchCases.push(
t.switchCase(null, [
t.throwStatement(t.newExpression(t.identifier("Error"), [t.stringLiteral("Unknown lazy id")])),
])
);
const selector = t.identifier("id");
body.push(t.switchStatement(selector, switchCases));
const params = [this._callbackLazyObjectParam, selector];
const initializerCallbackFunction = t.functionExpression(null, params, t.blockStatement(body));
// TODO: use NameGenerator.
return t.variableDeclaration("var", [
t.variableDeclarator(this._initializationCallbackName, initializerCallbackFunction),
]);
}
_serializeRegisterInitializationCallback(): BabelNodeStatement {
return t.expressionStatement(
t.callExpression(t.memberExpression(this._lazyObjectJSRuntimeName, t.identifier("setLazyObjectInitializer")), [
this._initializationCallbackName,
])
);
}
_serializeCreateLazyObject(obj: ObjectValue): BabelNodeExpression {
const lazyId = this._getValueLazyId(obj);
return t.callExpression(
t.memberExpression(this._lazyObjectJSRuntimeName, t.identifier("createLazyObject"), /*computed*/ false),
[t.numericLiteral(lazyId)]
);
}
// Override default behavior.
// Inside lazy objects callback, the lazy object identifier needs to be replaced with the
// parameter passed from the runtime.
getSerializeObjectIdentifier(val: Value): BabelNodeIdentifier {
return this.emitter.getBody().type === LAZY_OBJECTS_SERIALIZER_BODY_TYPE && this._currentSerializeLazyObject === val
? this._callbackLazyObjectParam
: super.getSerializeObjectIdentifier(val);
}
// Override default behavior.
// Inside lazy objects callback, the lazy object identifier needs to be replaced with the
// parameter passed from the runtime.
getSerializeObjectIdentifierOptional(val: Value): void | BabelNodeIdentifier {
return this.emitter.getBody().type === LAZY_OBJECTS_SERIALIZER_BODY_TYPE && this._currentSerializeLazyObject === val
? this._callbackLazyObjectParam
: super.getSerializeObjectIdentifierOptional(val);
}
// Override default serializer with lazy mode.
serializeValueRawObject(obj: ObjectValue): BabelNodeExpression {
this._lazyObjectInitializers.set(obj, this._serializeLazyObjectInitializer(obj));
return this._serializeCreateLazyObject(obj);
}
// Override.
// Serialize the initialization callback and its registration in prelude if there are object being lazied.
postGeneratorSerialization(): void {
if (this._lazyObjectInitializers.size > 0) {
// Insert initialization callback at the end of prelude code.
this.prelude.push(this._serializeInitializationCallback());
this.prelude.push(this._serializeRegisterInitializationCallback());
}
}
}

View File

@ -58,6 +58,7 @@ import type {
ReactSerializerState,
SerializedBody,
} from "./types.js";
import type { SerializerOptions } from "../options.js";
import { TimingStatistics, SerializerStatistics } from "./types.js";
import { Logger } from "./logger.js";
import { Modules } from "./modules.js";
@ -81,7 +82,7 @@ export class ResidualHeapSerializer {
residualValues: Map<Value, Set<Scope>>,
residualFunctionInstances: Map<FunctionValue, FunctionInstance>,
residualFunctionInfos: Map<BabelNodeBlockStatement, FunctionInfo>,
delayInitializations: boolean,
options: SerializerOptions,
referencedDeclaredValues: Set<AbstractValue>,
additionalFunctionValuesAndEffects: Map<FunctionValue, Effects> | void,
additionalFunctionValueInfos: Map<FunctionValue, AdditionalFunctionInfo>,
@ -119,7 +120,7 @@ export class ResidualHeapSerializer {
this.modules,
this.requireReturns,
{
getLocation: value => this.residualHeapValueIdentifiers.getIdentifierAndIncrementReferenceCountOptional(value),
getLocation: value => this.getSerializeObjectIdentifierOptional(value),
createLocation: () => {
let location = t.identifier(this.valueNameGenerator.generate("initialized"));
this.currentFunctionBody.entries.push(t.variableDeclaration("var", [t.variableDeclarator(location)]));
@ -142,7 +143,7 @@ export class ResidualHeapSerializer {
this.residualValues = residualValues;
this.residualFunctionInstances = residualFunctionInstances;
this.residualFunctionInfos = residualFunctionInfos;
this.delayInitializations = delayInitializations;
this._options = options;
this.referencedDeclaredValues = referencedDeclaredValues;
this.activeGeneratorBodies = new Map();
this.additionalFunctionValuesAndEffects = additionalFunctionValuesAndEffects;
@ -180,7 +181,7 @@ export class ResidualHeapSerializer {
residualFunctionInfos: Map<BabelNodeBlockStatement, FunctionInfo>;
serializedValues: Set<Value>;
residualFunctions: ResidualFunctions;
delayInitializations: boolean;
_options: SerializerOptions;
referencedDeclaredValues: Set<AbstractValue>;
activeGeneratorBodies: Map<Generator, SerializedBody>;
additionalFunctionValuesAndEffects: Map<FunctionValue, Effects> | void;
@ -256,7 +257,7 @@ export class ResidualHeapSerializer {
this.emitter.emitNowOrAfterWaitingForDependencies([proto, obj], () => {
invariant(proto);
let serializedProto = this.serializeValue(proto);
let uid = this.residualHeapValueIdentifiers.getIdentifierAndIncrementReferenceCount(obj);
let uid = this.getSerializeObjectIdentifier(obj);
let condition = t.binaryExpression("!==", t.memberExpression(uid, protoExpression), serializedProto);
let throwblock = t.blockStatement([
t.throwStatement(t.newExpression(t.identifier("Error"), [t.stringLiteral("unexpected prototype")])),
@ -270,7 +271,7 @@ export class ResidualHeapSerializer {
this.emitter.emitNowOrAfterWaitingForDependencies([proto, obj], () => {
invariant(proto);
let serializedProto = this.serializeValue(proto);
let uid = this.residualHeapValueIdentifiers.getIdentifierAndIncrementReferenceCount(obj);
let uid = this.getSerializeObjectIdentifier(obj);
if (!this.realm.isCompatibleWith(this.realm.MOBILE_JSC_VERSION))
this.emitter.emit(
t.expressionStatement(
@ -333,7 +334,7 @@ export class ResidualHeapSerializer {
let V = absVal.args[1];
let earlier_props = absVal.args[2];
if (earlier_props instanceof AbstractValue) this._emitPropertiesWithComputedNames(obj, earlier_props);
let uid = this.residualHeapValueIdentifiers.getIdentifierAndIncrementReferenceCount(obj);
let uid = this.getSerializeObjectIdentifier(obj);
let serializedP = this.serializeValue(P);
let serializedV = this.serializeValue(V);
this.emitter.emit(
@ -358,6 +359,16 @@ export class ResidualHeapSerializer {
}
}
// Overridable.
getSerializeObjectIdentifier(val: Value) {
return this.residualHeapValueIdentifiers.getIdentifierAndIncrementReferenceCount(val);
}
// Overridable.
getSerializeObjectIdentifierOptional(val: Value) {
return this.residualHeapValueIdentifiers.getIdentifierAndIncrementReferenceCountOptional(val);
}
_emitProperty(
val: ObjectValue,
key: string | SymbolValue,
@ -369,11 +380,7 @@ export class ResidualHeapSerializer {
let serializedKey =
key instanceof SymbolValue ? this.serializeValue(key) : this.generator.getAsPropertyNameExpression(key);
let computed = key instanceof SymbolValue || !t.isIdentifier(serializedKey);
return t.memberExpression(
this.residualHeapValueIdentifiers.getIdentifierAndIncrementReferenceCount(val),
serializedKey,
computed
);
return t.memberExpression(this.getSerializeObjectIdentifier(val), serializedKey, computed);
};
if (desc === undefined) {
this._deleteProperty(locationFunction());
@ -450,10 +457,9 @@ export class ResidualHeapSerializer {
? this.serializeValue(key)
: this.generator.getAsPropertyNameExpression(key, /*canBeIdentifier*/ false);
invariant(!this.emitter.getReasonToWaitForDependencies([val]), "precondition of _emitProperty");
let uid = this.residualHeapValueIdentifiers.getIdentifierAndIncrementReferenceCount(val);
body.push(
t.callExpression(this.preludeGenerator.memoizeReference("Object.defineProperty"), [
uid,
this.getSerializeObjectIdentifier(val),
serializedKey,
descriptorId,
])
@ -517,7 +523,7 @@ export class ResidualHeapSerializer {
).length;
}
if (numAdditionalFunctionReferences > 0 || !this.delayInitializations) {
if (numAdditionalFunctionReferences > 0 || !this._options.delayInitializations) {
// We can just emit it into the current function body.
return {
body: this.currentFunctionBody,
@ -557,7 +563,7 @@ export class ResidualHeapSerializer {
let scopes = this.residualValues.get(val);
invariant(scopes !== undefined);
let ref = this.residualHeapValueIdentifiers.getIdentifierAndIncrementReferenceCountOptional(val);
let ref = this.getSerializeObjectIdentifierOptional(val);
if (ref) {
return ref;
}
@ -709,11 +715,7 @@ export class ResidualHeapSerializer {
if (lenProperty instanceof AbstractValue || ToLength(realm, lenProperty) !== numberOfIndexProperties) {
this.emitter.emitNowOrAfterWaitingForDependencies([val], () => {
this._assignProperty(
() =>
t.memberExpression(
this.residualHeapValueIdentifiers.getIdentifierAndIncrementReferenceCount(val),
t.identifier("length")
),
() => t.memberExpression(this.getSerializeObjectIdentifier(val), t.identifier("length")),
() => {
return this.serializeValue(lenProperty);
},
@ -1042,6 +1044,70 @@ export class ResidualHeapSerializer {
return obj;
}
_serializeValueRegExpObject(val: ObjectValue): BabelNodeExpression {
let source = val.$OriginalSource;
let flags = val.$OriginalFlags;
invariant(typeof source === "string");
invariant(typeof flags === "string");
this._emitObjectProperties(val);
source = new RegExp(source).source; // add escapes as per 21.2.3.2.4
return t.regExpLiteral(source, flags);
}
// Overridable.
serializeValueRawObject(val: ObjectValue): BabelNodeExpression {
let proto = val.$Prototype;
let createViaAuxiliaryConstructor =
proto !== this.realm.intrinsics.ObjectPrototype &&
this._findLastObjectPrototype(val) === this.realm.intrinsics.ObjectPrototype &&
proto instanceof ObjectValue;
let remainingProperties = new Map(val.properties);
const dummyProperties = new Set();
let props = [];
for (let [key, propertyBinding] of val.properties) {
let descriptor = propertyBinding.descriptor;
if (descriptor === undefined || descriptor.value === undefined) continue; // deleted
if (!createViaAuxiliaryConstructor && this._canEmbedProperty(val, key, descriptor)) {
let propValue = descriptor.value;
invariant(propValue instanceof Value);
if (this.residualHeapInspector.canIgnoreProperty(val, key)) continue;
let mightHaveBeenDeleted = propValue.mightHaveBeenDeleted();
let serializedKey = this.generator.getAsPropertyNameExpression(key);
let delayReason =
this.emitter.getReasonToWaitForDependencies(propValue) ||
this.emitter.getReasonToWaitForActiveValue(val, mightHaveBeenDeleted);
// Although the property needs to be delayed, we still want to emit dummy "undefined"
// value as part of the object literal to ensure a consistent property ordering.
let serializedValue = voidExpression;
if (delayReason) {
// May need to be cleaned up later.
dummyProperties.add(key);
} else {
remainingProperties.delete(key);
serializedValue = this.serializeValue(propValue);
}
props.push(t.objectProperty(serializedKey, serializedValue));
}
}
this._emitObjectProperties(val, remainingProperties, createViaAuxiliaryConstructor, dummyProperties);
if (createViaAuxiliaryConstructor) {
this.needsAuxiliaryConstructor = true;
let serializedProto = this.serializeValue(proto);
return t.sequenceExpression([
t.assignmentExpression(
"=",
t.memberExpression(constructorExpression, t.identifier("prototype")),
serializedProto
),
t.newExpression(constructorExpression, []),
]);
} else {
return t.objectExpression(props);
}
}
_serializeValueObject(val: ObjectValue): BabelNodeExpression {
// If this object is a prototype object that was implicitly created by the runtime
// for a constructor, then we can obtain a reference to this object
@ -1063,13 +1129,7 @@ export class ResidualHeapSerializer {
let kind = val.getKind();
switch (kind) {
case "RegExp":
let source = val.$OriginalSource;
let flags = val.$OriginalFlags;
invariant(typeof source === "string");
invariant(typeof flags === "string");
this._emitObjectProperties(val);
source = new RegExp(source).source; // add escapes as per 21.2.3.2.4
return t.regExpLiteral(source, flags);
return this._serializeValueRegExpObject(val);
case "Number":
let numberData = val.$NumberData;
invariant(numberData !== undefined);
@ -1123,57 +1183,7 @@ export class ResidualHeapSerializer {
default:
invariant(kind === "Object", "invariant established by visitor");
invariant(this.$ParameterMap === undefined, "invariant established by visitor");
let proto = val.$Prototype;
let createViaAuxiliaryConstructor =
proto !== this.realm.intrinsics.ObjectPrototype &&
this._findLastObjectPrototype(val) === this.realm.intrinsics.ObjectPrototype &&
proto instanceof ObjectValue;
let remainingProperties = new Map(val.properties);
const dummyProperties = new Set();
let props = [];
for (let [key, propertyBinding] of val.properties) {
let descriptor = propertyBinding.descriptor;
if (descriptor === undefined || descriptor.value === undefined) continue; // deleted
if (!createViaAuxiliaryConstructor && this._canEmbedProperty(val, key, descriptor)) {
let propValue = descriptor.value;
invariant(propValue instanceof Value);
if (this.residualHeapInspector.canIgnoreProperty(val, key)) continue;
let mightHaveBeenDeleted = propValue.mightHaveBeenDeleted();
let serializedKey = this.generator.getAsPropertyNameExpression(key);
let delayReason =
this.emitter.getReasonToWaitForDependencies(propValue) ||
this.emitter.getReasonToWaitForActiveValue(val, mightHaveBeenDeleted);
// Although the property needs to be delayed, we still want to emit dummy "undefined"
// value as part of the object literal to ensure a consistent property ordering.
let serializedValue = voidExpression;
if (delayReason) {
// May need to be cleaned up later.
dummyProperties.add(key);
} else {
remainingProperties.delete(key);
serializedValue = this.serializeValue(propValue);
}
props.push(t.objectProperty(serializedKey, serializedValue));
}
}
this._emitObjectProperties(val, remainingProperties, createViaAuxiliaryConstructor, dummyProperties);
if (createViaAuxiliaryConstructor) {
this.needsAuxiliaryConstructor = true;
let serializedProto = this.serializeValue(proto);
return t.sequenceExpression([
t.assignmentExpression(
"=",
t.memberExpression(constructorExpression, t.identifier("prototype")),
serializedProto
),
t.newExpression(constructorExpression, []),
]);
} else {
return t.objectExpression(props);
}
return this.serializeValueRawObject(val);
}
}
@ -1202,7 +1212,6 @@ export class ResidualHeapSerializer {
}
_serializeAbstractValueHelper(val: AbstractValue): BabelNodeExpression {
invariant(val.kind !== "sentinel member expression", "invariant established by visitor");
let serializedArgs = val.args.map((abstractArg, i) => this.serializeValue(abstractArg));
let serializedValue = val.buildNode(serializedArgs);
if (serializedValue.type === "Identifier") {
@ -1217,13 +1226,11 @@ export class ResidualHeapSerializer {
if (val.hasIdentifier()) {
return this._serializeAbstractValueHelper(val);
} else {
// This abstract value's dependencies may have been declared
// This abstract value's dependencies should all be declared
// but still need to check it again in case their serialized bodies are in different generator scope.
this.emitter.emitNowOrAfterWaitingForDependencies(val.args, () => {
const serializedValue = this._serializeAbstractValueHelper(val);
// Fetch the previous assigned identifier by residualHeapValueIdentifiers.setIdentifier()
// before _serializeAbstractValue() is called.
let uid = this.residualHeapValueIdentifiers.getIdentifierAndIncrementReferenceCount(val);
let uid = this.getSerializeObjectIdentifier(val);
let declar = t.variableDeclaration("var", [t.variableDeclarator(uid, serializedValue)]);
this.emitter.emit(declar);
});
@ -1430,10 +1437,16 @@ export class ResidualHeapSerializer {
return rewrittenAdditionalFunctions;
}
// Hook point for any serialization needs to be done after generator serialization is complete.
postGeneratorSerialization(): void {
// For overriding only.
}
serialize(): BabelNodeFile {
this.generator.serialize(this._getContext());
invariant(this.emitter._declaredAbstractValues.size <= this.preludeGenerator.derivedIds.size);
this.postGeneratorSerialization();
Array.prototype.push.apply(this.prelude, this.preludeGenerator.prelude);
// TODO #20: add timers
@ -1494,6 +1507,7 @@ export class ResidualHeapSerializer {
])
);
}
let body = this.prelude.concat(this.emitter.getBody().entries);
factorifyObjects(body, this.factoryNameGenerator);

View File

@ -29,6 +29,7 @@ import { LoggingTracer } from "./LoggingTracer.js";
import { ResidualHeapVisitor } from "./ResidualHeapVisitor.js";
import { ResidualHeapSerializer } from "./ResidualHeapSerializer.js";
import { ResidualHeapValueIdentifiers } from "./ResidualHeapValueIdentifiers.js";
import { LazyObjectsSerializer } from "./LazyObjectsSerializer.js";
import * as t from "babel-types";
export class Serializer {
@ -156,7 +157,7 @@ export class Serializer {
residualHeapVisitor.values,
residualHeapVisitor.functionInstances,
residualHeapVisitor.functionInfos,
!!this.options.delayInitializations,
this.options,
residualHeapVisitor.referencedDeclaredValues,
additionalFunctionValuesAndEffects,
residualHeapVisitor.additionalFunctionValueInfos,
@ -170,7 +171,8 @@ export class Serializer {
// Serialize for a second time, using reference counts to minimize number of generated identifiers
if (timingStats !== undefined) timingStats.serializePassTime = Date.now();
let residualHeapSerializer = new ResidualHeapSerializer(
const TargetSerializer = this.options.lazyObjectsRuntime != null ? LazyObjectsSerializer : ResidualHeapSerializer;
let residualHeapSerializer = new TargetSerializer(
this.realm,
this.logger,
this.modules,
@ -179,7 +181,7 @@ export class Serializer {
residualHeapVisitor.values,
residualHeapVisitor.functionInstances,
residualHeapVisitor.functionInfos,
!!this.options.delayInitializations,
this.options,
residualHeapVisitor.referencedDeclaredValues,
additionalFunctionValuesAndEffects,
residualHeapVisitor.additionalFunctionValueInfos,

View File

@ -20,7 +20,12 @@ import invariant from "../invariant.js";
export type TryQuery<T> = (f: () => T, defaultValue: T, logFailures: boolean) => T;
// TODO: add type for additional functions.
export type SerializedBodyType = "MainGenerator" | "Generator" | "DelayInitializations" | "ConditionalAssignmentBranch";
export type SerializedBodyType =
| "MainGenerator"
| "Generator"
| "DelayInitializations"
| "ConditionalAssignmentBranch"
| "LazyObjectInitializer";
export type SerializedBody = {
type: SerializedBodyType,