mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-09-11 14:46:37 +03:00
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:
parent
3334237ab7
commit
b6050b06b9
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
203
src/serializer/LazyObjectsSerializer.js
Normal file
203
src/serializer/LazyObjectsSerializer.js
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user