Rework inlining logic (#463)

This commit is contained in:
Daniel Wirtz
2019-02-06 23:42:43 +01:00
committed by GitHub
parent 01cade13f9
commit 831054dfd3
40 changed files with 14369 additions and 9544 deletions

View File

@ -154,7 +154,7 @@ export function compileCall(
}
let element = compiler.resolver.resolveExpression(
operands[0],
compiler.currentFunction,
compiler.currentFlow,
Type.void,
ReportMode.SWALLOW
);
@ -639,11 +639,11 @@ export function compileCall(
case TypeKind.I8:
case TypeKind.I16:
case TypeKind.I32: {
let currentFunction = compiler.currentFunction;
let flow = compiler.currentFlow;
// possibly overflows, e.g. abs<i8>(-128) == 128
let tempLocal1 = currentFunction.getTempLocal(Type.i32, false);
let tempLocalIndex2 = currentFunction.getAndFreeTempLocal(Type.i32, false).index;
let tempLocal1 = flow.getTempLocal(Type.i32, false);
let tempLocalIndex2 = flow.getAndFreeTempLocal(Type.i32, false).index;
let tempLocalIndex1 = tempLocal1.index;
// (x + (x >> 31)) ^ (x >> 31)
@ -661,16 +661,16 @@ export function compileCall(
module.createGetLocal(tempLocalIndex2, NativeType.I32)
);
currentFunction.freeTempLocal(tempLocal1);
flow.freeTempLocal(tempLocal1);
break;
}
case TypeKind.ISIZE: {
let options = compiler.options;
let currentFunction = compiler.currentFunction;
let flow = compiler.currentFlow;
let wasm64 = options.isWasm64;
let tempLocal1 = currentFunction.getTempLocal(options.usizeType, false);
let tempLocalIndex2 = currentFunction.getAndFreeTempLocal(options.usizeType, false).index;
let tempLocal1 = flow.getTempLocal(options.usizeType, false);
let tempLocalIndex2 = flow.getAndFreeTempLocal(options.usizeType, false).index;
let tempLocalIndex1 = tempLocal1.index;
ret = module.createBinary(wasm64 ? BinaryOp.XorI64 : BinaryOp.XorI32,
@ -687,14 +687,14 @@ export function compileCall(
module.createGetLocal(tempLocalIndex2, options.nativeSizeType)
);
currentFunction.freeTempLocal(tempLocal1);
flow.freeTempLocal(tempLocal1);
break;
}
case TypeKind.I64: {
let currentFunction = compiler.currentFunction;
let flow = compiler.currentFlow;
let tempLocal1 = currentFunction.getTempLocal(Type.i64, false);
let tempLocalIndex2 = currentFunction.getAndFreeTempLocal(Type.i64, false).index;
let tempLocal1 = flow.getTempLocal(Type.i64, false);
let tempLocalIndex2 = flow.getAndFreeTempLocal(Type.i64, false).index;
let tempLocalIndex1 = tempLocal1.index;
// (x + (x >> 63)) ^ (x >> 63)
@ -712,7 +712,7 @@ export function compileCall(
module.createGetLocal(tempLocalIndex2, NativeType.I64)
);
currentFunction.freeTempLocal(tempLocal1);
flow.freeTempLocal(tempLocal1);
break;
}
case TypeKind.USIZE: {
@ -792,16 +792,16 @@ export function compileCall(
case TypeKind.I8:
case TypeKind.I16:
case TypeKind.I32: {
let flow = compiler.currentFunction.flow;
let tempLocal0 = compiler.currentFunction.getTempLocal(
let flow = compiler.currentFlow;
let tempLocal0 = flow.getTempLocal(
compiler.currentType,
!flow.canOverflow(arg0, compiler.currentType)
);
let tempLocal1 = compiler.currentFunction.getAndFreeTempLocal(
let tempLocal1 = flow.getAndFreeTempLocal(
compiler.currentType,
!flow.canOverflow(arg1, compiler.currentType)
);
compiler.currentFunction.freeTempLocal(tempLocal0);
flow.freeTempLocal(tempLocal0);
ret = module.createSelect(
module.createTeeLocal(tempLocal0.index, arg0),
module.createTeeLocal(tempLocal1.index, arg1),
@ -816,16 +816,16 @@ export function compileCall(
case TypeKind.U16:
case TypeKind.U32:
case TypeKind.BOOL: {
let flow = compiler.currentFunction.flow;
let tempLocal0 = compiler.currentFunction.getTempLocal(
let flow = compiler.currentFlow;
let tempLocal0 = flow.getTempLocal(
compiler.currentType,
!flow.canOverflow(arg0, compiler.currentType)
);
let tempLocal1 = compiler.currentFunction.getAndFreeTempLocal(
let tempLocal1 = flow.getAndFreeTempLocal(
compiler.currentType,
!flow.canOverflow(arg1, compiler.currentType)
);
compiler.currentFunction.freeTempLocal(tempLocal0);
flow.freeTempLocal(tempLocal0);
ret = module.createSelect(
module.createTeeLocal(tempLocal0.index, arg0),
module.createTeeLocal(tempLocal1.index, arg1),
@ -837,9 +837,10 @@ export function compileCall(
break;
}
case TypeKind.I64: {
let tempLocal0 = compiler.currentFunction.getTempLocal(Type.i64, false);
let tempLocal1 = compiler.currentFunction.getAndFreeTempLocal(Type.i64, false);
compiler.currentFunction.freeTempLocal(tempLocal0);
let flow = compiler.currentFlow;
let tempLocal0 = flow.getTempLocal(Type.i64, false);
let tempLocal1 = flow.getAndFreeTempLocal(Type.i64, false);
flow.freeTempLocal(tempLocal0);
ret = module.createSelect(
module.createTeeLocal(tempLocal0.index, arg0),
module.createTeeLocal(tempLocal1.index, arg1),
@ -851,9 +852,10 @@ export function compileCall(
break;
}
case TypeKind.U64: {
let tempLocal0 = compiler.currentFunction.getTempLocal(Type.i64, false);
let tempLocal1 = compiler.currentFunction.getAndFreeTempLocal(Type.i64, false);
compiler.currentFunction.freeTempLocal(tempLocal0);
let flow = compiler.currentFlow;
let tempLocal0 = flow.getTempLocal(Type.i64, false);
let tempLocal1 = flow.getAndFreeTempLocal(Type.i64, false);
flow.freeTempLocal(tempLocal0);
ret = module.createSelect(
module.createTeeLocal(tempLocal0.index, arg0),
module.createTeeLocal(tempLocal1.index, arg1),
@ -865,9 +867,10 @@ export function compileCall(
break;
}
case TypeKind.ISIZE: {
let tempLocal0 = compiler.currentFunction.getTempLocal(compiler.options.usizeType, false);
let tempLocal1 = compiler.currentFunction.getAndFreeTempLocal(compiler.options.usizeType, false);
compiler.currentFunction.freeTempLocal(tempLocal0);
let flow = compiler.currentFlow;
let tempLocal0 = flow.getTempLocal(compiler.options.usizeType, false);
let tempLocal1 = flow.getAndFreeTempLocal(compiler.options.usizeType, false);
flow.freeTempLocal(tempLocal0);
ret = module.createSelect(
module.createTeeLocal(tempLocal0.index, arg0),
module.createTeeLocal(tempLocal1.index, arg1),
@ -890,9 +893,10 @@ export function compileCall(
ret = module.createUnreachable();
break;
}
let tempLocal0 = compiler.currentFunction.getTempLocal(compiler.options.usizeType, false);
let tempLocal1 = compiler.currentFunction.getAndFreeTempLocal(compiler.options.usizeType, false);
compiler.currentFunction.freeTempLocal(tempLocal0);
let flow = compiler.currentFlow;
let tempLocal0 = flow.getTempLocal(compiler.options.usizeType, false);
let tempLocal1 = flow.getAndFreeTempLocal(compiler.options.usizeType, false);
flow.freeTempLocal(tempLocal0);
ret = module.createSelect(
module.createTeeLocal(tempLocal0.index, arg0),
module.createTeeLocal(tempLocal1.index, arg1),
@ -960,16 +964,16 @@ export function compileCall(
case TypeKind.I8:
case TypeKind.I16:
case TypeKind.I32: {
let flow = compiler.currentFunction.flow;
let tempLocal0 = compiler.currentFunction.getTempLocal(
let flow = compiler.currentFlow;
let tempLocal0 = flow.getTempLocal(
compiler.currentType,
!flow.canOverflow(arg0, compiler.currentType)
);
let tempLocal1 = compiler.currentFunction.getAndFreeTempLocal(
let tempLocal1 = flow.getAndFreeTempLocal(
compiler.currentType,
!flow.canOverflow(arg1, compiler.currentType)
);
compiler.currentFunction.freeTempLocal(tempLocal0);
flow.freeTempLocal(tempLocal0);
ret = module.createSelect(
module.createTeeLocal(tempLocal0.index, arg0),
module.createTeeLocal(tempLocal1.index, arg1),
@ -984,16 +988,16 @@ export function compileCall(
case TypeKind.U16:
case TypeKind.U32:
case TypeKind.BOOL: {
let flow = compiler.currentFunction.flow;
let tempLocal0 = compiler.currentFunction.getTempLocal(
let flow = compiler.currentFlow;
let tempLocal0 = flow.getTempLocal(
compiler.currentType,
!flow.canOverflow(arg0, compiler.currentType)
);
let tempLocal1 = compiler.currentFunction.getAndFreeTempLocal(
let tempLocal1 = flow.getAndFreeTempLocal(
compiler.currentType,
!flow.canOverflow(arg1, compiler.currentType)
);
compiler.currentFunction.freeTempLocal(tempLocal0);
flow.freeTempLocal(tempLocal0);
ret = module.createSelect(
module.createTeeLocal(tempLocal0.index, arg0),
module.createTeeLocal(tempLocal1.index, arg1),
@ -1005,9 +1009,10 @@ export function compileCall(
break;
}
case TypeKind.I64: {
let tempLocal0 = compiler.currentFunction.getTempLocal(Type.i64, false);
let tempLocal1 = compiler.currentFunction.getAndFreeTempLocal(Type.i64, false);
compiler.currentFunction.freeTempLocal(tempLocal0);
let flow = compiler.currentFlow;
let tempLocal0 = flow.getTempLocal(Type.i64, false);
let tempLocal1 = flow.getAndFreeTempLocal(Type.i64, false);
flow.freeTempLocal(tempLocal0);
ret = module.createSelect(
module.createTeeLocal(tempLocal0.index, arg0),
module.createTeeLocal(tempLocal1.index, arg1),
@ -1019,9 +1024,10 @@ export function compileCall(
break;
}
case TypeKind.U64: {
let tempLocal0 = compiler.currentFunction.getTempLocal(Type.i64, false);
let tempLocal1 = compiler.currentFunction.getAndFreeTempLocal(Type.i64, false);
compiler.currentFunction.freeTempLocal(tempLocal0);
let flow = compiler.currentFlow;
let tempLocal0 = flow.getTempLocal(Type.i64, false);
let tempLocal1 = flow.getAndFreeTempLocal(Type.i64, false);
flow.freeTempLocal(tempLocal0);
ret = module.createSelect(
module.createTeeLocal(tempLocal0.index, arg0),
module.createTeeLocal(tempLocal1.index, arg1),
@ -1033,9 +1039,10 @@ export function compileCall(
break;
}
case TypeKind.ISIZE: {
let tempLocal0 = compiler.currentFunction.getTempLocal(compiler.options.usizeType, false);
let tempLocal1 = compiler.currentFunction.getAndFreeTempLocal(compiler.options.usizeType, false);
compiler.currentFunction.freeTempLocal(tempLocal0);
let flow = compiler.currentFlow;
let tempLocal0 = flow.getTempLocal(compiler.options.usizeType, false);
let tempLocal1 = flow.getAndFreeTempLocal(compiler.options.usizeType, false);
flow.freeTempLocal(tempLocal0);
ret = module.createSelect(
module.createTeeLocal(tempLocal0.index, arg0),
module.createTeeLocal(tempLocal1.index, arg1),
@ -1058,9 +1065,10 @@ export function compileCall(
ret = module.createUnreachable();
break;
}
let tempLocal0 = compiler.currentFunction.getTempLocal(compiler.options.usizeType, false);
let tempLocal1 = compiler.currentFunction.getAndFreeTempLocal(compiler.options.usizeType, false);
compiler.currentFunction.freeTempLocal(tempLocal0);
let flow = compiler.currentFlow;
let tempLocal0 = flow.getTempLocal(compiler.options.usizeType, false);
let tempLocal1 = flow.getAndFreeTempLocal(compiler.options.usizeType, false);
flow.freeTempLocal(tempLocal0);
ret = module.createSelect(
module.createTeeLocal(tempLocal0.index, arg0),
module.createTeeLocal(tempLocal1.index, arg1),
@ -2186,8 +2194,8 @@ export function compileCall(
case TypeKind.U8:
case TypeKind.U16:
case TypeKind.BOOL: {
let flow = compiler.currentFunction.flow;
let tempLocal = compiler.currentFunction.getAndFreeTempLocal(
let flow = compiler.currentFlow;
let tempLocal = flow.getAndFreeTempLocal(
compiler.currentType,
!flow.canOverflow(arg0, compiler.currentType)
);
@ -2201,7 +2209,7 @@ export function compileCall(
case TypeKind.I32:
case TypeKind.U32:
default: {
let tempLocal = compiler.currentFunction.getAndFreeTempLocal(Type.i32, false);
let tempLocal = compiler.currentFlow.getAndFreeTempLocal(Type.i32, false);
ret = module.createIf(
module.createTeeLocal(tempLocal.index, arg0),
module.createGetLocal(tempLocal.index, NativeType.I32),
@ -2211,7 +2219,7 @@ export function compileCall(
}
case TypeKind.I64:
case TypeKind.U64: {
let tempLocal = compiler.currentFunction.getAndFreeTempLocal(Type.i64, false);
let tempLocal = compiler.currentFlow.getAndFreeTempLocal(Type.i64, false);
ret = module.createIf(
module.createUnary(UnaryOp.EqzI64,
module.createTeeLocal(tempLocal.index, arg0)
@ -2223,7 +2231,7 @@ export function compileCall(
}
case TypeKind.ISIZE:
case TypeKind.USIZE: {
let tempLocal = compiler.currentFunction.getAndFreeTempLocal(compiler.options.usizeType, false);
let tempLocal = compiler.currentFlow.getAndFreeTempLocal(compiler.options.usizeType, false);
ret = module.createIf(
module.createUnary(
compiler.options.isWasm64
@ -2237,7 +2245,7 @@ export function compileCall(
break;
}
case TypeKind.F32: {
let tempLocal = compiler.currentFunction.getAndFreeTempLocal(Type.f32, false);
let tempLocal = compiler.currentFlow.getAndFreeTempLocal(Type.f32, false);
ret = module.createIf(
module.createBinary(BinaryOp.EqF32,
module.createTeeLocal(tempLocal.index, arg0),
@ -2249,7 +2257,7 @@ export function compileCall(
break;
}
case TypeKind.F64: {
let tempLocal = compiler.currentFunction.getAndFreeTempLocal(Type.f64, false);
let tempLocal = compiler.currentFlow.getAndFreeTempLocal(Type.f64, false);
ret = module.createIf(
module.createBinary(BinaryOp.EqF64,
module.createTeeLocal(tempLocal.index, arg0),
@ -2286,7 +2294,7 @@ export function compileCall(
);
return module.createUnreachable();
}
let flow = compiler.currentFunction.flow;
let flow = compiler.currentFlow;
flow.set(FlowFlags.UNCHECKED_CONTEXT);
ret = compiler.compileExpressionRetainType(operands[0], contextualType, WrapMode.NONE);
flow.unset(FlowFlags.UNCHECKED_CONTEXT);

File diff suppressed because it is too large Load Diff

View File

@ -584,7 +584,7 @@ export class Program extends DiagnosticEmitter {
let derivedPrototype = queuedExtends[i];
let derivedDeclaration = derivedPrototype.declaration;
let derivedType = assert(derivedDeclaration.extendsType);
let baseElement = resolver.resolveIdentifier(derivedType.name, null); // reports
let baseElement = resolver.resolveIdentifier(derivedType.name, null, null); // reports
if (!baseElement) continue;
if (baseElement.kind == ElementKind.CLASS_PROTOTYPE) {
let basePrototype = <ClassPrototype>baseElement;
@ -2473,8 +2473,6 @@ export class Function extends Element {
localsByIndex: Local[] = [];
/** List of additional non-parameter locals. */
additionalLocals: Type[] = [];
/** Current break context label. */
breakContext: string | null = null;
/** Contextual type arguments. */
contextualTypeArguments: Map<string,Type> | null;
/** Current control flow. */
@ -2490,8 +2488,6 @@ export class Function extends Element {
/** The outer scope, if a function expression. */
outerScope: Flow | null = null;
private nextBreakId: i32 = 0;
private breakStack: i32[] | null = null;
nextInlineId: i32 = 0;
/** Constructs a new concrete function. */
@ -2509,45 +2505,42 @@ export class Function extends Element {
this.flags = prototype.flags;
this.decoratorFlags = prototype.decoratorFlags;
this.contextualTypeArguments = contextualTypeArguments;
if (prototype.internalName != "NATIVE_CODE") { // e.g. generated constructor without a real prototype
if (!(prototype.is(CommonFlags.AMBIENT))) {
let localIndex = 0;
if (parent && parent.kind == ElementKind.CLASS) {
assert(this.is(CommonFlags.INSTANCE));
let local = new Local(
prototype.program,
"this",
localIndex++,
assert(signature.thisType)
);
this.localsByName.set("this", local);
this.localsByIndex[local.index] = local;
let inheritedTypeArguments = (<Class>parent).contextualTypeArguments;
if (inheritedTypeArguments) {
if (!this.contextualTypeArguments) this.contextualTypeArguments = new Map();
for (let [inheritedName, inheritedType] of inheritedTypeArguments) {
if (!this.contextualTypeArguments.has(inheritedName)) {
this.contextualTypeArguments.set(inheritedName, inheritedType);
}
if (!prototype.is(CommonFlags.AMBIENT)) {
let localIndex = 0;
if (parent && parent.kind == ElementKind.CLASS) {
let local = new Local(
prototype.program,
"this",
localIndex++,
assert(signature.thisType)
);
this.localsByName.set("this", local);
this.localsByIndex[local.index] = local;
let inheritedTypeArguments = (<Class>parent).contextualTypeArguments;
if (inheritedTypeArguments) {
if (!this.contextualTypeArguments) this.contextualTypeArguments = new Map();
for (let [inheritedName, inheritedType] of inheritedTypeArguments) {
if (!this.contextualTypeArguments.has(inheritedName)) {
this.contextualTypeArguments.set(inheritedName, inheritedType);
}
}
} else {
assert(!this.is(CommonFlags.INSTANCE)); // internal error
}
let parameterTypes = signature.parameterTypes;
for (let i = 0, k = parameterTypes.length; i < k; ++i) {
let parameterType = parameterTypes[i];
let parameterName = signature.getParameterName(i);
let local = new Local(
prototype.program,
parameterName,
localIndex++,
parameterType
// FIXME: declaration?
);
this.localsByName.set(parameterName, local);
this.localsByIndex[local.index] = local;
}
} else {
assert(!this.is(CommonFlags.INSTANCE)); // internal error
}
let parameterTypes = signature.parameterTypes;
for (let i = 0, k = parameterTypes.length; i < k; ++i) {
let parameterType = parameterTypes[i];
let parameterName = signature.getParameterName(i);
let local = new Local(
prototype.program,
parameterName,
localIndex++,
parameterType
// FIXME: declaration?
);
this.localsByName.set(parameterName, local);
this.localsByIndex[local.index] = local;
}
}
this.flow = Flow.create(this);
@ -2576,140 +2569,23 @@ export class Function extends Element {
return local;
}
private tempI32s: Local[] | null = null;
private tempI64s: Local[] | null = null;
private tempF32s: Local[] | null = null;
private tempF64s: Local[] | null = null;
// used by flows to keep track of temporary locals
tempI32s: Local[] | null = null;
tempI64s: Local[] | null = null;
tempF32s: Local[] | null = null;
tempF64s: Local[] | null = null;
/** Gets a free temporary local of the specified type. */
getTempLocal(type: Type, wrapped: bool = false): Local {
var temps: Local[] | null;
switch (type.toNativeType()) {
case NativeType.I32: {
temps = this.tempI32s;
break;
}
case NativeType.I64: {
temps = this.tempI64s;
break;
}
case NativeType.F32: {
temps = this.tempF32s;
break;
}
case NativeType.F64: {
temps = this.tempF64s;
break;
}
default: throw new Error("concrete type expected");
}
var local: Local;
if (temps && temps.length) {
local = temps.pop();
local.type = type;
local.flags = CommonFlags.NONE;
} else {
local = this.addLocal(type);
}
if (type.is(TypeFlags.SHORT | TypeFlags.INTEGER)) {
this.flow.setLocalWrapped(local.index, wrapped);
}
return local;
}
/** Frees the temporary local for reuse. */
freeTempLocal(local: Local): void {
if (local.is(CommonFlags.INLINED)) return;
assert(local.index >= 0);
var temps: Local[];
assert(local.type != null); // internal error
switch ((<Type>local.type).toNativeType()) {
case NativeType.I32: {
temps = this.tempI32s || (this.tempI32s = []);
break;
}
case NativeType.I64: {
temps = this.tempI64s || (this.tempI64s = []);
break;
}
case NativeType.F32: {
temps = this.tempF32s || (this.tempF32s = []);
break;
}
case NativeType.F64: {
temps = this.tempF64s || (this.tempF64s = []);
break;
}
default: throw new Error("concrete type expected");
}
assert(local.index >= 0);
temps.push(local);
}
/** Gets and immediately frees a temporary local of the specified type. */
getAndFreeTempLocal(type: Type, wrapped: bool): Local {
var temps: Local[];
switch (type.toNativeType()) {
case NativeType.I32: {
temps = this.tempI32s || (this.tempI32s = []);
break;
}
case NativeType.I64: {
temps = this.tempI64s || (this.tempI64s = []);
break;
}
case NativeType.F32: {
temps = this.tempF32s || (this.tempF32s = []);
break;
}
case NativeType.F64: {
temps = this.tempF64s || (this.tempF64s = []);
break;
}
default: throw new Error("concrete type expected");
}
var local: Local;
if (temps.length) {
local = temps[temps.length - 1];
local.type = type;
} else {
local = this.addLocal(type);
temps.push(local);
}
if (type.is(TypeFlags.SHORT | TypeFlags.INTEGER)) {
this.flow.setLocalWrapped(local.index, wrapped);
}
return local;
}
/** Enters a(nother) break context. */
enterBreakContext(): string {
var id = this.nextBreakId++;
if (!this.breakStack) this.breakStack = [ id ];
else this.breakStack.push(id);
return this.breakContext = id.toString(10);
}
/** Leaves the current break context. */
leaveBreakContext(): void {
assert(this.breakStack != null);
var length = (<i32[]>this.breakStack).length;
assert(length > 0);
(<i32[]>this.breakStack).pop();
if (length > 1) {
this.breakContext = (<i32[]>this.breakStack)[length - 2].toString(10);
} else {
this.breakContext = null;
this.breakStack = null;
}
}
// used by flows to keep track of break labels
nextBreakId: i32 = 0;
breakStack: i32[] | null = null;
breakLabel: string | null = null;
/** Finalizes the function once compiled, releasing no longer needed resources. */
finalize(module: Module, ref: FunctionRef): void {
this.ref = ref;
assert(!this.breakStack || !this.breakStack.length); // internal error
this.breakStack = null;
this.breakContext = null;
this.breakLabel = null;
this.tempI32s = this.tempI64s = this.tempF32s = this.tempF64s = null;
if (this.program.options.sourceMap) {
let debugLocations = this.debugLocations;
@ -3049,39 +2925,39 @@ export const enum FlowFlags {
// categorical
/** This branch always returns. */
/** This flow returns. */
RETURNS = 1 << 0,
/** This branch always returns a wrapped value. */
/** This flow returns a wrapped value. */
RETURNS_WRAPPED = 1 << 1,
/** This branch always throws. */
/** This flow throws. */
THROWS = 1 << 2,
/** This branch always breaks. */
/** This flow breaks. */
BREAKS = 1 << 3,
/** This branch always continues. */
/** This flow continues. */
CONTINUES = 1 << 4,
/** This branch always allocates. Constructors only. */
/** This flow allocates. Constructors only. */
ALLOCATES = 1 << 5,
/** This branch always calls super. Constructors only. */
/** This flow calls super. Constructors only. */
CALLS_SUPER = 1 << 6,
// conditional
/** This branch conditionally returns in a child branch. */
/** This flow conditionally returns in a child flow. */
CONDITIONALLY_RETURNS = 1 << 7,
/** This branch conditionally throws in a child branch. */
/** This flow conditionally throws in a child flow. */
CONDITIONALLY_THROWS = 1 << 8,
/** This branch conditionally breaks in a child branch. */
/** This flow conditionally breaks in a child flow. */
CONDITIONALLY_BREAKS = 1 << 9,
/** This branch conditionally continues in a child branch. */
/** This flow conditionally continues in a child flow. */
CONDITIONALLY_CONTINUES = 1 << 10,
/** This branch conditionally allocates in a child branch. Constructors only. */
/** This flow conditionally allocates in a child flow. Constructors only. */
CONDITIONALLY_ALLOCATES = 1 << 11,
// special
/** This branch is part of inlining a function. */
/** This is an inlining flow. */
INLINE_CONTEXT = 1 << 12,
/** This branch explicitly requests no bounds checking. */
/** This is a flow with explicitly disabled bounds checking. */
UNCHECKED_CONTEXT = 1 << 13,
// masks
@ -3117,13 +2993,11 @@ export class Flow {
/** Flow flags indicating specific conditions. */
flags: FlowFlags;
/** Function this flow belongs to. */
currentFunction: Function;
parentFunction: Function;
/** The label we break to when encountering a continue statement. */
continueLabel: string | null;
/** The label we break to when encountering a break statement. */
breakLabel: string | null;
/** The label we break to when encountering a return statement, when inlining. */
returnLabel: string | null;
/** The current return type. */
returnType: Type;
/** The current contextual type arguments. */
@ -3134,25 +3008,46 @@ export class Flow {
wrappedLocals: I64;
/** Local variable wrap states for locals with index >= 64. */
wrappedLocalsExt: I64[] | null;
/** Function being inlined, when inlining. */
inlineFunction: Function | null;
/** The label we break to when encountering a return statement, when inlining. */
inlineReturnLabel: string | null;
/** Creates the parent flow of the specified function. */
static create(currentFunction: Function): Flow {
var parentFlow = new Flow();
parentFlow.parent = null;
parentFlow.flags = FlowFlags.NONE;
parentFlow.currentFunction = currentFunction;
parentFlow.continueLabel = null;
parentFlow.breakLabel = null;
parentFlow.returnLabel = null;
parentFlow.returnType = currentFunction.signature.returnType;
parentFlow.contextualTypeArguments = currentFunction.contextualTypeArguments;
parentFlow.wrappedLocals = i64_new(0);
parentFlow.wrappedLocalsExt = null;
return parentFlow;
static create(parentFunction: Function): Flow {
var flow = new Flow();
flow.parent = null;
flow.flags = FlowFlags.NONE;
flow.parentFunction = parentFunction;
flow.continueLabel = null;
flow.breakLabel = null;
flow.returnType = parentFunction.signature.returnType;
flow.contextualTypeArguments = parentFunction.contextualTypeArguments;
flow.wrappedLocals = i64_new(0);
flow.wrappedLocalsExt = null;
flow.inlineFunction = null;
flow.inlineReturnLabel = null;
return flow;
}
/** Creates an inline flow within `currentFunction`. */
static createInline(parentFunction: Function, inlineFunction: Function): Flow {
var flow = Flow.create(parentFunction);
flow.set(FlowFlags.INLINE_CONTEXT);
flow.inlineFunction = inlineFunction;
flow.inlineReturnLabel = inlineFunction.internalName + "|inlined." + (inlineFunction.nextInlineId++).toString(10);
flow.returnType = inlineFunction.signature.returnType;
flow.contextualTypeArguments = inlineFunction.contextualTypeArguments;
return flow;
}
private constructor() { }
/** Gets the actual function being compiled, The inlined function when inlining, otherwise the parent function. */
get actualFunction(): Function {
return this.inlineFunction || this.parentFunction;
}
/** Tests if this flow has the specified flag or flags. */
is(flag: FlowFlags): bool { return (this.flags & flag) == flag; }
/** Tests if this flow has one of the specified flags. */
@ -3167,44 +3062,119 @@ export class Flow {
var branch = new Flow();
branch.parent = this;
branch.flags = this.flags;
branch.currentFunction = this.currentFunction;
branch.parentFunction = this.parentFunction;
branch.continueLabel = this.continueLabel;
branch.breakLabel = this.breakLabel;
branch.returnLabel = this.returnLabel;
branch.returnType = this.returnType;
branch.contextualTypeArguments = this.contextualTypeArguments;
branch.wrappedLocals = this.wrappedLocals;
branch.wrappedLocalsExt = this.wrappedLocalsExt ? this.wrappedLocalsExt.slice() : null;
branch.inlineFunction = this.inlineFunction;
branch.inlineReturnLabel = this.inlineReturnLabel;
return branch;
}
/** Frees this flow's scoped variables. */
free(): Flow {
var parent = assert(this.parent);
if (this.scopedLocals) { // free block-scoped locals
for (let scopedLocal of this.scopedLocals.values()) {
if (scopedLocal.is(CommonFlags.SCOPED)) { // otherwise an alias
this.currentFunction.freeTempLocal(scopedLocal);
}
}
this.scopedLocals = null;
/** Gets a free temporary local of the specified type. */
getTempLocal(type: Type, wrapped: bool = false): Local {
var parentFunction = this.parentFunction;
var temps: Local[] | null;
switch (type.toNativeType()) {
case NativeType.I32: { temps = parentFunction.tempI32s; break; }
case NativeType.I64: { temps = parentFunction.tempI64s; break; }
case NativeType.F32: { temps = parentFunction.tempF32s; break; }
case NativeType.F64: { temps = parentFunction.tempF64s; break; }
default: throw new Error("concrete type expected");
}
return parent;
var local: Local;
if (temps && temps.length) {
local = temps.pop();
local.type = type;
local.flags = CommonFlags.NONE;
} else {
local = parentFunction.addLocal(type);
}
if (type.is(TypeFlags.SHORT | TypeFlags.INTEGER)) this.setLocalWrapped(local.index, wrapped);
return local;
}
/** Frees the temporary local for reuse. */
freeTempLocal(local: Local): void {
if (local.is(CommonFlags.INLINED)) return;
assert(local.index >= 0);
var parentFunction = this.parentFunction;
var temps: Local[];
assert(local.type != null); // internal error
switch ((<Type>local.type).toNativeType()) {
case NativeType.I32: {
temps = parentFunction.tempI32s || (parentFunction.tempI32s = []);
break;
}
case NativeType.I64: {
temps = parentFunction.tempI64s || (parentFunction.tempI64s = []);
break;
}
case NativeType.F32: {
temps = parentFunction.tempF32s || (parentFunction.tempF32s = []);
break;
}
case NativeType.F64: {
temps = parentFunction.tempF64s || (parentFunction.tempF64s = []);
break;
}
default: throw new Error("concrete type expected");
}
assert(local.index >= 0);
temps.push(local);
}
/** Gets and immediately frees a temporary local of the specified type. */
getAndFreeTempLocal(type: Type, wrapped: bool): Local {
var parentFunction = this.parentFunction;
var temps: Local[];
switch (type.toNativeType()) {
case NativeType.I32: {
temps = parentFunction.tempI32s || (parentFunction.tempI32s = []);
break;
}
case NativeType.I64: {
temps = parentFunction.tempI64s || (parentFunction.tempI64s = []);
break;
}
case NativeType.F32: {
temps = parentFunction.tempF32s || (parentFunction.tempF32s = []);
break;
}
case NativeType.F64: {
temps = parentFunction.tempF64s || (parentFunction.tempF64s = []);
break;
}
default: throw new Error("concrete type expected");
}
var local: Local;
if (temps.length) {
local = temps[temps.length - 1];
local.type = type;
} else {
local = parentFunction.addLocal(type);
temps.push(local);
}
if (type.is(TypeFlags.SHORT | TypeFlags.INTEGER)) this.setLocalWrapped(local.index, wrapped);
return local;
}
/** Adds a new scoped local of the specified name. */
addScopedLocal(type: Type, name: string, wrapped: bool, declaration?: VariableDeclaration): Local {
var scopedLocal = this.currentFunction.getTempLocal(type, false);
addScopedLocal(name: string, type: Type, wrapped: bool, reportNode: Node | null = null): Local {
var scopedLocal = this.getTempLocal(type, false);
if (!this.scopedLocals) this.scopedLocals = new Map();
else {
let existingLocal = this.scopedLocals.get(name);
if (existingLocal) {
if (declaration) {
this.currentFunction.program.error(
if (reportNode) {
this.parentFunction.program.error(
DiagnosticCode.Duplicate_identifier_0,
declaration.name.range
reportNode.range
);
} else assert(false);
}
return existingLocal;
}
}
@ -3216,47 +3186,56 @@ export class Flow {
return scopedLocal;
}
/** Adds a new scoped alias for the specified local. */
addScopedLocalAlias(index: i32, type: Type, name: string): Local {
/** Adds a new scoped alias for the specified local. For example `super` aliased to the `this` local. */
addScopedAlias(name: string, type: Type, index: i32, reportNode: Node | null = null): Local {
if (!this.scopedLocals) this.scopedLocals = new Map();
else {
let existingLocal = this.scopedLocals.get(name);
if (existingLocal) {
let declaration = existingLocal.declaration;
if (declaration) {
this.currentFunction.program.error(
if (reportNode) {
this.parentFunction.program.error(
DiagnosticCode.Duplicate_identifier_0,
declaration.name.range
reportNode.range
);
} else assert(false);
}
return existingLocal;
}
}
assert(index < this.currentFunction.localsByIndex.length);
var scopedAlias = new Local( // not SCOPED as an indicator that it isn't automatically free'd
this.currentFunction.program,
assert(index < this.parentFunction.localsByIndex.length);
var scopedAlias = new Local(
this.parentFunction.program,
name,
index,
type,
null
);
// not flagged as SCOPED as it must not be free'd when the flow is finalized
this.scopedLocals.set(name, scopedAlias);
return scopedAlias;
}
/** Gets the local of the specified name in the current scope. */
getScopedLocal(name: string): Local | null {
var local: Local | null;
var current: Flow | null = this;
do {
if (current.scopedLocals && (local = current.scopedLocals.get(name))) {
return local;
/** Frees this flow's scoped variables and returns its parent flow. */
freeScopedLocals(): void {
if (this.scopedLocals) {
for (let scopedLocal of this.scopedLocals.values()) {
if (scopedLocal.is(CommonFlags.SCOPED)) { // otherwise an alias
this.freeTempLocal(scopedLocal);
}
}
} while (current = current.parent);
return this.currentFunction.localsByName.get(name);
this.scopedLocals = null;
}
}
/** Tests if the local with the specified index is considered wrapped. */
/** Looks up the local of the specified name in the current scope. */
lookupLocal(name: string): Local | null {
var local: Local | null;
var current: Flow | null = this;
do if (current.scopedLocals && (local = current.scopedLocals.get(name))) return local;
while (current = current.parent);
return this.parentFunction.localsByName.get(name);
}
/** Tests if the value of the local at the specified index is considered wrapped. */
isLocalWrapped(index: i32): bool {
var map: I64;
var ext: I64[] | null;
@ -3283,7 +3262,7 @@ export class Flow {
);
}
/** Sets if the local with the specified index is considered wrapped. */
/** Sets if the value of the local at the specified index is considered wrapped. */
setLocalWrapped(index: i32, wrapped: bool): void {
var map: I64;
var off: i32 = -1;
@ -3322,6 +3301,30 @@ export class Flow {
else this.wrappedLocals = map;
}
/** Pushes a new break label to the stack, for example when entering a loop that one can `break` from. */
pushBreakLabel(): string {
var parentFunction = this.parentFunction;
var id = parentFunction.nextBreakId++;
var stack = parentFunction.breakStack;
if (!stack) parentFunction.breakStack = [ id ];
else stack.push(id);
return parentFunction.breakLabel = id.toString(10);
}
/** Pops the most recent break label from the stack. */
popBreakLabel(): void {
var parentFunction = this.parentFunction;
var stack = assert(parentFunction.breakStack);
var length = assert(stack.length);
stack.pop();
if (length > 1) {
parentFunction.breakLabel = stack[length - 2].toString(10);
} else {
parentFunction.breakLabel = null;
parentFunction.breakStack = null;
}
}
/** Inherits flags and local wrap states from the specified flow (e.g. blocks). */
inherit(other: Flow): void {
this.flags |= other.flags & (FlowFlags.ANY_CATEGORICAL | FlowFlags.ANY_CONDITIONAL);
@ -3395,9 +3398,8 @@ export class Flow {
// overflows if the local isn't wrapped or the conversion does
case ExpressionId.GetLocal: {
let currentFunction = this.currentFunction;
let local = currentFunction.localsByIndex[getGetLocalIndex(expr)];
return !currentFunction.flow.isLocalWrapped(local.index)
let local = this.parentFunction.localsByIndex[getGetLocalIndex(expr)];
return !this.isLocalWrapped(local.index)
|| canConversionOverflow(local.type, type);
}
@ -3410,7 +3412,7 @@ export class Flow {
// overflows if the conversion does (globals are wrapped on set)
case ExpressionId.GetGlobal: {
// TODO: this is inefficient because it has to read a string
let global = assert(this.currentFunction.program.elementsLookup.get(assert(getGetGlobalName(expr))));
let global = assert(this.parentFunction.program.elementsLookup.get(assert(getGetGlobalName(expr))));
assert(global.kind == ElementKind.GLOBAL);
return canConversionOverflow(assert((<Global>global).type), type);
}
@ -3594,7 +3596,6 @@ export class Flow {
let last = getBlockChild(expr, size - 1);
return this.canOverflow(last, type);
}
// actually, brs with a value that'd be handled here is not emitted atm
break;
}
@ -3612,7 +3613,7 @@ export class Flow {
// overflows if the call does not return a wrapped value or the conversion does
case ExpressionId.Call: {
let program = this.currentFunction.program;
let program = this.parentFunction.program;
let instance = assert(program.instancesLookup.get(assert(getCallTarget(expr))));
assert(instance.kind == ElementKind.FUNCTION);
let returnType = (<Function>instance).signature.returnType;
@ -3625,15 +3626,6 @@ export class Flow {
}
return true;
}
/** Finalizes this flow. Must be the topmost parent flow of the function. */
finalize(): void {
assert(this.parent == null); // must be the topmost parent flow
this.continueLabel = null;
this.breakLabel = null;
this.returnLabel = null;
this.contextualTypeArguments = null;
}
}
/** Tests if a conversion from one type to another can technically overflow. */

View File

@ -25,7 +25,8 @@ import {
DecoratorFlags,
FieldPrototype,
Field,
Global
Global,
Flow
} from "./program";
import {
@ -355,22 +356,26 @@ export class Resolver extends DiagnosticEmitter {
/** Resolves an identifier to the element it refers to. */
resolveIdentifier(
identifier: IdentifierExpression,
flow: Flow | null,
context: Element | null,
reportMode: ReportMode = ReportMode.REPORT
): Element | null {
var name = identifier.text;
var element: Element | null;
if (flow) {
let local = flow.lookupLocal(name);
if (local) {
this.currentThisExpression = null;
this.currentElementExpression = null;
return local;
}
}
if (context) {
switch (context.kind) {
case ElementKind.FUNCTION: { // search locals, use prototype
element = (<Function>context).flow.getScopedLocal(name);
if (element) {
this.currentThisExpression = null;
this.currentElementExpression = null;
return element;
}
case ElementKind.FUNCTION: { // use prototype
context = (<Function>context).prototype.parent;
break;
}
@ -433,13 +438,13 @@ export class Resolver extends DiagnosticEmitter {
/** Resolves a property access to the element it refers to. */
resolvePropertyAccess(
propertyAccess: PropertyAccessExpression,
contextualFunction: Function,
flow: Flow,
contextualType: Type,
reportMode: ReportMode = ReportMode.REPORT
): Element | null {
// start by resolving the lhs target (expression before the last dot)
var targetExpression = propertyAccess.expression;
var target = this.resolveExpression(targetExpression, contextualFunction, contextualType, reportMode); // reports
var target = this.resolveExpression(targetExpression, flow, contextualType, reportMode); // reports
if (!target) return null;
// at this point we know exactly what the target is, so look up the element within
@ -565,12 +570,12 @@ export class Resolver extends DiagnosticEmitter {
resolveElementAccess(
elementAccess: ElementAccessExpression,
contextualFunction: Function,
flow: Flow,
contextualType: Type,
reportMode: ReportMode = ReportMode.REPORT
): Element | null {
var targetExpression = elementAccess.expression;
var target = this.resolveExpression(targetExpression, contextualFunction, contextualType, reportMode);
var target = this.resolveExpression(targetExpression, flow, contextualType, reportMode);
if (!target) return null;
switch (target.kind) {
case ElementKind.GLOBAL: if (!this.ensureResolvedLazyGlobal(<Global>target, reportMode)) return null;
@ -685,7 +690,7 @@ export class Resolver extends DiagnosticEmitter {
resolveExpression(
expression: Expression,
contextualFunction: Function,
flow: Flow,
contextualType: Type = Type.void,
reportMode: ReportMode = ReportMode.REPORT
): Element | null {
@ -697,14 +702,14 @@ export class Resolver extends DiagnosticEmitter {
if ((<AssertionExpression>expression).assertionKind == AssertionKind.NONNULL) {
return this.resolveExpression(
(<AssertionExpression>expression).expression,
contextualFunction,
flow,
contextualType,
reportMode
);
}
let type = this.resolveType(
assert((<AssertionExpression>expression).toType),
contextualFunction.flow.contextualTypeArguments,
flow.contextualTypeArguments,
reportMode
);
if (!type) return null;
@ -733,7 +738,7 @@ export class Resolver extends DiagnosticEmitter {
}
return this.resolveExpression(
operand,
contextualFunction,
flow,
contextualType,
reportMode
);
@ -743,7 +748,7 @@ export class Resolver extends DiagnosticEmitter {
case Token.MINUS_MINUS: {
return this.resolveExpression(
(<UnaryPrefixExpression>expression).operand,
contextualFunction,
flow,
contextualType,
reportMode
);
@ -754,7 +759,7 @@ export class Resolver extends DiagnosticEmitter {
case Token.TILDE: {
let resolvedOperand = this.resolveExpression(
(<UnaryPrefixExpression>expression).operand,
contextualFunction,
flow,
contextualType,
reportMode
);
@ -772,7 +777,7 @@ export class Resolver extends DiagnosticEmitter {
case Token.MINUS_MINUS: {
return this.resolveExpression(
(<UnaryPostfixExpression>expression).operand,
contextualFunction,
flow,
contextualType,
reportMode
);
@ -788,15 +793,15 @@ export class Resolver extends DiagnosticEmitter {
throw new Error("not implemented");
}
case NodeKind.THIS: { // -> Class / ClassPrototype
if (contextualFunction.flow.is(FlowFlags.INLINE_CONTEXT)) {
let explicitLocal = contextualFunction.flow.getScopedLocal("this");
if (flow.is(FlowFlags.INLINE_CONTEXT)) {
let explicitLocal = flow.lookupLocal("this");
if (explicitLocal) {
this.currentThisExpression = null;
this.currentElementExpression = null;
return explicitLocal;
}
}
let parent = contextualFunction.parent;
let parent = flow.parentFunction.parent;
if (parent) {
this.currentThisExpression = null;
this.currentElementExpression = null;
@ -811,15 +816,15 @@ export class Resolver extends DiagnosticEmitter {
return null;
}
case NodeKind.SUPER: { // -> Class
if (contextualFunction.flow.is(FlowFlags.INLINE_CONTEXT)) {
let explicitLocal = contextualFunction.flow.getScopedLocal("super");
if (flow.is(FlowFlags.INLINE_CONTEXT)) {
let explicitLocal = flow.lookupLocal("super");
if (explicitLocal) {
this.currentThisExpression = null;
this.currentElementExpression = null;
return explicitLocal;
}
}
let parent = contextualFunction.parent;
let parent = flow.actualFunction.parent;
if (parent && parent.kind == ElementKind.CLASS && (parent = (<Class>parent).base)) {
this.currentThisExpression = null;
this.currentElementExpression = null;
@ -834,7 +839,7 @@ export class Resolver extends DiagnosticEmitter {
return null;
}
case NodeKind.IDENTIFIER: {
return this.resolveIdentifier(<IdentifierExpression>expression, contextualFunction, reportMode);
return this.resolveIdentifier(<IdentifierExpression>expression, flow, flow.actualFunction, reportMode);
}
case NodeKind.LITERAL: {
switch ((<LiteralExpression>expression).literalKind) {
@ -871,7 +876,7 @@ export class Resolver extends DiagnosticEmitter {
case NodeKind.PROPERTYACCESS: {
return this.resolvePropertyAccess(
<PropertyAccessExpression>expression,
contextualFunction,
flow,
contextualType,
reportMode
);
@ -879,20 +884,20 @@ export class Resolver extends DiagnosticEmitter {
case NodeKind.ELEMENTACCESS: {
return this.resolveElementAccess(
<ElementAccessExpression>expression,
contextualFunction,
flow,
contextualType,
reportMode
);
}
case NodeKind.CALL: {
let targetExpression = (<CallExpression>expression).expression;
let target = this.resolveExpression(targetExpression, contextualFunction, contextualType, reportMode);
let target = this.resolveExpression(targetExpression, flow, contextualType, reportMode);
if (!target) return null;
if (target.kind == ElementKind.FUNCTION_PROTOTYPE) {
let instance = this.resolveFunctionInclTypeArguments(
<FunctionPrototype>target,
(<CallExpression>expression).typeArguments,
makeMap<string,Type>(contextualFunction.flow.contextualTypeArguments),
makeMap<string,Type>(flow.contextualTypeArguments),
expression,
reportMode
);