Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 73 additions & 16 deletions src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ import {
lowerRequiresExportRuntime
} from "./bindings/js";

import * as binaryen from "./glue/binaryen";

/** Features enabled by default. */
export const defaultFeatures = Feature.MutableGlobals
| Feature.SignExtension
Expand Down Expand Up @@ -375,7 +377,9 @@ export const enum Constraints {
/** Indicates that static data is preferred. */
PreferStatic = 1 << 4,
/** Indicates that the value will become `this` of a property access or instance call. */
IsThis = 1 << 5
IsThis = 1 << 5,
/** Indicates that the expression is compiled for an immediate return position. */
WillReturn = 1 << 6
}

/** Runtime features to be activated by the compiler. */
Expand Down Expand Up @@ -2825,6 +2829,7 @@ export class Compiler extends DiagnosticEmitter {
if (valueExpression) {
let constraints = Constraints.ConvImplicit;
if (flow.sourceFunction.is(CommonFlags.ModuleExport)) constraints |= Constraints.MustWrap;
if (!flow.isInline && this.options.hasFeature(Feature.TailCalls)) constraints |= Constraints.WillReturn;

expr = this.compileExpression(valueExpression, returnType, constraints);
if (!flow.canOverflow(expr, returnType)) flow.set(FlowFlags.ReturnsWrapped);
Expand Down Expand Up @@ -2854,6 +2859,8 @@ export class Compiler extends DiagnosticEmitter {
: module.br(inlineReturnLabel);
}

if (expr && this.options.hasFeature(Feature.TailCalls) && this.isTailReturnCallExpression(expr)) return expr;

// Otherwise emit a normal return
return expr
? this.currentType == Type.void
Expand Down Expand Up @@ -6145,6 +6152,7 @@ export class Compiler extends DiagnosticEmitter {
Constraints.ConvImplicit | Constraints.IsThis
);
}
if (!this.canTailReturn(constraints, contextualType, functionInstance.signature.returnType)) constraints &= ~Constraints.WillReturn;
return this.compileCallDirect(
functionInstance,
expression.args,
Expand All @@ -6159,13 +6167,15 @@ export class Compiler extends DiagnosticEmitter {
let functionArg = this.compileExpression(expression.expression, Type.auto);
let signature = this.currentType.getSignature();
if (signature) {
if (!this.canTailReturn(constraints, contextualType, signature.returnType)) constraints &= ~Constraints.WillReturn;
return this.compileCallIndirect(
signature,
functionArg,
expression.args,
expression,
0,
contextualType == Type.void
contextualType == Type.void,
constraints
);
}
this.error(
Expand All @@ -6184,6 +6194,38 @@ export class Compiler extends DiagnosticEmitter {
return module.unreachable();
}

private canTailReturn(constraints: Constraints, contextType: Type, returnType: Type): bool {
if ((constraints & Constraints.WillReturn) == 0) return false;
// Tail calls are only valid as long as no result conversions are needed.
// `compileExpression` skips conversion for:
// 1. `currentType == nonnull<contextType>`, and
// 2. nullable reference identity (`T | null` -> `T | null`) where conversion is a no-op.
let sameWithoutNullable = returnType.equals(contextType.nonNullableType);
let sameNullableRef = returnType.isReference && returnType.equals(contextType);
if (!sameWithoutNullable && !sameNullableRef) return false;
if ((constraints & Constraints.MustWrap) == 0) return true;
switch (returnType.kind) {
case TypeKind.I8:
case TypeKind.I16:
case TypeKind.U8:
case TypeKind.U16:
return false;
default:
return true;
}
}

private isTailReturnCallExpression(expr: ExpressionRef): bool {
switch (getExpressionId(expr)) {
case ExpressionId.Call:
return binaryen._BinaryenCallIsReturn(expr);
case ExpressionId.CallIndirect:
return binaryen._BinaryenCallIndirectIsReturn(expr);
default:
return false;
}
}

/** Compiles the given arguments like a call expression according to the specified context. */
private compileCallExpressionLike(
/** Called expression. */
Expand Down Expand Up @@ -6439,7 +6481,7 @@ export class Compiler extends DiagnosticEmitter {
operands[index] = paramExpr;
}
assert(index == numArgumentsInclThis);
return this.makeCallDirect(instance, operands, reportNode, (constraints & Constraints.WillDrop) != 0);
return this.makeCallDirect(instance, operands, reportNode, (constraints & Constraints.WillDrop) != 0, (constraints & Constraints.WillReturn) != 0);
}

makeCallInline(
Expand Down Expand Up @@ -6883,7 +6925,8 @@ export class Compiler extends DiagnosticEmitter {
instance: Function,
operands: ExpressionRef[] | null,
reportNode: Node,
immediatelyDropped: bool = false
immediatelyDropped: bool = false,
isReturn: bool = false
): ExpressionRef {
if (instance.hasDecorator(DecoratorFlags.Inline)) {
if (!instance.is(CommonFlags.Overridden)) {
Expand Down Expand Up @@ -6982,8 +7025,10 @@ export class Compiler extends DiagnosticEmitter {
lastOperand
], lastOperandType.toRef());
this.operandsTostack(instance.signature, operands);
let expr = module.call(instance.internalName, operands, returnTypeRef);
if (returnType != Type.void && immediatelyDropped) {
let expr = isReturn
? module.return_call(instance.internalName, operands, returnTypeRef)
: module.call(instance.internalName, operands, returnTypeRef);
if (!isReturn && returnType != Type.void && immediatelyDropped) {
expr = module.drop(expr);
this.currentType = Type.void;
} else {
Expand All @@ -6999,7 +7044,9 @@ export class Compiler extends DiagnosticEmitter {
}

if (operands) this.operandsTostack(instance.signature, operands);
let expr = module.call(instance.internalName, operands, returnType.toRef());
let expr = isReturn
? module.return_call(instance.internalName, operands, returnType.toRef())
: module.call(instance.internalName, operands, returnType.toRef());
this.currentType = returnType;
return expr;
}
Expand All @@ -7011,7 +7058,8 @@ export class Compiler extends DiagnosticEmitter {
argumentExpressions: Expression[],
reportNode: Node,
thisArg: ExpressionRef = 0,
immediatelyDropped: bool = false
immediatelyDropped: bool = false,
constraints: Constraints = Constraints.None
): ExpressionRef {
let numArguments = argumentExpressions.length;

Expand Down Expand Up @@ -7041,7 +7089,7 @@ export class Compiler extends DiagnosticEmitter {
);
}
assert(index == numArgumentsInclThis);
return this.makeCallIndirect(signature, functionArg, reportNode, operands, immediatelyDropped);
return this.makeCallIndirect(signature, functionArg, reportNode, operands, immediatelyDropped, (constraints & Constraints.WillReturn) != 0);
}

/** Creates an indirect call to a first-class function. */
Expand All @@ -7051,6 +7099,7 @@ export class Compiler extends DiagnosticEmitter {
reportNode: Node,
operands: ExpressionRef[] | null = null,
immediatelyDropped: bool = false,
isReturn: bool = false,
): ExpressionRef {
let module = this.module;
let numOperands = operands ? operands.length : 0;
Expand Down Expand Up @@ -7101,13 +7150,21 @@ export class Compiler extends DiagnosticEmitter {
], sizeTypeRef);
}
if (operands) this.operandsTostack(signature, operands);
let expr = module.call_indirect(
null, // TODO: handle multiple tables
module.load(4, false, functionArg, TypeRef.I32), // ._index
operands,
signature.paramRefs,
signature.resultRefs
);
let expr = isReturn
? module.return_call_indirect(
null,
module.load(4, false, functionArg, TypeRef.I32),
operands,
signature.paramRefs,
signature.resultRefs
)
: module.call_indirect(
null,
module.load(4, false, functionArg, TypeRef.I32),
operands,
signature.paramRefs,
signature.resultRefs
);
this.currentType = returnType;
return expr;
}
Expand Down
21 changes: 11 additions & 10 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1842,21 +1842,16 @@ export class Module {
index: ExpressionRef,
operands: ExpressionRef[] | null,
params: TypeRef,
results: TypeRef,
isReturn: bool = false
results: TypeRef
): ExpressionRef {
let cStr = this.allocStringCached(tableName != null
? tableName
: CommonNames.DefaultTable
);
let cArr = allocPtrArray(operands);
let ret = isReturn
? binaryen._BinaryenReturnCallIndirect(
this.ref, cStr, index, cArr, operands ? operands.length : 0, params, results
)
: binaryen._BinaryenCallIndirect(
this.ref, cStr, index, cArr, operands ? operands.length : 0, params, results
);
let ret = binaryen._BinaryenCallIndirect(
this.ref, cStr, index, cArr, operands ? operands.length : 0, params, results
);
binaryen._free(cArr);
return ret;
}
Expand All @@ -1868,7 +1863,13 @@ export class Module {
params: TypeRef,
results: TypeRef
): ExpressionRef {
return this.call_indirect(tableName, index, operands, params, results, true);
let cStr = this.allocStringCached(tableName != null ? tableName : CommonNames.DefaultTable);
let cArr = allocPtrArray(operands);
let ret = binaryen._BinaryenReturnCallIndirect(
this.ref, cStr, index, cArr, operands ? operands.length : 0, params, results
);
binaryen._free(cArr);
return ret;
}

unreachable(): ExpressionRef {
Expand Down
Loading