Serialize independent functions

Summary:
Currently works for all test cases except `noconflict-existantobject.js`.

There are a couple things that are dubious in the current implementation that I will address as I iterate on it:
* We don't visit things correctly in the visitor or the serializer -- we should be visiting only:
  * generator entries in effects
  * Bindings, PropertyBindings in effects not relating to CreatedObjects
* There is currently no way to emit to the main body after the main generator has been serialized -- we may need to if a value is only reachable from one of the additional functions.
* More thought needs to go into how this interacts with
  * speculative initialization
  * require optimization
  * inlining
Closes https://github.com/facebook/prepack/pull/912

Differential Revision: D5859741

Pulled By: cblappert

fbshipit-source-id: 9fe7bd4429ad53629582f9fb7154a28971159554
This commit is contained in:
Chris Blappert 2017-09-25 16:50:15 -07:00 committed by Facebook Github Bot
parent 8d7390f7d5
commit cd3821ab7e
29 changed files with 880 additions and 92 deletions

View File

@ -36,7 +36,7 @@
"test-sourcemaps": "babel-node scripts/generate-sourcemaps-test.js && bash < scripts/test-sourcemaps.sh",
"test-test262": "babel-node scripts/test262-runner.js",
"test-test262-nightly": "NIGHTLY_BUILD=true babel-node -- scripts/test262-runner.js",
"test-internal": "babel-node --stack_size=10000 --stack_trace_limit=200 --max_old_space_size=8192 scripts/test-internal.js",
"test-internal": "babel-node --stack_size=10000 --stack_trace_limit=200 --max_old_space_size=16384 scripts/test-internal.js",
"test-error-handler": "babel-node scripts/test-error-handler.js",
"test-error-handler-with-coverage": "./node_modules/.bin/istanbul cover ./lib/test-error-handler.js --dir coverage.error && ./node_modules/.bin/remap-istanbul -i coverage.error/coverage.json -o coverage-sourcemapped.error -t html",
"test-node-cli-mode": "bash < scripts/test-node-cli-mode.sh",

View File

@ -60,7 +60,19 @@ function runTest(name: string, code: string): boolean {
let modelCode = fs.existsSync(modelName) ? fs.readFileSync(modelName, "utf8") : undefined;
let sourceMap = fs.existsSync(sourceMapName) ? fs.readFileSync(sourceMapName, "utf8") : undefined;
let sources = [];
if (modelCode) sources.push({ filePath: modelName, fileContents: modelCode });
let additionalFunctions;
if (modelCode) {
/* allows specifying additional functions by a comment of the form:
// additional function: additional1, additional2
*/
let marker = "// additional functions:";
if (modelCode.includes(marker)) {
let i = modelCode.indexOf(marker);
let value = modelCode.substring(i + marker.length, modelCode.indexOf("\n", i));
additionalFunctions = value.split(",").map(funcStr => funcStr.trim());
}
sources.push({ filePath: modelName, fileContents: modelCode });
}
sources.push({ filePath: name, fileContents: sourceCode, sourceMapContents: sourceMap });
let options = {
@ -72,6 +84,7 @@ function runTest(name: string, code: string): boolean {
serialize: true,
initializeMoreModules: !modelCode,
sourceMaps: !!sourceMap,
additionalFunctions: additionalFunctions,
};
if (name.endsWith("/bundle.js~"))
(options: any).additionalFunctions = [

View File

@ -119,8 +119,9 @@ export default function(realm: Realm): void {
});
// Maps from initialized moduleId to exports object
// NB: Changes to this shouldn't ever be serialized
global.$DefineOwnProperty("__initializedModules", {
value: new ObjectValue(realm),
value: new ObjectValue(realm, realm.intrinsics.ObjectPrototype, "__initializedModules", true),
writable: true,
enumerable: false,
configurable: true,

View File

@ -330,7 +330,11 @@ export class Realm {
return this.evaluateForEffects(() => env.evaluateAbstractCompletion(ast, strictCode), state);
}
evaluateNodeForEffectsInGlobalEnv(node: BabelNode, state?: any) {
evaluateAndRevertInGlobalEnv(func: () => void): void {
this.wrapInGlobalEnv(() => this.evaluateForEffects(func));
}
evaluateNodeForEffectsInGlobalEnv(node: BabelNode, state?: any): Effects {
return this.wrapInGlobalEnv(() => this.evaluateNodeForEffects(node, false, this.$GlobalEnv, state));
}

View File

@ -172,7 +172,11 @@ export class ResidualFunctions {
return scope;
}
_referentialize(unbound: Names, instances: Array<FunctionInstance>): void {
_referentialize(
unbound: Names,
instances: Array<FunctionInstance>,
shouldReferentializeInstanceFn: FunctionInstance => boolean
): void {
for (let instance of instances) {
let serializedBindings = instance.serializedBindings;
@ -182,6 +186,9 @@ export class ResidualFunctions {
if (serializedBinding.modified) {
// Initialize captured scope at function call instead of globally
if (!serializedBinding.referentialized) {
if (!shouldReferentializeInstanceFn(instance)) {
throw new FatalError("TODO: implement referentialization for prepacked functions");
}
let scope = this._getSerializedBindingScopeInstance(serializedBinding);
let capturedScope = "__captured" + scope.name;
// Save the serialized value for initialization at the top of
@ -229,7 +236,9 @@ export class ResidualFunctions {
];
}
spliceFunctions(): ResidualFunctionsResult {
spliceFunctions(
rewrittenAdditionalFunctions: Map<FunctionValue, Array<BabelNodeStatement>>
): ResidualFunctionsResult {
this.residualFunctionInitializers.scrubFunctionInitializers();
let functionBodies = new Map();
@ -252,7 +261,11 @@ export class ResidualFunctions {
for (let [funcBody, instances] of functionEntries) {
let functionInfo = this.residualFunctionInfos.get(funcBody);
invariant(functionInfo);
this._referentialize(functionInfo.unbound, instances);
this._referentialize(
functionInfo.unbound,
instances,
instance => !rewrittenAdditionalFunctions.has(instance.functionValue)
);
}
for (let [funcBody, instances] of functionEntries) {
@ -270,32 +283,69 @@ export class ResidualFunctions {
let define = (instance, funcId, funcNode) => {
let { functionValue } = instance;
let body;
let addToBody;
if (t.isFunctionExpression(funcNode)) {
funcNodes.set(functionValue, ((funcNode: any): BabelNodeFunctionExpression));
body = this.prelude;
addToBody = elem => {
// Let additional functions override the prelude to be their own body.
// In this case, prepend to that body to simulate a "prelude" in the function
if (instance.preludeOverride) instance.preludeOverride.unshift(elem);
else this.prelude.push(elem);
};
} else {
invariant(t.isCallExpression(funcNode)); // .bind call
body = getFunctionBody(instance);
addToBody = elem => getFunctionBody(instance).push(elem);
}
let declaration = t.variableDeclaration("var", [t.variableDeclarator(funcId, funcNode)]);
body.push(declaration);
addToBody(declaration);
let prototypeId = this.functionPrototypes.get(functionValue);
if (prototypeId !== undefined) {
let id = this.locationService.getLocation(functionValue);
invariant(id !== undefined);
body.push(
addToBody(
t.variableDeclaration("var", [
t.variableDeclarator(prototypeId, t.memberExpression(id, t.identifier("prototype"))),
])
);
}
};
// Split instances into normal or additional functions (whose bodies have been rewritten)
let normalInstances = [];
let additionalFunctionInstances = [];
for (let instance of instances) {
if (rewrittenAdditionalFunctions.has(instance.functionValue)) additionalFunctionInstances.push(instance);
else normalInstances.push(instance);
}
if (shouldInline || instances.length === 1 || usesArguments) {
this.statistics.functionClones += instances.length - 1;
let rewrittenBody = rewrittenAdditionalFunctions.get(instances[0].functionValue);
for (let instance of instances) {
// rewritten functions shouldn't have references fixed up because the body,
// consists of serialized code. For simplicity we emit their instances in a naive way
if (rewrittenBody) {
let functionBody = t.blockStatement(rewrittenBody);
this.statistics.functionClones += additionalFunctionInstances.length - 1;
for (let instance of additionalFunctionInstances) {
let { functionValue } = instance;
let id = this.locationService.getLocation(functionValue);
invariant(id !== undefined);
let funcParams = params.slice();
let funcNode = t.functionExpression(null, funcParams, functionBody);
if (functionValue.$Strict) {
strictFunctionBodies.push(funcNode);
} else {
unstrictFunctionBodies.push(funcNode);
}
define(instance, id, funcNode);
}
}
if (normalInstances.length === 0) continue;
if (shouldInline || normalInstances.length === 1 || usesArguments) {
this.statistics.functionClones += normalInstances.length - 1;
for (let instance of normalInstances) {
let { functionValue, serializedBindings, scopeInstances } = instance;
let id = this.locationService.getLocation(functionValue);
invariant(id !== undefined);
@ -331,7 +381,7 @@ export class ResidualFunctions {
define(instance, id, funcNode);
}
} else {
let suffix = instances[0].functionValue.__originalName || "";
let suffix = normalInstances[0].functionValue.__originalName || "";
let factoryId = t.identifier(this.factoryNameGenerator.generate(suffix));
// filter included variables to only include those that are different
@ -341,13 +391,13 @@ export class ResidualFunctions {
let isDifferent = false;
let lastBinding;
if (instances[0].serializedBindings[name].modified) {
if (normalInstances[0].serializedBindings[name].modified) {
// Must modify for traversal
sameSerializedBindings[name] = instances[0].serializedBindings[name];
sameSerializedBindings[name] = normalInstances[0].serializedBindings[name];
continue;
}
for (let { serializedBindings } of instances) {
for (let { serializedBindings } of normalInstances) {
let serializedBinding = serializedBindings[name];
invariant(!serializedBinding.modified);
@ -374,7 +424,7 @@ export class ResidualFunctions {
}
let scopeInitialization = [];
for (let scope of instances[0].scopeInstances) {
for (let scope of normalInstances[0].scopeInstances) {
factoryParams.push(t.identifier(scope.name));
scopeInitialization = scopeInitialization.concat(this._getReferentializedScopeInitialization(scope));
}
@ -388,7 +438,7 @@ export class ResidualFunctions {
((t.cloneDeep(funcBody): any): BabelNodeBlockStatement)
);
if (instances[0].functionValue.$Strict) {
if (normalInstances[0].functionValue.$Strict) {
strictFunctionBodies.push(factoryNode);
} else {
unstrictFunctionBodies.push(factoryNode);
@ -405,10 +455,10 @@ export class ResidualFunctions {
modified,
requireReturns: this.requireReturns,
requireStatistics,
isRequire: this.modules.getIsRequire(factoryParams, instances.map(instance => instance.functionValue)),
isRequire: this.modules.getIsRequire(factoryParams, normalInstances.map(instance => instance.functionValue)),
});
for (let instance of instances) {
for (let instance of normalInstances) {
let { functionValue, serializedBindings, insertionPoint } = instance;
let functionId = this.locationService.getLocation(functionValue);
invariant(functionId !== undefined);

View File

@ -54,6 +54,7 @@ import { voidExpression, emptyExpression, constructorExpression, protoExpression
import { Emitter } from "./Emitter.js";
import { ResidualHeapValueIdentifiers } from "./ResidualHeapValueIdentifiers.js";
import { commonAncestorOf, getSuggestedArrayLiteralLength } from "./utils.js";
import type { Effects } from "../realm.js";
export class ResidualHeapSerializer {
constructor(
@ -67,6 +68,7 @@ export class ResidualHeapSerializer {
residualFunctionInfos: Map<BabelNodeBlockStatement, FunctionInfo>,
delayInitializations: boolean,
referencedDeclaredValues: Set<AbstractValue>,
additionalFunctionValuesAndEffects: Map<FunctionValue, Effects> | void,
statistics: SerializerStatistics
) {
this.realm = realm;
@ -102,7 +104,7 @@ export class ResidualHeapSerializer {
getLocation: value => this.residualHeapValueIdentifiers.getIdentifierAndIncrementReferenceCountOptional(value),
createLocation: () => {
let location = t.identifier(this.valueNameGenerator.generate("initialized"));
this.mainBody.push(t.variableDeclaration("var", [t.variableDeclarator(location)]));
this.currentFunctionBody.push(t.variableDeclaration("var", [t.variableDeclarator(location)]));
return location;
},
},
@ -114,6 +116,7 @@ export class ResidualHeapSerializer {
);
this.emitter = new Emitter(this.residualFunctions, delayInitializations);
this.mainBody = this.emitter.getBody();
this.currentFunctionBody = this.mainBody;
this.residualHeapInspector = residualHeapInspector;
this.residualValues = residualValues;
this.residualFunctionBindings = residualFunctionBindings;
@ -121,6 +124,8 @@ export class ResidualHeapSerializer {
this.delayInitializations = delayInitializations;
this.referencedDeclaredValues = referencedDeclaredValues;
this.activeGeneratorBodies = new Map();
this.additionalFunctionValuesAndEffects = additionalFunctionValuesAndEffects;
this.additionalFunctionValueNestedFunctions = new Set();
}
emitter: Emitter;
@ -130,6 +135,9 @@ export class ResidualHeapSerializer {
prelude: Array<BabelNodeStatement>;
body: Array<BabelNodeStatement>;
mainBody: Array<BabelNodeStatement>;
// if we're in an additional function we need to access both mainBody and the
// additional function's body which will be currentFunctionBody.
currentFunctionBody: Array<BabelNodeStatement>;
realm: Realm;
preludeGenerator: PreludeGenerator;
generator: Generator;
@ -155,6 +163,10 @@ export class ResidualHeapSerializer {
delayInitializations: boolean;
referencedDeclaredValues: Set<AbstractValue>;
activeGeneratorBodies: Map<Generator, Array<BabelNodeStatement>>;
additionalFunctionValuesAndEffects: Map<FunctionValue, Effects> | void;
// function values nested in additional functions can't delay initializations
// TODO: revisit this and fix additional functions to be capable of delaying initializations
additionalFunctionValueNestedFunctions: Set<FunctionValue>;
// Configures all mutable aspects of an object, in particular:
// symbols, properties, prototype.
@ -325,32 +337,36 @@ export class ResidualHeapSerializer {
_emitProperty(
val: ObjectValue,
key: string | SymbolValue,
desc: Descriptor,
cleanupDummyProperty: boolean = false
desc: Descriptor | void,
deleteIfMightHaveBeenDeleted: boolean = false
): void {
if (this._canEmbedProperty(val, key, desc)) {
// Location for the property to be assigned to
let locationFunction = () => {
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
);
};
if (desc === undefined) {
this._deleteProperty(locationFunction());
} else if (this._canEmbedProperty(val, key, desc)) {
let descValue = desc.value;
invariant(descValue instanceof Value);
invariant(!this.emitter.getReasonToWaitForDependencies([descValue, val]), "precondition of _emitProperty");
let mightHaveBeenDeleted = descValue.mightHaveBeenDeleted();
// The only case we do not need to remove the dummy property is array index property.
this._assignProperty(
() => {
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
);
},
locationFunction,
() => {
invariant(descValue instanceof Value);
return this.serializeValue(descValue);
},
mightHaveBeenDeleted,
cleanupDummyProperty
deleteIfMightHaveBeenDeleted
);
} else {
this.emitter.emit(this.emitDefinePropertyBody(val, key, desc));
@ -463,7 +479,7 @@ export class ResidualHeapSerializer {
if (scope === this.realm.generator) {
// This value is used from the main generator scope. This means that we need to emit the value and its
// initialization code into the main body, and cannot delay initialization.
return { body: this.mainBody };
return { body: this.currentFunctionBody };
}
generators.push(scope);
}
@ -472,16 +488,28 @@ export class ResidualHeapSerializer {
if (generators.length === 0) {
// This value is only referenced from residual functions.
invariant(functionValues.length > 0);
if (this.delayInitializations) {
let additionalFunctionValuesAndEffects = this.additionalFunctionValuesAndEffects;
let numAdditionalFunctionReferences = 0;
// Make sure we don't delay things referenced by additional functions or nested functions
if (additionalFunctionValuesAndEffects) {
// flow forces me to do this
let additionalFuncValuesAndEffects = additionalFunctionValuesAndEffects;
numAdditionalFunctionReferences = functionValues.filter(
funcValue =>
additionalFuncValuesAndEffects.has(funcValue) || this.additionalFunctionValueNestedFunctions.has(funcValue)
).length;
}
if (numAdditionalFunctionReferences > 0 || !this.delayInitializations) {
// We can just emit it into the current function body.
return { body: this.currentFunctionBody };
} else {
// We can delay the initialization, and move it into a conditional code block in the residual functions!
let body = this.residualFunctions.residualFunctionInitializers.registerValueOnlyReferencedByResidualFunctions(
functionValues,
val
);
return { body, usedOnlyByResidualFunctions: true };
} else {
// We can just emit it into the main body which will get executed unconditionally.
return { body: this.mainBody };
}
}
@ -489,12 +517,14 @@ export class ResidualHeapSerializer {
// We can emit the initialization of this value into the body associated with their common ancestor.
let commonAncestor = Array.from(scopes).reduce((x, y) => commonAncestorOf(x, y), generators[0]);
invariant(commonAncestor instanceof Generator); // every scope is either the root, or a descendant
let body = commonAncestor === this.generator ? this.mainBody : this.activeGeneratorBodies.get(commonAncestor);
let body =
commonAncestor === this.generator ? this.currentFunctionBody : this.activeGeneratorBodies.get(commonAncestor);
invariant(body !== undefined);
return { body: body };
}
serializeValue(val: Value, referenceOnly?: boolean, bindingType?: BabelVariableKind): BabelNodeExpression {
invariant(!val.refuseSerialization);
let scopes = this.residualValues.get(val);
invariant(scopes !== undefined);
@ -556,7 +586,7 @@ export class ResidualHeapSerializer {
return this.preludeGenerator.convertStringToMember(intrinsicName);
} else {
// The intrinsic conceptually exists ahead of time.
invariant(this.emitter.getBody() === this.mainBody);
invariant(this.emitter.getBody() === this.currentFunctionBody);
return this.preludeGenerator.memoizeReference(intrinsicName);
}
}
@ -568,11 +598,18 @@ export class ResidualHeapSerializer {
return [desc.get, desc.set];
}
_deleteProperty(location: BabelNodeLVal) {
invariant(location.type === "MemberExpression");
this.emitter.emit(
t.expressionStatement(t.unaryExpression("delete", ((location: any): BabelNodeMemberExpression), true))
);
}
_assignProperty(
locationFn: () => BabelNodeLVal,
valueFn: () => BabelNodeExpression,
mightHaveBeenDeleted: boolean,
cleanupDummyProperty: boolean = false
deleteIfMightHaveBeenDeleted: boolean = false
) {
let location = locationFn();
let value = valueFn();
@ -580,7 +617,7 @@ export class ResidualHeapSerializer {
if (mightHaveBeenDeleted) {
let condition = t.binaryExpression("!==", value, this.serializeValue(this.realm.intrinsics.empty));
let deletion = null;
if (cleanupDummyProperty) {
if (deleteIfMightHaveBeenDeleted) {
invariant(location.type === "MemberExpression");
deletion = t.expressionStatement(
t.unaryExpression("delete", ((location: any): BabelNodeMemberExpression), true)
@ -820,6 +857,7 @@ export class ResidualHeapSerializer {
}
invariant(!(val instanceof NativeFunctionValue), "all native function values should be intrinsics");
invariant(val instanceof ECMAScriptSourceFunctionValue);
let residualBindings = this.residualFunctionBindings.get(val);
invariant(residualBindings);
@ -832,6 +870,7 @@ export class ResidualHeapSerializer {
scopeInstances: new Set(),
};
if (this.currentFunctionBody !== this.mainBody) instance.preludeOverride = this.currentFunctionBody;
let delayed = 1;
let undelay = () => {
if (--delayed === 0) {
@ -1099,19 +1138,23 @@ export class ResidualHeapSerializer {
}
}
_withGeneratorScope(generator: Generator, callback: (Array<BabelNodeStatement>) => void): Array<BabelNodeStatement> {
let newBody = [];
let oldBody = this.emitter.beginEmitting(generator, newBody);
this.activeGeneratorBodies.set(generator, newBody);
callback(newBody);
this.activeGeneratorBodies.delete(generator);
return this.emitter.endEmitting(generator, oldBody);
}
_getContext(): SerializationContext {
// TODO #482: Values serialized by nested generators would currently only get defined
// along the code of the nested generator; their definitions need to get hoisted
// or repeated so that they are accessible and defined from all using scopes
let context = {
serializeValue: this.serializeValue.bind(this),
serializeGenerator: (generator: Generator) => {
let newBody = [];
let oldBody = this.emitter.beginEmitting(generator, newBody);
this.activeGeneratorBodies.set(generator, newBody);
generator.serialize(context);
this.activeGeneratorBodies.delete(generator);
return this.emitter.endEmitting(generator, oldBody);
serializeGenerator: (generator: Generator): Array<BabelNodeStatement> => {
return this._withGeneratorScope(generator, () => generator.serialize(context));
},
emit: (statement: BabelNodeStatement) => {
this.emitter.emit(statement);
@ -1127,6 +1170,17 @@ export class ResidualHeapSerializer {
return context;
}
_serializeAdditionalFunction(generator: Generator, postGeneratorCallback: () => void) {
let context = this._getContext();
return this._withGeneratorScope(generator, newBody => {
let oldCurBody = this.currentFunctionBody;
this.currentFunctionBody = newBody;
generator.serialize(context);
if (postGeneratorCallback) postGeneratorCallback();
this.currentFunctionBody = oldCurBody;
});
}
_shouldBeWrapped(body: Array<any>) {
for (let i = 0; i < body.length; i++) {
let item = body[i];
@ -1154,6 +1208,73 @@ export class ResidualHeapSerializer {
return false;
}
processAdditionalFunctionValues(): Map<FunctionValue, Array<BabelNodeStatement>> {
let rewrittenAdditionalFunctions: Map<FunctionValue, Array<BabelNodeStatement>> = new Map();
let shouldEmitLog = !this.residualHeapValueIdentifiers.collectValToRefCountOnly;
let processAdditionalFunctionValuesFn = () => {
let additionalFVEffects = this.additionalFunctionValuesAndEffects;
if (additionalFVEffects) {
for (let [additionalFunctionValue, effects] of additionalFVEffects.entries()) {
let [
result,
generator,
modifiedBindings,
modifiedProperties: Map<PropertyBinding, void | Descriptor>,
createdObjects,
] = effects;
let nestedFunctions = new Set([...createdObjects].filter(object => object instanceof FunctionValue));
// result -- ignore TODO: return the result from the function somehow
// Generator -- visit all entries
// Bindings -- only need to serialize bindings if they're captured by some nested function ??
// -- need to apply them and maybe need to revisit functions in ancestors to make sure
// -- we don't overwrite anything they capture
// -- TODO: deal with these properly
// PropertyBindings -- visit any property bindings that aren't to createdobjects
// CreatedObjects -- should take care of itself
this.realm.applyEffects([
result,
new Generator(this.realm),
modifiedBindings,
modifiedProperties,
createdObjects,
]);
// Allows us to emit function declarations etc. inside of this additional
// function instead of adding them at global scope
// TODO: make sure this generator isn't getting mutated oddly
this.additionalFunctionValueNestedFunctions = ((nestedFunctions: any): Set<FunctionValue>);
let serializePropertiesAndBindings = () => {
for (let propertyBinding of modifiedProperties.keys()) {
let binding: PropertyBinding = ((propertyBinding: any): PropertyBinding);
let object = binding.object;
if (object instanceof ObjectValue && createdObjects.has(object)) continue;
if (object.refuseSerialization) continue;
if (object.isIntrinsic()) continue;
invariant(object instanceof ObjectValue);
this._emitProperty(object, binding.key, binding.descriptor, true);
}
};
let body = this._serializeAdditionalFunction(generator, serializePropertiesAndBindings);
invariant(additionalFunctionValue instanceof ECMAScriptSourceFunctionValue);
rewrittenAdditionalFunctions.set(additionalFunctionValue, body);
// re-resolve initialized modules to include things from additional functions
this.modules.resolveInitializedModules();
if (shouldEmitLog && this.modules.moduleIds.size > 0)
console.log(
`=== ${this.modules.initializedModules.size} of ${this.modules.moduleIds
.size} modules initialized after additional function ${additionalFunctionValue.intrinsicName
? additionalFunctionValue.intrinsicName
: ""}`
);
// These don't restore themselves properly otherwise.
this.realm.restoreBindings(modifiedBindings);
this.realm.restoreProperties(modifiedProperties);
}
}
};
this.realm.evaluateAndRevertInGlobalEnv(processAdditionalFunctionValuesFn);
return rewrittenAdditionalFunctions;
}
serialize(): BabelNodeFile {
this.generator.serialize(this._getContext());
invariant(this.emitter._declaredAbstractValues.size <= this.preludeGenerator.derivedIds.size);
@ -1163,12 +1284,20 @@ export class ResidualHeapSerializer {
// TODO #20: add timers
// TODO #21: add event listeners
for (let [moduleId, moduleValue] of this.modules.initializedModules)
this.requireReturns.set(moduleId, this.serializeValue(moduleValue));
// Make sure additional functions get serialized.
let rewrittenAdditionalFunctions = this.processAdditionalFunctionValues();
this.modules.resolveInitializedModules();
this.emitter.finalize();
let { unstrictFunctionBodies, strictFunctionBodies, requireStatistics } = this.residualFunctions.spliceFunctions();
let { unstrictFunctionBodies, strictFunctionBodies, requireStatistics } = this.residualFunctions.spliceFunctions(
rewrittenAdditionalFunctions
);
if (requireStatistics.replaced > 0 && !this.residualHeapValueIdentifiers.collectValToRefCountOnly) {
console.log(
`=== ${this.modules.initializedModules.size} of ${this.modules.moduleIds

View File

@ -12,7 +12,8 @@
import { GlobalEnvironmentRecord, DeclarativeEnvironmentRecord } from "../environment.js";
import { FatalError } from "../errors.js";
import { Realm } from "../realm.js";
import type { Descriptor } from "../types.js";
import type { Effects } from "../realm.js";
import type { Descriptor, PropertyBinding } from "../types.js";
import { IsUnresolvableReference, ToLength, ResolveBinding, HashSet, IsArray, Get } from "../methods/index.js";
import {
BoundFunctionValue,
@ -24,6 +25,7 @@ import {
FunctionValue,
Value,
ObjectValue,
AbstractObjectValue,
NativeFunctionValue,
} from "../values/index.js";
import { describeLocation } from "../intrinsics/ecma262/Error.js";
@ -49,7 +51,12 @@ export type Scope = FunctionValue | Generator;
TODO #680: Figure out minimal set of values that need to be kept alive for WeakSet and WeakMap instances.
*/
export class ResidualHeapVisitor {
constructor(realm: Realm, logger: Logger, modules: Modules) {
constructor(
realm: Realm,
logger: Logger,
modules: Modules,
additionalFunctionValuesAndEffects: Map<FunctionValue, Effects>
) {
invariant(realm.useAbstractInterpretation);
this.realm = realm;
this.logger = logger;
@ -62,10 +69,11 @@ export class ResidualHeapVisitor {
this.values = new Map();
let generator = this.realm.generator;
invariant(generator);
this.scope = this.realmGenerator = generator;
this.scope = this.commonScope = generator;
this.inspector = new ResidualHeapInspector(realm, logger);
this.referencedDeclaredValues = new Set();
this.delayedVisitGeneratorEntries = [];
this.additionalFunctionValuesAndEffects = additionalFunctionValuesAndEffects;
this.equivalenceSet = new HashSet();
}
@ -78,11 +86,13 @@ export class ResidualHeapVisitor {
functionInfos: Map<BabelNodeBlockStatement, FunctionInfo>;
functionBindings: Map<FunctionValue, VisitedBindings>;
scope: Scope;
realmGenerator: Generator;
// Either the realm's generator or the FunctionValue of an additional function to serialize
commonScope: Scope;
values: Map<Value, Set<Scope>>;
inspector: ResidualHeapInspector;
referencedDeclaredValues: Set<AbstractValue>;
delayedVisitGeneratorEntries: Array<{| generator: Generator, entry: GeneratorEntry |}>;
delayedVisitGeneratorEntries: Array<{| commonScope: Scope, generator: Generator, entry: GeneratorEntry |}>;
additionalFunctionValuesAndEffects: Map<FunctionValue, Effects>;
equivalenceSet: HashSet<AbstractValue>;
_withScope(scope: Scope, f: () => void) {
@ -92,6 +102,15 @@ export class ResidualHeapVisitor {
this.scope = oldScope;
}
visitObjectProperty(binding: PropertyBinding) {
let desc = binding.descriptor;
if (desc === undefined) return; //deleted
let obj = binding.object;
if (obj instanceof AbstractObjectValue || !this.inspector.canIgnoreProperty(obj, binding.key)) {
this.visitDescriptor(desc);
}
}
visitObjectProperties(obj: ObjectValue): void {
// visit properties
for (let [symbol, propertyBinding] of obj.symbols) {
@ -103,13 +122,9 @@ export class ResidualHeapVisitor {
}
// visit properties
for (let [key, propertyBinding] of obj.properties) {
for (let propertyBinding of obj.properties.values()) {
invariant(propertyBinding);
let desc = propertyBinding.descriptor;
if (desc === undefined) continue; //deleted
if (!this.inspector.canIgnoreProperty(obj, key)) {
this.visitDescriptor(desc);
}
this.visitObjectProperty(propertyBinding);
}
// inject properties with computed names
@ -321,7 +336,7 @@ export class ResidualHeapVisitor {
let reference = this.logger.tryQuery(
() => ResolveBinding(this.realm, innerName, doesNotMatter, val.$Environment),
undefined,
false /* The only reason `ResolveBinding` might fail is because the global object is partial. But in that case, we know that we are dealing with the global scope. */
false /* The only reason `ResolveBinding` might fail is because the global object is partial. But in that case, we know that we are dealing with the common scope. */
);
if (
reference === undefined ||
@ -438,12 +453,13 @@ export class ResidualHeapVisitor {
}
visitValue(val: Value): void {
invariant(!val.refuseSerialization);
if (val instanceof AbstractValue) {
if (this._mark(val)) this.visitAbstractValue(val);
} else if (val.isIntrinsic()) {
// All intrinsic values exist from the beginning of time...
// ...except for a few that come into existance as templates for abstract objects (TODO #882).
this._withScope(this.realmGenerator, () => {
this._withScope(this.commonScope, () => {
this._mark(val);
});
} else if (val instanceof EmptyValue) {
@ -456,8 +472,8 @@ export class ResidualHeapVisitor {
} else if (val instanceof ProxyValue) {
if (this._mark(val)) this.visitValueProxy(val);
} else if (val instanceof FunctionValue) {
// Function declarations should get hoisted in the global code so that instances only get allocated once
this._withScope(this.realmGenerator, () => {
// Function declarations should get hoisted in common scope so that instances only get allocated once
this._withScope(this.commonScope, () => {
invariant(val instanceof FunctionValue);
if (this._mark(val)) this.visitValueFunction(val);
});
@ -467,9 +483,9 @@ export class ResidualHeapVisitor {
invariant(val instanceof ObjectValue);
// Prototypes are reachable via function declarations, and those get hoisted, so we need to move
// prototype initialization to the global code as well.
// prototype initialization to the common scope code as well.
if (val.originalConstructor !== undefined) {
this._withScope(this.realmGenerator, () => {
this._withScope(this.commonScope, () => {
invariant(val instanceof ObjectValue);
if (this._mark(val)) this.visitValueObject(val);
});
@ -490,7 +506,7 @@ export class ResidualHeapVisitor {
return binding;
}
createGeneratorVisitCallbacks(generator: Generator): VisitEntryCallbacks {
createGeneratorVisitCallbacks(generator: Generator, commonScope: Scope): VisitEntryCallbacks {
return {
visitValues: (values: Array<Value>) => {
for (let i = 0, n = values.length; i < n; i++) values[i] = this.visitEquivalentValue(values[i]);
@ -503,30 +519,84 @@ export class ResidualHeapVisitor {
this.referencedDeclaredValues.add(value);
},
recordDelayedEntry: (entry: GeneratorEntry) => {
this.delayedVisitGeneratorEntries.push({ generator, entry });
this.delayedVisitGeneratorEntries.push({ commonScope, generator, entry });
},
};
}
visitGenerator(generator: Generator): void {
this._withScope(generator, () => {
generator.visit(this.createGeneratorVisitCallbacks(generator));
generator.visit(this.createGeneratorVisitCallbacks(generator, this.commonScope));
});
}
visitRoots(): void {
this.visitGenerator(this.realmGenerator);
for (let [, moduleValue] of this.modules.initializedModules) this.visitValue(moduleValue);
visitAdditionalFunctionEffects() {
for (let [functionValue, effects] of this.additionalFunctionValuesAndEffects.entries()) {
let [
result,
generator,
modifiedBindings,
modifiedProperties: Map<PropertyBinding, void | Descriptor>,
createdObjects,
] = effects;
// Need to do this fixup because otherwise we will skip over this function's
// generator in the _getTarget scope lookup
generator.parent = functionValue.parent;
functionValue.parent = generator;
// result -- ignore TODO: return the result from the function somehow
// Generator -- visit all entries
// Bindings -- (modifications to named variables) only need to serialize bindings if they're
// captured by a residual function
// -- need to apply them and maybe need to revisit functions in ancestors to make sure
// we don't overwrite anything they capture
// -- TODO: deal with these properly
// PropertyBindings -- (property modifications) visit any property bindings to pre-existing objects
// CreatedObjects -- should take care of itself
this.realm.applyEffects([
result,
new Generator(this.realm),
modifiedBindings,
modifiedProperties,
createdObjects,
]);
// Allows us to emit function declarations etc. inside of this additional
// function instead of adding them at global scope
this.commonScope = functionValue;
let visitPropertiesAndBindings = () => {
for (let propertyBinding of modifiedProperties.keys()) {
let binding: PropertyBinding = ((propertyBinding: any): PropertyBinding);
let object = binding.object;
if (object instanceof ObjectValue && createdObjects.has(object)) continue; // Created Object's binding
if (object.refuseSerialization) continue; // modification to internal state
if (object.intrinsicName === "global") continue; // Avoid double-counting
this.visitObjectProperty(binding);
}
};
this.visitGenerator(generator);
this._withScope(generator, visitPropertiesAndBindings);
this.realm.restoreBindings(modifiedBindings);
this.realm.restoreProperties(modifiedProperties);
}
// Do a fixpoint over all pure generator entries to make sure that we visit
// arguments of only BodyEntries that are required by some other residual value
let oldDelayedEntries = [];
while (oldDelayedEntries.length !== this.delayedVisitGeneratorEntries.length) {
oldDelayedEntries = this.delayedVisitGeneratorEntries;
this.delayedVisitGeneratorEntries = [];
for (let { generator, entry } of oldDelayedEntries)
this._withScope(generator, () => {
generator.visitEntry(entry, this.createGeneratorVisitCallbacks(generator));
for (let { commonScope, generator: entryGenerator, entry } of oldDelayedEntries) {
this.commonScope = commonScope;
this._withScope(entryGenerator, () => {
entryGenerator.visitEntry(entry, this.createGeneratorVisitCallbacks(entryGenerator, commonScope));
});
}
}
}
visitRoots(): void {
let generator = this.realm.generator;
invariant(generator);
this.visitGenerator(generator);
for (let moduleValue of this.modules.initializedModules.values()) this.visitValue(moduleValue);
this.realm.evaluateAndRevertInGlobalEnv(this.visitAdditionalFunctionEffects.bind(this));
}
}

View File

@ -26,11 +26,15 @@ export class Functions {
this.realm = realm;
this.functions = functions;
this.moduleTracer = moduleTracer;
this.writeEffects = new Map();
this.nameToFunctionValue = new Map();
}
realm: Realm;
functions: ?Array<string>;
moduleTracer: ModuleTracer;
writeEffects: Map<string, Effects>;
nameToFunctionValue: Map<string, FunctionValue>;
checkThatFunctionsAreIndependent() {
let functions = this.functions;
@ -59,24 +63,24 @@ export class Functions {
this.realm.handleError(error);
throw new FatalError();
}
this.nameToFunctionValue.set(fname, fun);
let call = t.callExpression(fnameAst, []);
calls.push([fname, call]);
}
// Get write effects of the functions
let writeEffects: Map<string, Effects> = new Map();
for (let [fname, call] of calls) {
// This may throw a FatalError if there is an unrecoverable error in the called function
// When that happens we cannot prepack the bundle.
// There may also be warnings reported for errors that happen inside imported modules that can be postponed.
let e = this.realm.evaluateNodeForEffectsInGlobalEnv(call, this.moduleTracer);
writeEffects.set(fname, e);
this.writeEffects.set(fname, e);
}
// check that functions are independent
let conflicts: Map<BabelNodeSourceLocation, CompilerDiagnostic> = new Map();
for (let [fname1, call1] of calls) {
let e1 = writeEffects.get(fname1);
let e1 = this.writeEffects.get(fname1);
invariant(e1 !== undefined);
if (e1[0] instanceof Completion) {
let error = new CompilerDiagnostic(
@ -100,6 +104,16 @@ export class Functions {
}
}
getAdditionalFunctionValuesToEffects(): Map<FunctionValue, Effects> {
let functionValueToEffects = new Map();
for (let [functionString, effects] of this.writeEffects.entries()) {
let funcValue = this.nameToFunctionValue.get(functionString);
invariant(funcValue);
functionValueToEffects.set(funcValue, effects);
}
return functionValueToEffects;
}
reportWriteConflicts(
fname: string,
conflicts: Map<BabelNodeSourceLocation, CompilerDiagnostic>,

View File

@ -308,6 +308,7 @@ export class Modules {
moduleTracer: ModuleTracer;
resolveInitializedModules(): void {
this.initializedModules.clear();
let globalInitializedModulesMap = this._getGlobalProperty("__initializedModules");
invariant(globalInitializedModulesMap instanceof ObjectValue);
for (let moduleId of globalInitializedModulesMap.getOwnPropertyKeysArray()) {
@ -468,7 +469,9 @@ export class Modules {
// Check for escaping property assignments, if none escape, we got an existing object
let escapes = false;
for (let [binding] of properties) {
if (!createdObjects.has(binding.object)) escapes = true;
let object = binding.object;
invariant(object instanceof ObjectValue);
if (!createdObjects.has(object)) escapes = true;
}
if (escapes) return undefined;

View File

@ -109,6 +109,7 @@ export class Serializer {
if (this.options.additionalFunctions) {
this.functions.checkThatFunctionsAreIndependent();
}
if (this.options.initializeMoreModules) {
if (timingStats !== undefined) timingStats.initializeMoreModulesTime = Date.now();
this.modules.initializeMoreModules();
@ -117,10 +118,15 @@ export class Serializer {
timingStats.initializeMoreModulesTime = Date.now() - timingStats.initializeMoreModulesTime;
}
let additionalFunctionValuesAndEffects = this.functions.getAdditionalFunctionValuesToEffects();
//Deep traversal of the heap to identify the necessary scope of residual functions
if (timingStats !== undefined) timingStats.deepTraversalTime = Date.now();
let residualHeapVisitor = new ResidualHeapVisitor(this.realm, this.logger, this.modules);
let residualHeapVisitor = new ResidualHeapVisitor(
this.realm,
this.logger,
this.modules,
additionalFunctionValuesAndEffects
);
residualHeapVisitor.visitRoots();
if (this.logger.hasErrors()) return undefined;
if (timingStats !== undefined) timingStats.deepTraversalTime = Date.now() - timingStats.deepTraversalTime;
@ -128,6 +134,7 @@ export class Serializer {
// Phase 2: Let's serialize the heap and generate code.
// Serialize for the first time in order to gather reference counts
let residualHeapValueIdentifiers = new ResidualHeapValueIdentifiers();
if (this.options.inlineExpressions) {
if (timingStats !== undefined) timingStats.referenceCountsTime = Date.now();
residualHeapValueIdentifiers.initPass1();
@ -142,6 +149,7 @@ export class Serializer {
residualHeapVisitor.functionInfos,
!!this.options.delayInitializations,
residualHeapVisitor.referencedDeclaredValues,
additionalFunctionValuesAndEffects,
this.statistics
).serialize();
if (this.logger.hasErrors()) return undefined;
@ -151,7 +159,6 @@ 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(
this.realm,
this.logger,
@ -163,8 +170,10 @@ export class Serializer {
residualHeapVisitor.functionInfos,
!!this.options.delayInitializations,
residualHeapVisitor.referencedDeclaredValues,
additionalFunctionValuesAndEffects,
this.statistics
);
let ast = residualHeapSerializer.serialize();
let generated = generate(ast, { sourceMaps: sourceMaps }, (code: any));
if (timingStats !== undefined) {

View File

@ -23,6 +23,8 @@ export type FunctionInstance = {
serializedBindings: SerializedBindings,
functionValue: ECMAScriptSourceFunctionValue,
insertionPoint?: BodyReference,
// Optional place to put the function declaration
preludeOverride?: Array<BabelNodeStatement>,
scopeInstances: Set<ScopeBinding>,
};

View File

@ -134,7 +134,8 @@ export class Generator {
});
}
emitPropertyAssignment(object: Value, key: string, value: Value) {
emitPropertyAssignment(object: ObjectValue, key: string, value: Value) {
if (object.refuseSerialization) return;
let propName = this.getAsPropertyNameExpression(key);
this.body.push({
args: [object, value],
@ -146,6 +147,7 @@ export class Generator {
}
emitDefineProperty(object: ObjectValue, key: string, desc: Descriptor) {
if (object.refuseSerialization) return;
if (desc.enumerable && desc.configurable && desc.writable && desc.value) {
let descValue = desc.value;
invariant(descValue instanceof Value);
@ -164,7 +166,8 @@ export class Generator {
}
}
emitPropertyDelete(object: Value, key: string) {
emitPropertyDelete(object: ObjectValue, key: string) {
if (object.refuseSerialization) return;
let propName = this.getAsPropertyNameExpression(key);
this.body.push({
args: [object],

View File

@ -53,7 +53,12 @@ import {
import invariant from "../invariant.js";
export default class ObjectValue extends ConcreteValue {
constructor(realm: Realm, proto?: ObjectValue | NullValue, intrinsicName?: string) {
constructor(
realm: Realm,
proto?: ObjectValue | NullValue,
intrinsicName?: string,
refuseSerialization: boolean = false
) {
super(realm, intrinsicName);
realm.recordNewObject(this);
if (realm.useAbstractInterpretation) this.setupBindings();
@ -63,6 +68,7 @@ export default class ObjectValue extends ConcreteValue {
this._isSimple = realm.intrinsics.false;
this.properties = new Map();
this.symbols = new Map();
this.refuseSerialization = refuseSerialization;
}
static trackedProperties = [
@ -232,6 +238,10 @@ export default class ObjectValue extends ConcreteValue {
return this.hashValue;
}
// We track some internal state as properties on the global object, these should
// never be serialized.
refuseSerialization: boolean;
mightBeFalse(): boolean {
return false;
}

View File

@ -0,0 +1,29 @@
// additional functions
// does not contain:x = 5;
// does not contain:y = 10;
// add at runtime: global.a = {}; global.b = {};
if (global.__abstract) {
global.a = __abstract({}, "global.a");
global.b = __abstract({}, "global.b");
}
if (global.__makeSimple) {
__makeSimple(global.a);
__makeSimple(global.b);
}
var z = 42;
function additional1() {
global.a.foo = z + "foo";
var x = 5;
}
function additional2() {
global.b.bar = z + "bar";
var y = 10;
}
inspect = function() {
additional2();
additional1();
return JSON.stringify(global.a) + JSON.stringify(global.b);
}

View File

@ -0,0 +1,26 @@
// additional functions
// does not contain:x = 5;
// does not contain:y = 10;
function additional1() {
var z = { foo: 5 };
global.x = function nested1() { return z };
var x = 5;
}
function additional2() {
global.y = function nested2() { return 6; };
var y = 10;
}
inspect = function inspect() {
additional1();
additional2();
let x_fun = global.x;
let x = global.x();
let y = global.y;
additional1();
additional2();
return '' + x.foo + y() + (x_fun === global.x) + (global.x() === x) + global.x().foo + global.y() + (global.y === y);
}

View File

@ -0,0 +1,25 @@
// additional functions
// does not contain:x = 5;
// does not contain:y = 10;
function additional1() {
var z = { foo: 5 };
global.x = z;
var x = 5;
}
function additional2() {
var z = { bar: 6 };
global.y = z;
var y = 10;
}
inspect = function() {
additional1();
additional2();
let x = global.x;
let y = global.y;
additional1();
return '' + x.foo + y.bar + (global.x === x) + global.x.foo;
}

View File

@ -0,0 +1,31 @@
// additional functions
// does not contain:x = 5;
// does not contain:y = 10;
global.foo = { x: 100, y: 200 };
var z = 42;
function additional1() {
let local = {};
local.x = global.foo.x;
local.foo = z + local.x;
delete local.x;
var x = 5;
global.foo.x = local;
}
function additional2() {
let local = {};
local.y = global.foo.y;
local.bar = z + local.y;
delete local.y;
var y = 10;
global.foo.y = local;
}
inspect = function() {
additional2();
additional1();
let foo = global.foo;
return "" + JSON.stringify(foo.x) + JSON.stringify(foo.y);
}

View File

@ -0,0 +1,27 @@
// additional functions
// does not contain:x = 5;
// does not contain:y = 10;
let toCapture1 = {};
function additional1() {
toCapture1 = 5;
var x = 5;
x = 10;
global.x = x;
}
function additional2() {
var y = 10;
y = 5;
global.y = y;
}
inspect = function inspect() {
let z = toCapture1;
additional1();
let z2 = toCapture1;
additional2();
return "PASS";
//return '' + JSON.stringify(z) + JSON.stringify(z2) + JSON.stringify(toCapture1);
}

View File

@ -0,0 +1,37 @@
// additional functions
// does not contain:x = 5;
// does not contain:y = 10;
let toCapture1 = {};
let toCapture2 = 5;
let toCapture3 = {};
Object.defineProperty(global, "foo", { configurable: true, enumerable: false, value: 42 });
Object.defineProperty(global, "bar", { configurable: true, enumerable: false, get: function () { return 43; } });
function additional1() {
toCapture1 = 5;
toCapture2 = undefined;
global.foo += 1;
foo += 1;
var x = 5;
x = 10;
global.x = x;
}
function additional2() {
var y = 10;
y = 5;
toCapture3 = y;
global.y = y;
}
inspect = function inspect() {
let z = toCapture1;
let x = toCapture2;
let y = toCapture3;
additional1();
let z2 = toCapture1;
additional2();
return 'PASS';
//return '' + z + z2 + x + y + toCapture1 + toCapture2 + toCapture3 + foo + bar + (foo === bar);
}

View File

@ -0,0 +1,29 @@
// additional functions
// does not contain:var y = 5;
// does not contain:var y = 10;
function additional1() {
var x2 = { foo: 5 };
foo = function() { return x2; }
var y = 5;
}
function produceObject() {
return { bar: 5 };
}
function additional2() {
let x1 = produceObject();
bar = function() { return x1; }
}
inspect = function() {
additional1();
additional2();
let bar1 = bar;
let foo1 = foo;
additional1();
additional2();
return ' ' + JSON.stringify(bar()) + foo().foo + bar1() + foo1().foo + (bar1 === bar) + (bar1() === bar()) + (foo1() === foo());
}

View File

@ -0,0 +1,23 @@
// additional functions
// does not contain:x = 5;
// does not contain:y = 10;
global.a = "";
global.b = "";
var z = 42;
function additional1() {
global.a = z + "foo";
var x = 5;
}
function additional2() {
global.b = z + "bar";
var y = 10;
}
inspect = function() {
additional2();
additional1();
return global.a + global.b;
}

View File

@ -0,0 +1,23 @@
// additional functions
// does not contain:x = 5;
// does not contain:y = 10;
global.a = "";
global.b = "";
var z = global.__abstract ? __abstract("number", "(42)") : 42;
function additional1() {
global.a = z + "foo";
var x = 5;
}
function additional2() {
global.b = z + "bar";
var y = 10;
}
inspect = function() {
additional2();
additional1();
return global.a + global.b;
}

View File

@ -0,0 +1,21 @@
// additional functions
// does not contain:x = 5;
// does not contain:y = 10;
global.c = {};
function additional1() {
global.c.foo = 5;
var x = 5;
}
function additional2() {
global.c.bar = 2;
var y = 10;
}
inspect = function() {
additional2();
additional1();
return global.b + global.c.foo + global.c.bar;
}

View File

@ -0,0 +1,22 @@
// additional functions
// does not contain:x = 5;
// does not contain:y = 10;
global.a = "";
global.b = "";
var z = 42;
function additional1() {
var x = 5;
}
function additional2() {
global.b = z + "bar";
var y = 10;
}
inspect = function() {
additional2();
additional1();
return global.a + global.b;
}

View File

@ -0,0 +1,25 @@
// additional functions
// does not contain:x = 5;
// does not contain:y = 10;
global.foo = { x: 100, y: 200};
var z = 42;
function additional1() {
global.foo.foo = z + global.foo.x;
delete global.foo.x;
var x = 5;
}
function additional2() {
global.foo.bar = z + global.foo.y;
delete global.foo.y
var y = 10;
}
inspect = function() {
additional2();
additional1();
let foo = global.foo;
return "" + foo.x + foo.y + foo.foo + foo.bar;
}

View File

@ -0,0 +1,23 @@
// additional functions
// does not contain:x = 5;
// does not contain:y = 10;
global.a = {};
global.b = {};
var z = 42;
function additional1() {
global.a.foo = z + "foo";
var x = 5;
}
function additional2() {
global.b.bar = z + "bar";
var y = 10;
}
inspect = function() {
additional2();
additional1();
return JSON.stringify(global.a) + JSON.stringify(global.b);
}

View File

@ -0,0 +1,135 @@
// additional functions
// does not contain:var y = 5;
// does not contain:var y = 10;
var modules = Object.create(null);
__d = define;
function require(moduleId) {
var moduleIdReallyIsNumber = moduleId;
var module = modules[moduleIdReallyIsNumber];
return module && module.isInitialized ? module.exports : guardedLoadModule(moduleIdReallyIsNumber, module);
}
function define(factory, moduleId, dependencyMap) {
if (moduleId in modules) {
return;
}
modules[moduleId] = {
dependencyMap: dependencyMap,
exports: undefined,
factory: factory,
hasError: false,
isInitialized: false
};
var _verboseName = arguments[3];
if (_verboseName) {
modules[moduleId].verboseName = _verboseName;
verboseNamesToModuleIds[_verboseName] = moduleId;
}
}
var inGuard = false;
function guardedLoadModule(moduleId, module) {
if (!inGuard && global.ErrorUtils) {
inGuard = true;
var returnValue = void 0;
try {
returnValue = loadModuleImplementation(moduleId, module);
} catch (e) {
global.ErrorUtils.reportFatalError(e);
}
inGuard = false;
return returnValue;
} else {
return loadModuleImplementation(moduleId, module);
}
}
function loadModuleImplementation(moduleId, module) {
var nativeRequire = global.nativeRequire;
if (!module && nativeRequire) {
nativeRequire(moduleId);
module = modules[moduleId];
}
if (!module) {
throw unknownModuleError(moduleId);
}
if (module.hasError) {
throw moduleThrewError(moduleId);
}
module.isInitialized = true;
var exports = module.exports = {};
var _module = module,
factory = _module.factory,
dependencyMap = _module.dependencyMap;
try {
var _moduleObject = { exports: exports };
factory(global, require, _moduleObject, exports, dependencyMap);
module.factory = undefined;
return module.exports = _moduleObject.exports;
} catch (e) {
module.hasError = true;
module.isInitialized = false;
module.exports = undefined;
throw e;
}
}
function unknownModuleError(id) {
var message = 'Requiring unknown module "' + id + '".';
return Error(message);
}
function moduleThrewError(id) {
return Error('Requiring module "' + id + '", which threw an exception.');
}
// === End require code ===
define(function(global, require, module, exports) {
let exportval = { foo: " hello " };
module.exports = exportval;
}, 0, null);
define(function(global, require, module, exports) {
var x1 = require(0);
var y = require(2);
module.exports = {
bar: " goodbye",
foo2: x1.foo,
baz: y.baz
};
}, 1, null);
define(function(global, require, module, exports) {
module.exports = { baz: " foo " };
}, 2, null);
function additional1() {
var x2 = require(0);
global.foo = function() { return x2; }
var y = 5;
}
function additional2() {
//global.bar = function() { return require(0).baz + "bar"; }
global.bar = function() { return 5; }
}
inspect = function() {
additional1();
additional2();
let requireequal = require(0) === global.foo();
let uninitialized = modules[1].exports === undefined &&
require(1).bar === " goodbye";
return ' ' + requireequal + uninitialized + global.bar();
}

View File

@ -1,14 +1,18 @@
// additional functions
// does not contain:x = 5;
// does not contain:y = 10;
global.a = "";
global.b = "";
function additional1() {
global.a = "foo";
var x = 5;
}
function additional2() {
global.b = "bar";
var y = 10;
}
inspect = function() {

View File

@ -1,4 +1,4 @@
Object.defineProperty(global, "foo", { configurable: true, enumerable: false, value: 42 });
Object.defineProperty(global, "foo", { configurable: true, enumerable: false, value: 41 });
Object.defineProperty(global, "bar", { configurable: true, enumerable: false, get: function () { return 43; } });
inspect = function() { global.foo += 1; return foo === bar; }