@@ -1307,7 +1307,13 @@ export class Compiler extends DiagnosticEmitter {
13071307 let index = 0 ;
13081308 let thisType = signature . thisType ;
13091309 if ( thisType ) {
1310- // No need to retain `this` as it can't be reassigned and thus can't become prematurely released
1310+ // In normal instance functions, `this` is effectively a constant
1311+ // retained elsewhere so does not need to be retained.
1312+ if ( instance . is ( CommonFlags . CONSTRUCTOR ) ) {
1313+ // Constructors, however, can allocate their own memory, and as such
1314+ // must refcount the allocation in case something else is `return`ed.
1315+ flow . setLocalFlag ( index , LocalFlags . RETAINED ) ;
1316+ }
13111317 ++ index ;
13121318 }
13131319 let parameterTypes = signature . parameterTypes ;
@@ -1343,7 +1349,7 @@ export class Compiler extends DiagnosticEmitter {
13431349 signature . nativeParams ,
13441350 signature . nativeResults ,
13451351 typesToNativeTypes ( instance . additionalLocals ) ,
1346- module . flatten ( stmts , instance . signature . returnType . toNativeType ( ) )
1352+ body
13471353 ) ;
13481354
13491355 // imported function
@@ -1392,6 +1398,9 @@ export class Compiler extends DiagnosticEmitter {
13921398 var bodyNode = assert ( instance . prototype . bodyNode ) ;
13931399 var returnType = instance . signature . returnType ;
13941400 var flow = this . currentFlow ;
1401+ var thisLocal = instance . is ( CommonFlags . INSTANCE )
1402+ ? assert ( flow . lookupLocal ( CommonNames . this_ ) )
1403+ : null ;
13951404
13961405 // compile statements
13971406 if ( bodyNode . kind == NodeKind . BLOCK ) {
@@ -1432,39 +1441,66 @@ export class Compiler extends DiagnosticEmitter {
14321441 }
14331442 }
14341443
1435- // make constructors return their instance pointer
1444+ // Make constructors return their instance pointer, and prepend a conditional
1445+ // allocation if any code path accesses `this`.
14361446 if ( instance . is ( CommonFlags . CONSTRUCTOR ) ) {
14371447 let nativeSizeType = this . options . nativeSizeType ;
14381448 assert ( instance . is ( CommonFlags . INSTANCE ) ) ;
1449+ thisLocal = assert ( thisLocal ) ;
14391450 let parent = assert ( instance . parent ) ;
14401451 assert ( parent . kind == ElementKind . CLASS ) ;
14411452 let classInstance = < Class > parent ;
14421453
1443- if ( ! flow . is ( FlowFlags . TERMINATES ) ) {
1444- let thisLocal = assert ( flow . lookupLocal ( CommonNames . this_ ) ) ;
1445-
1446- // if `this` wasn't accessed before, allocate if necessary and initialize `this`
1447- if ( ! flow . is ( FlowFlags . ALLOCATES ) ) {
1448- // {
1449- // if (!this) this = <ALLOC>
1450- // this.a = X
1451- // this.b = Y
1452- // }
1453- stmts . push (
1454- module . if (
1455- module . unary ( nativeSizeType == NativeType . I64 ? UnaryOp . EqzI64 : UnaryOp . EqzI32 ,
1456- module . local_get ( thisLocal . index , nativeSizeType )
1457- ) ,
1458- module . local_set ( thisLocal . index ,
1459- this . makeRetain (
1460- this . makeAllocation ( classInstance )
1461- ) ,
1454+ if ( flow . isAny ( FlowFlags . ACCESSES_THIS | FlowFlags . CONDITIONALLY_ACCESSES_THIS ) || ! flow . is ( FlowFlags . TERMINATES ) ) {
1455+ // Allocate `this` if not a super call, and initialize fields
1456+ let allocStmts = new Array < ExpressionRef > ( ) ;
1457+ allocStmts . push (
1458+ module . if (
1459+ module . unary ( nativeSizeType == NativeType . I64 ? UnaryOp . EqzI64 : UnaryOp . EqzI32 ,
1460+ module . local_get ( thisLocal . index , nativeSizeType )
1461+ ) ,
1462+ module . local_set ( thisLocal . index ,
1463+ this . makeRetain (
1464+ this . makeAllocation ( classInstance )
14621465 )
14631466 )
1467+ )
1468+ ) ;
1469+ this . makeFieldInitializationInConstructor ( classInstance , allocStmts ) ;
1470+ if ( flow . isInline ) {
1471+ let firstStmt = stmts [ 0 ] ; // `this` alias assignment
1472+ assert ( getExpressionId ( firstStmt ) == ExpressionId . LocalSet ) ;
1473+ assert ( getLocalSetIndex ( firstStmt ) == thisLocal . index ) ;
1474+ allocStmts . unshift ( firstStmt ) ;
1475+ stmts [ 0 ] = module . flatten ( allocStmts , NativeType . None ) ;
1476+ } else {
1477+ stmts . unshift (
1478+ module . flatten ( allocStmts , NativeType . None )
14641479 ) ;
1465- this . makeFieldInitializationInConstructor ( classInstance , stmts ) ;
14661480 }
1467- this . performAutoreleases ( flow , stmts ) ; // `this` is excluded anyway
1481+
1482+ // Just prepended allocation is dropped when returning non-'this'
1483+ if ( flow . is ( FlowFlags . MAY_RETURN_NONTHIS ) ) {
1484+ this . pedantic (
1485+ DiagnosticCode . Explicitly_returning_constructor_drops_this_allocation ,
1486+ instance . identifierNode . range
1487+ ) ;
1488+ }
1489+ }
1490+
1491+ // Returning something else than 'this' would break 'super()' calls
1492+ if ( flow . is ( FlowFlags . MAY_RETURN_NONTHIS ) && ! classInstance . hasDecorator ( DecoratorFlags . FINAL ) ) {
1493+ this . error (
1494+ DiagnosticCode . A_class_with_a_constructor_explicitly_returning_something_else_than_this_must_be_final ,
1495+ classInstance . identifierNode . range
1496+ ) ;
1497+ }
1498+
1499+ // Implicitly return `this` if the flow falls through
1500+ if ( ! flow . is ( FlowFlags . TERMINATES ) ) {
1501+ assert ( flow . isAnyLocalFlag ( thisLocal . index , LocalFlags . ANY_RETAINED ) ) ;
1502+ flow . unsetLocalFlag ( thisLocal . index , LocalFlags . ANY_RETAINED ) ; // undo
1503+ this . performAutoreleases ( flow , stmts ) ;
14681504 this . finishAutoreleases ( flow , stmts ) ;
14691505 stmts . push ( module . local_get ( thisLocal . index , this . options . nativeSizeType ) ) ;
14701506 flow . set ( FlowFlags . RETURNS | FlowFlags . RETURNS_NONNULL | FlowFlags . TERMINATES ) ;
@@ -2623,6 +2659,9 @@ export class Compiler extends DiagnosticEmitter {
26232659
26242660 // take special care of properly retaining the returned value
26252661 expr = this . compileReturnedExpression ( valueExpression , returnType , constraints ) ;
2662+ if ( flow . actualFunction . is ( CommonFlags . CONSTRUCTOR ) && valueExpression . kind != NodeKind . THIS ) {
2663+ flow . set ( FlowFlags . MAY_RETURN_NONTHIS ) ;
2664+ }
26262665 } else if ( returnType != Type . void ) {
26272666 this . error (
26282667 DiagnosticCode . Type_0_is_not_assignable_to_type_1 ,
@@ -6322,44 +6361,29 @@ export class Compiler extends DiagnosticEmitter {
63226361 let thisLocal = assert ( flow . lookupLocal ( CommonNames . this_ ) ) ;
63236362 let nativeSizeType = this . options . nativeSizeType ;
63246363
6325- // {
6326- // this = super(this || <ALLOC>, ...args)
6327- // this.a = X
6328- // this.b = Y
6329- // }
6330- let theCall = this . compileCallDirect (
6364+ let superCall = this . compileCallDirect (
63316365 this . ensureConstructor ( baseClassInstance , expression ) ,
63326366 expression . arguments ,
63336367 expression ,
6334- module . if (
6335- module . local_get ( thisLocal . index , nativeSizeType ) ,
6336- module . local_get ( thisLocal . index , nativeSizeType ) ,
6337- this . makeRetain (
6338- this . makeAllocation ( classInstance )
6339- )
6340- ) ,
6368+ module . local_get ( thisLocal . index , nativeSizeType ) ,
63416369 Constraints . WILL_RETAIN
63426370 ) ;
6343- assert ( baseClassInstance . type . isUnmanaged || this . skippedAutoreleases . has ( theCall ) ) ; // guaranteed
6344- let stmts : ExpressionRef [ ] = [
6345- module . local_set ( thisLocal . index , theCall )
6346- ] ;
6347- this . makeFieldInitializationInConstructor ( classInstance , stmts ) ;
6371+ assert ( baseClassInstance . type . isUnmanaged || this . skippedAutoreleases . has ( superCall ) ) ; // guaranteed
63486372
63496373 // check that super had been called before accessing `this`
63506374 if ( flow . isAny (
6351- FlowFlags . ALLOCATES |
6352- FlowFlags . CONDITIONALLY_ALLOCATES
6375+ FlowFlags . ACCESSES_THIS |
6376+ FlowFlags . CONDITIONALLY_ACCESSES_THIS
63536377 ) ) {
63546378 this . error (
63556379 DiagnosticCode . _super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class ,
63566380 expression . range
63576381 ) ;
63586382 return module . unreachable ( ) ;
63596383 }
6360- flow . set ( FlowFlags . ALLOCATES | FlowFlags . CALLS_SUPER ) ;
6384+ flow . set ( FlowFlags . ACCESSES_THIS | FlowFlags . CALLS_SUPER ) ;
63616385 this . currentType = Type . void ;
6362- return module . flatten ( stmts ) ;
6386+ return module . local_set ( thisLocal . index , superCall ) ;
63636387 }
63646388
63656389 // otherwise resolve normally
@@ -6774,7 +6798,13 @@ export class Compiler extends DiagnosticEmitter {
67746798 let classInstance = < Class > parent ;
67756799 let thisType = assert ( instance . signature . thisType ) ;
67766800 let thisLocal = flow . addScopedLocal ( CommonNames . this_ , thisType , usedLocals ) ;
6777- // No need to retain `this` as it can't be reassigned and thus can't become prematurely released
6801+ // In normal instance functions, `this` is effectively a constant
6802+ // retained elsewhere so does not need to be retained.
6803+ if ( instance . is ( CommonFlags . CONSTRUCTOR ) ) {
6804+ // Constructors, however, can allocate their own memory, and as such
6805+ // must refcount the allocation in case something else is `return`ed.
6806+ flow . setLocalFlag ( thisLocal . index , LocalFlags . RETAINED ) ;
6807+ }
67786808 body . unshift (
67796809 module . local_set ( thisLocal . index , thisArg )
67806810 ) ;
@@ -7986,41 +8016,10 @@ export class Compiler extends DiagnosticEmitter {
79868016 case NodeKind . THIS : {
79878017 if ( actualFunction . is ( CommonFlags . INSTANCE ) ) {
79888018 let thisLocal = assert ( flow . lookupLocal ( CommonNames . this_ ) ) ;
8019+ let thisType = assert ( actualFunction . signature . thisType ) ;
79898020 let parent = assert ( actualFunction . parent ) ;
79908021 assert ( parent . kind == ElementKind . CLASS ) ;
7991- let classInstance = < Class > parent ;
7992- let nativeSizeType = this . options . nativeSizeType ;
7993- if ( actualFunction . is ( CommonFlags . CONSTRUCTOR ) ) {
7994- if ( ! flow . is ( FlowFlags . ALLOCATES ) ) {
7995- flow . set ( FlowFlags . ALLOCATES ) ;
7996- // {
7997- // if (!this) this = <ALLOC>
7998- // this.a = X
7999- // this.b = Y
8000- // return this
8001- // }
8002- let stmts : ExpressionRef [ ] = [
8003- module . if (
8004- module . unary ( nativeSizeType == NativeType . I64 ? UnaryOp . EqzI64 : UnaryOp . EqzI32 ,
8005- module . local_get ( thisLocal . index , nativeSizeType )
8006- ) ,
8007- module . local_set ( thisLocal . index ,
8008- this . makeRetain (
8009- this . makeAllocation ( classInstance )
8010- )
8011- )
8012- )
8013- ] ;
8014- this . makeFieldInitializationInConstructor ( classInstance , stmts ) ;
8015- stmts . push (
8016- module . local_get ( thisLocal . index , nativeSizeType )
8017- ) ;
8018- this . currentType = thisLocal . type ;
8019- return module . flatten ( stmts , nativeSizeType ) ;
8020- }
8021- }
8022- // if not a constructor, `this` type can differ
8023- let thisType = assert ( actualFunction . signature . thisType ) ;
8022+ flow . set ( FlowFlags . ACCESSES_THIS ) ;
80248023 this . currentType = thisType ;
80258024 return module . local_get ( thisLocal . index , thisType . toNativeType ( ) ) ;
80268025 }
0 commit comments