@@ -223,6 +223,8 @@ import {
223223 lowerRequiresExportRuntime
224224} from "./bindings/js" ;
225225
226+ import * as binaryen from "./glue/binaryen" ;
227+
226228/** Features enabled by default. */
227229export const defaultFeatures = Feature . MutableGlobals
228230 | Feature . SignExtension
@@ -375,7 +377,9 @@ export const enum Constraints {
375377 /** Indicates that static data is preferred. */
376378 PreferStatic = 1 << 4 ,
377379 /** Indicates that the value will become `this` of a property access or instance call. */
378- IsThis = 1 << 5
380+ IsThis = 1 << 5 ,
381+ /** Indicates that the expression is compiled for an immediate return position. */
382+ WillReturn = 1 << 6
379383}
380384
381385/** Runtime features to be activated by the compiler. */
@@ -2825,6 +2829,7 @@ export class Compiler extends DiagnosticEmitter {
28252829 if ( valueExpression ) {
28262830 let constraints = Constraints . ConvImplicit ;
28272831 if ( flow . sourceFunction . is ( CommonFlags . ModuleExport ) ) constraints |= Constraints . MustWrap ;
2832+ if ( ! flow . isInline && this . options . hasFeature ( Feature . TailCalls ) ) constraints |= Constraints . WillReturn ;
28282833
28292834 expr = this . compileExpression ( valueExpression , returnType , constraints ) ;
28302835 if ( ! flow . canOverflow ( expr , returnType ) ) flow . set ( FlowFlags . ReturnsWrapped ) ;
@@ -2854,6 +2859,13 @@ export class Compiler extends DiagnosticEmitter {
28542859 : module . br ( inlineReturnLabel ) ;
28552860 }
28562861
2862+ if (
2863+ expr &&
2864+ this . options . hasFeature ( Feature . TailCalls ) &&
2865+ getExpressionId ( expr ) == ExpressionId . Call &&
2866+ binaryen . _BinaryenCallIsReturn ( expr )
2867+ ) return expr ;
2868+
28572869 // Otherwise emit a normal return
28582870 return expr
28592871 ? this . currentType == Type . void
@@ -6145,6 +6157,8 @@ export class Compiler extends DiagnosticEmitter {
61456157 Constraints . ConvImplicit | Constraints . IsThis
61466158 ) ;
61476159 }
6160+ if ( ! this . canTailReturn ( constraints , contextualType , functionInstance . signature . returnType ) )
6161+ constraints &= ~ Constraints . WillReturn ;
61486162 return this . compileCallDirect (
61496163 functionInstance ,
61506164 expression . args ,
@@ -6184,6 +6198,27 @@ export class Compiler extends DiagnosticEmitter {
61846198 return module . unreachable ( ) ;
61856199 }
61866200
6201+ private canTailReturn ( constraints : Constraints , contextType : Type , returnType : Type ) : bool {
6202+ if ( ( constraints & Constraints . WillReturn ) == 0 ) return false ;
6203+ // Tail calls are only valid as long as no result conversions are needed.
6204+ // `compileExpression` skips conversion for:
6205+ // 1. `currentType == nonnull<contextType>`, and
6206+ // 2. nullable reference identity (`T | null` -> `T | null`) where conversion is a no-op.
6207+ let sameWithoutNullable = returnType . equals ( contextType . nonNullableType ) ;
6208+ let sameNullableRef = returnType . isReference && returnType . equals ( contextType ) ;
6209+ if ( ! sameWithoutNullable && ! sameNullableRef ) return false ;
6210+ if ( ( constraints & Constraints . MustWrap ) == 0 ) return true ;
6211+ switch ( returnType . kind ) {
6212+ case TypeKind . I8 :
6213+ case TypeKind . I16 :
6214+ case TypeKind . U8 :
6215+ case TypeKind . U16 :
6216+ return false ;
6217+ default :
6218+ return true ;
6219+ }
6220+ }
6221+
61876222 /** Compiles the given arguments like a call expression according to the specified context. */
61886223 private compileCallExpressionLike (
61896224 /** Called expression. */
@@ -6439,7 +6474,13 @@ export class Compiler extends DiagnosticEmitter {
64396474 operands [ index ] = paramExpr ;
64406475 }
64416476 assert ( index == numArgumentsInclThis ) ;
6442- return this . makeCallDirect ( instance , operands , reportNode , ( constraints & Constraints . WillDrop ) != 0 ) ;
6477+ return this . makeCallDirect (
6478+ instance ,
6479+ operands ,
6480+ reportNode ,
6481+ ( constraints & Constraints . WillDrop ) != 0 ,
6482+ ( constraints & Constraints . WillReturn ) != 0
6483+ ) ;
64436484 }
64446485
64456486 makeCallInline (
@@ -6883,7 +6924,8 @@ export class Compiler extends DiagnosticEmitter {
68836924 instance : Function ,
68846925 operands : ExpressionRef [ ] | null ,
68856926 reportNode : Node ,
6886- immediatelyDropped : bool = false
6927+ immediatelyDropped : bool = false ,
6928+ isReturn : bool = false
68876929 ) : ExpressionRef {
68886930 if ( instance . hasDecorator ( DecoratorFlags . Inline ) ) {
68896931 if ( ! instance . is ( CommonFlags . Overridden ) ) {
@@ -6982,8 +7024,10 @@ export class Compiler extends DiagnosticEmitter {
69827024 lastOperand
69837025 ] , lastOperandType . toRef ( ) ) ;
69847026 this . operandsTostack ( instance . signature , operands ) ;
6985- let expr = module . call ( instance . internalName , operands , returnTypeRef ) ;
6986- if ( returnType != Type . void && immediatelyDropped ) {
7027+ let expr = isReturn
7028+ ? module . return_call ( instance . internalName , operands , returnTypeRef )
7029+ : module . call ( instance . internalName , operands , returnTypeRef ) ;
7030+ if ( ! isReturn && returnType != Type . void && immediatelyDropped ) {
69877031 expr = module . drop ( expr ) ;
69887032 this . currentType = Type . void ;
69897033 } else {
@@ -6999,7 +7043,9 @@ export class Compiler extends DiagnosticEmitter {
69997043 }
70007044
70017045 if ( operands ) this . operandsTostack ( instance . signature , operands ) ;
7002- let expr = module . call ( instance . internalName , operands , returnType . toRef ( ) ) ;
7046+ let expr = isReturn
7047+ ? module . return_call ( instance . internalName , operands , returnType . toRef ( ) )
7048+ : module . call ( instance . internalName , operands , returnType . toRef ( ) ) ;
70037049 this . currentType = returnType ;
70047050 return expr ;
70057051 }
0 commit comments