@@ -1141,12 +1141,13 @@ export class Compiler extends DiagnosticEmitter {
11411141 // none of the following can be an arrow function
11421142 assert ( ! instance . isAny ( CommonFlags . CONSTRUCTOR | CommonFlags . GET | CommonFlags . SET ) ) ;
11431143
1144- let expr = this . compileExpression ( ( < ExpressionStatement > bodyNode ) . expression , returnType ,
1145- Constraints . CONV_IMPLICIT
1146- ) ;
1144+ // take special care of properly retaining the returned value
1145+ let expr = this . compileReturnedExpression ( ( < ExpressionStatement > bodyNode ) . expression , returnType , Constraints . CONV_IMPLICIT ) ;
1146+
11471147 if ( ! stmts ) stmts = [ expr ] ;
11481148 else stmts . push ( expr ) ;
1149- if ( ! flow . is ( FlowFlags . TERMINATES ) ) { // TODO: detect if returning an autorelease local?
1149+
1150+ if ( ! flow . is ( FlowFlags . TERMINATES ) ) {
11501151 let indexBefore = stmts . length ;
11511152 this . performAutoreleases ( flow , stmts ) ;
11521153 this . finishAutoreleases ( flow , stmts ) ;
@@ -2215,6 +2216,32 @@ export class Compiler extends DiagnosticEmitter {
22152216 // foo // is possibly null
22162217 }
22172218
2219+ /** Compiles an expression that is about to be returned, taking special care of retaining and setting flow states. */
2220+ compileReturnedExpression (
2221+ /** Expression to compile. */
2222+ expression : Expression ,
2223+ /** Return type of the function. */
2224+ returnType : Type ,
2225+ /** Constraints indicating contextual conditions. */
2226+ constraints : Constraints = Constraints . NONE
2227+ ) : ExpressionRef {
2228+ // pretend to retain the expression immediately so the autorelease, if any, is skipped
2229+ var expr = this . compileExpression ( expression , returnType , constraints | Constraints . WILL_RETAIN ) ;
2230+ var flow = this . currentFlow ;
2231+ if ( returnType . isManaged ) {
2232+ // check if that worked, and if it didn't, keep the reference alive
2233+ if ( ! this . skippedAutoreleases . has ( expr ) ) {
2234+ let index = this . tryUndoAutorelease ( expr , flow ) ;
2235+ if ( index == - 1 ) expr = this . makeRetain ( expr ) ;
2236+ this . skippedAutoreleases . add ( expr ) ;
2237+ }
2238+ }
2239+ // remember return states
2240+ if ( ! flow . canOverflow ( expr , returnType ) ) flow . set ( FlowFlags . RETURNS_WRAPPED ) ;
2241+ if ( flow . isNonnull ( expr , returnType ) ) flow . set ( FlowFlags . RETURNS_NONNULL ) ;
2242+ return expr ;
2243+ }
2244+
22182245 compileReturnStatement (
22192246 statement : ReturnStatement ,
22202247 isLastInBody : bool
@@ -2239,27 +2266,9 @@ export class Compiler extends DiagnosticEmitter {
22392266 }
22402267 let constraints = Constraints . CONV_IMPLICIT ;
22412268 if ( flow . actualFunction . is ( CommonFlags . MODULE_EXPORT ) ) constraints |= Constraints . MUST_WRAP ;
2242- expr = this . compileExpression ( valueExpression , returnType , constraints | Constraints . WILL_RETAIN ) ;
2243-
2244- // when returning a local, and it is already retained, skip the final set
2245- // of retaining it as the return value and releasing it as a variable
2246- if ( ! this . skippedAutoreleases . has ( expr ) ) {
2247- if ( returnType . isManaged ) {
2248- if ( getExpressionId ( expr ) == ExpressionId . LocalGet ) {
2249- let index = getLocalGetIndex ( expr ) ;
2250- if ( flow . isAnyLocalFlag ( index , LocalFlags . ANY_RETAINED ) ) {
2251- flow . unsetLocalFlag ( index , LocalFlags . ANY_RETAINED ) ;
2252- flow . setLocalFlag ( index , LocalFlags . RETURNED ) ;
2253- this . skippedAutoreleases . add ( expr ) ;
2254- }
2255- }
2256- }
2257- }
2258-
2259- // remember return states
2260- if ( ! flow . canOverflow ( expr , returnType ) ) flow . set ( FlowFlags . RETURNS_WRAPPED ) ;
2261- if ( flow . isNonnull ( expr , returnType ) ) flow . set ( FlowFlags . RETURNS_NONNULL ) ;
22622269
2270+ // take special care of properly retaining the returned value
2271+ expr = this . compileReturnedExpression ( valueExpression , returnType , constraints ) ;
22632272 } else if ( returnType != Type . void ) {
22642273 this . error (
22652274 DiagnosticCode . Type_0_is_not_assignable_to_type_1 ,
@@ -2272,9 +2281,6 @@ export class Compiler extends DiagnosticEmitter {
22722281 this . performAutoreleases ( flow , stmts ) ;
22732282 this . finishAutoreleases ( flow , stmts ) ;
22742283
2275- // Make sure that the return value is retained for the caller
2276- if ( returnType . isManaged && ! this . skippedAutoreleases . has ( expr ) ) expr = this . makeRetain ( expr ) ;
2277-
22782284 if ( returnType != Type . void && stmts . length ) {
22792285 let temp = flow . getTempLocal ( returnType ) ;
22802286 if ( flow . isNonnull ( expr , returnType ) ) flow . setLocalFlag ( temp . index , LocalFlags . NONNULL ) ;
@@ -6506,24 +6512,32 @@ export class Compiler extends DiagnosticEmitter {
65066512 /** Flow that would autorelease. */
65076513 flow : Flow
65086514 ) : i32 {
6509- // NOTE: Can't remove the local.tee completely because it's already compiled
6510- // and a child of something else. Preventing the final release however makes
6511- // it optimize away .
6515+ // The following assumes that the expression actually belongs to the flow and that
6516+ // top-level autoreleases are never undone. While that's true, it's not necessary
6517+ // to check presence in scopedLocals .
65126518 switch ( getExpressionId ( expr ) ) {
6513- case ExpressionId . LocalSet : { // local.tee(__retain(expr))
6519+ case ExpressionId . LocalGet : { // local.get(idx)
6520+ let index = getLocalGetIndex ( expr ) ;
6521+ if ( flow . isAnyLocalFlag ( index , LocalFlags . ANY_RETAINED ) ) {
6522+ flow . unsetLocalFlag ( index , LocalFlags . ANY_RETAINED ) ;
6523+ return index ;
6524+ }
6525+ break ;
6526+ }
6527+ case ExpressionId . LocalSet : { // local.tee(idx, expr)
65146528 if ( isLocalTee ( expr ) ) {
6529+ // NOTE: Can't remove the local.tee completely because it's already compiled
6530+ // and a child of something else. Preventing the final release however makes
6531+ // it optimize away.
65156532 let index = getLocalSetIndex ( expr ) ;
65166533 if ( flow . isAnyLocalFlag ( index , LocalFlags . ANY_RETAINED ) ) {
6517- // Assumes that the expression actually belongs to the flow and that
6518- // top-level autoreleases are never undone. While that's true, it's
6519- // not necessary to check presence in scopedLocals.
65206534 flow . unsetLocalFlag ( index , LocalFlags . ANY_RETAINED ) ;
65216535 return index ;
65226536 }
65236537 }
65246538 break ;
65256539 }
6526- case ExpressionId . Block : { // { ..., local.tee(__retain(expr) ) }
6540+ case ExpressionId . Block : { // { ..., local.get| tee(... ) }
65276541 if ( getBlockName ( expr ) === null ) { // must not be a break target
65286542 let count = getBlockChildCount ( expr ) ;
65296543 if ( count ) {
0 commit comments