@@ -1307,7 +1307,13 @@ export class Compiler extends DiagnosticEmitter {
1307
1307
let index = 0 ;
1308
1308
let thisType = signature . thisType ;
1309
1309
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
+ }
1311
1317
++ index ;
1312
1318
}
1313
1319
let parameterTypes = signature . parameterTypes ;
@@ -1343,7 +1349,7 @@ export class Compiler extends DiagnosticEmitter {
1343
1349
signature . nativeParams ,
1344
1350
signature . nativeResults ,
1345
1351
typesToNativeTypes ( instance . additionalLocals ) ,
1346
- module . flatten ( stmts , instance . signature . returnType . toNativeType ( ) )
1352
+ body
1347
1353
) ;
1348
1354
1349
1355
// imported function
@@ -1392,6 +1398,9 @@ export class Compiler extends DiagnosticEmitter {
1392
1398
var bodyNode = assert ( instance . prototype . bodyNode ) ;
1393
1399
var returnType = instance . signature . returnType ;
1394
1400
var flow = this . currentFlow ;
1401
+ var thisLocal = instance . is ( CommonFlags . INSTANCE )
1402
+ ? assert ( flow . lookupLocal ( CommonNames . this_ ) )
1403
+ : null ;
1395
1404
1396
1405
// compile statements
1397
1406
if ( bodyNode . kind == NodeKind . BLOCK ) {
@@ -1432,39 +1441,66 @@ export class Compiler extends DiagnosticEmitter {
1432
1441
}
1433
1442
}
1434
1443
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`.
1436
1446
if ( instance . is ( CommonFlags . CONSTRUCTOR ) ) {
1437
1447
let nativeSizeType = this . options . nativeSizeType ;
1438
1448
assert ( instance . is ( CommonFlags . INSTANCE ) ) ;
1449
+ thisLocal = assert ( thisLocal ) ;
1439
1450
let parent = assert ( instance . parent ) ;
1440
1451
assert ( parent . kind == ElementKind . CLASS ) ;
1441
1452
let classInstance = < Class > parent ;
1442
1453
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 )
1462
1465
)
1463
1466
)
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 )
1464
1479
) ;
1465
- this . makeFieldInitializationInConstructor ( classInstance , stmts ) ;
1466
1480
}
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 ) ;
1468
1504
this . finishAutoreleases ( flow , stmts ) ;
1469
1505
stmts . push ( module . local_get ( thisLocal . index , this . options . nativeSizeType ) ) ;
1470
1506
flow . set ( FlowFlags . RETURNS | FlowFlags . RETURNS_NONNULL | FlowFlags . TERMINATES ) ;
@@ -2623,6 +2659,9 @@ export class Compiler extends DiagnosticEmitter {
2623
2659
2624
2660
// take special care of properly retaining the returned value
2625
2661
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
+ }
2626
2665
} else if ( returnType != Type . void ) {
2627
2666
this . error (
2628
2667
DiagnosticCode . Type_0_is_not_assignable_to_type_1 ,
@@ -6322,44 +6361,29 @@ export class Compiler extends DiagnosticEmitter {
6322
6361
let thisLocal = assert ( flow . lookupLocal ( CommonNames . this_ ) ) ;
6323
6362
let nativeSizeType = this . options . nativeSizeType ;
6324
6363
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 (
6331
6365
this . ensureConstructor ( baseClassInstance , expression ) ,
6332
6366
expression . arguments ,
6333
6367
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 ) ,
6341
6369
Constraints . WILL_RETAIN
6342
6370
) ;
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
6348
6372
6349
6373
// check that super had been called before accessing `this`
6350
6374
if ( flow . isAny (
6351
- FlowFlags . ALLOCATES |
6352
- FlowFlags . CONDITIONALLY_ALLOCATES
6375
+ FlowFlags . ACCESSES_THIS |
6376
+ FlowFlags . CONDITIONALLY_ACCESSES_THIS
6353
6377
) ) {
6354
6378
this . error (
6355
6379
DiagnosticCode . _super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class ,
6356
6380
expression . range
6357
6381
) ;
6358
6382
return module . unreachable ( ) ;
6359
6383
}
6360
- flow . set ( FlowFlags . ALLOCATES | FlowFlags . CALLS_SUPER ) ;
6384
+ flow . set ( FlowFlags . ACCESSES_THIS | FlowFlags . CALLS_SUPER ) ;
6361
6385
this . currentType = Type . void ;
6362
- return module . flatten ( stmts ) ;
6386
+ return module . local_set ( thisLocal . index , superCall ) ;
6363
6387
}
6364
6388
6365
6389
// otherwise resolve normally
@@ -6774,7 +6798,13 @@ export class Compiler extends DiagnosticEmitter {
6774
6798
let classInstance = < Class > parent ;
6775
6799
let thisType = assert ( instance . signature . thisType ) ;
6776
6800
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
+ }
6778
6808
body . unshift (
6779
6809
module . local_set ( thisLocal . index , thisArg )
6780
6810
) ;
@@ -7986,41 +8016,10 @@ export class Compiler extends DiagnosticEmitter {
7986
8016
case NodeKind . THIS : {
7987
8017
if ( actualFunction . is ( CommonFlags . INSTANCE ) ) {
7988
8018
let thisLocal = assert ( flow . lookupLocal ( CommonNames . this_ ) ) ;
8019
+ let thisType = assert ( actualFunction . signature . thisType ) ;
7989
8020
let parent = assert ( actualFunction . parent ) ;
7990
8021
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 ) ;
8024
8023
this . currentType = thisType ;
8025
8024
return module . local_get ( thisLocal . index , thisType . toNativeType ( ) ) ;
8026
8025
}
0 commit comments