Skip to content

Commit b9809cc

Browse files
committed
Merge remote-tracking branch 'origin/master' into release
2 parents a6caad2 + b37cf1a commit b9809cc

21 files changed

+5778
-198
lines changed

src/compiler.ts

Lines changed: 49 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -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) {

src/diagnosticMessages.generated.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export enum DiagnosticCode {
125125
The_0_operator_cannot_be_applied_to_type_1 = 2469,
126126
In_const_enum_declarations_member_initializer_must_be_constant_expression = 2474,
127127
Export_declaration_conflicts_with_exported_declaration_of_0 = 2484,
128+
_0_is_referenced_directly_or_indirectly_in_its_own_base_expression = 2506,
128129
Object_is_possibly_null = 2531,
129130
Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property = 2540,
130131
The_target_of_an_assignment_must_be_a_variable_or_a_property_access = 2541,
@@ -268,6 +269,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
268269
case 2469: return "The '{0}' operator cannot be applied to type '{1}'.";
269270
case 2474: return "In 'const' enum declarations member initializer must be constant expression.";
270271
case 2484: return "Export declaration conflicts with exported declaration of '{0}'.";
272+
case 2506: return "'{0}' is referenced directly or indirectly in its own base expression.";
271273
case 2531: return "Object is possibly 'null'.";
272274
case 2540: return "Cannot assign to '{0}' because it is a constant or a read-only property.";
273275
case 2541: return "The target of an assignment must be a variable or a property access.";

src/diagnosticMessages.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"The '{0}' operator cannot be applied to type '{1}'.": 2469,
121121
"In 'const' enum declarations member initializer must be constant expression.": 2474,
122122
"Export declaration conflicts with exported declaration of '{0}'.": 2484,
123+
"'{0}' is referenced directly or indirectly in its own base expression.": 2506,
123124
"Object is possibly 'null'.": 2531,
124125
"Cannot assign to '{0}' because it is a constant or a read-only property.": 2540,
125126
"The target of an assignment must be a variable or a property access.": 2541,

src/flow.ts

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -156,17 +156,15 @@ export enum LocalFlags {
156156
WRITTENTO = 1 << 5,
157157
/** Local is retained. */
158158
RETAINED = 1 << 6,
159-
/** Local is returned. */
160-
RETURNED = 1 << 7,
161159

162160
/** Local is conditionally read from. */
163-
CONDITIONALLY_READFROM = 1 << 8,
161+
CONDITIONALLY_READFROM = 1 << 7,
164162
/** Local is conditionally written to. */
165-
CONDITIONALLY_WRITTENTO = 1 << 9,
163+
CONDITIONALLY_WRITTENTO = 1 << 8,
166164
/** Local must be conditionally retained. */
167-
CONDITIONALLY_RETAINED = 1 << 10,
165+
CONDITIONALLY_RETAINED = 1 << 9,
168166
/** Local is conditionally returned. */
169-
CONDITIONALLY_RETURNED = 1 << 11,
167+
CONDITIONALLY_RETURNED = 1 << 10,
170168

171169
/** Any categorical flag. */
172170
ANY_CATEGORICAL = CONSTANT
@@ -175,8 +173,7 @@ export enum LocalFlags {
175173
| NONNULL
176174
| READFROM
177175
| WRITTENTO
178-
| RETAINED
179-
| RETURNED,
176+
| RETAINED,
180177

181178
/** Any conditional flag. */
182179
ANY_CONDITIONAL = RETAINED
@@ -191,11 +188,7 @@ export enum LocalFlags {
191188

192189
/** Any retained flag. */
193190
ANY_RETAINED = RETAINED
194-
| CONDITIONALLY_RETAINED,
195-
196-
/** Any returned flag. */
197-
ANY_RETURNED = RETURNED
198-
| CONDITIONALLY_RETURNED
191+
| CONDITIONALLY_RETAINED
199192
}
200193
export namespace LocalFlags {
201194
export function join(left: LocalFlags, right: LocalFlags): LocalFlags {
@@ -585,7 +578,6 @@ export class Flow {
585578
if (flags & LocalFlags.RETAINED) this.setLocalFlag(i, LocalFlags.CONDITIONALLY_RETAINED);
586579
if (flags & LocalFlags.READFROM) this.setLocalFlag(i, LocalFlags.CONDITIONALLY_READFROM);
587580
if (flags & LocalFlags.WRITTENTO) this.setLocalFlag(i, LocalFlags.CONDITIONALLY_WRITTENTO);
588-
if (flags & LocalFlags.RETURNED) this.setLocalFlag(i, LocalFlags.CONDITIONALLY_RETURNED);
589581
}
590582
}
591583

src/resolver.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2761,10 +2761,24 @@ export class Resolver extends DiagnosticEmitter {
27612761
assert(!(typeParameterNodes && typeParameterNodes.length));
27622762
}
27632763

2764+
// There shouldn't be an instance before resolving the base class
2765+
assert(!prototype.getResolvedInstance(instanceKey));
2766+
27642767
// Resolve base class if applicable
27652768
var basePrototype = prototype.basePrototype;
27662769
var baseClass: Class | null = null;
27672770
if (basePrototype) {
2771+
let current: ClassPrototype | null = basePrototype;
2772+
do {
2773+
if (current == prototype) {
2774+
this.error(
2775+
DiagnosticCode._0_is_referenced_directly_or_indirectly_in_its_own_base_expression,
2776+
prototype.identifierNode.range,
2777+
prototype.internalName
2778+
);
2779+
return null;
2780+
}
2781+
} while (current = current.basePrototype);
27682782
let extendsNode = assert(prototype.extendsNode); // must be present if it has a base prototype
27692783
baseClass = this.resolveClassInclTypeArguments(
27702784
basePrototype,
@@ -2778,11 +2792,17 @@ export class Resolver extends DiagnosticEmitter {
27782792
}
27792793

27802794
// Construct the instance and remember that it has been resolved already
2781-
var nameInclTypeParamters = prototype.name;
2782-
if (instanceKey.length) nameInclTypeParamters += "<" + instanceKey + ">";
2783-
instance = new Class(nameInclTypeParamters, prototype, typeArguments, baseClass);
2784-
instance.contextualTypeArguments = ctxTypes;
2785-
prototype.setResolvedInstance(instanceKey, instance);
2795+
var recursiveInstanceFromResolvingBase = prototype.getResolvedInstance(instanceKey);
2796+
if (recursiveInstanceFromResolvingBase) {
2797+
// Happens if the base class already triggers resolving this class
2798+
instance = recursiveInstanceFromResolvingBase;
2799+
} else {
2800+
let nameInclTypeParamters = prototype.name;
2801+
if (instanceKey.length) nameInclTypeParamters += "<" + instanceKey + ">";
2802+
instance = new Class(nameInclTypeParamters, prototype, typeArguments, baseClass);
2803+
prototype.setResolvedInstance(instanceKey, instance);
2804+
}
2805+
instance.contextualTypeArguments = ctxTypes; // unique (as specified by the caller)
27862806

27872807
// Inherit base class members and set up the initial memory offset for own fields
27882808
var memoryOffset: u32 = 0;

tests/compiler/assert-nonnull.untouched.wat

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -273,22 +273,15 @@
273273
)
274274
(func $assert-nonnull/testFn (; 14 ;) (param $0 i32) (result i32)
275275
(local $1 i32)
276-
(local $2 i32)
277276
i32.const 0
278277
global.set $~lib/argc
279278
local.get $0
280279
call_indirect (type $none_=>_i32)
281280
local.tee $1
282-
call $~lib/rt/stub/__retain
283-
local.set $2
284-
local.get $1
285-
call $~lib/rt/stub/__release
286-
local.get $2
287281
)
288282
(func $assert-nonnull/testFn2 (; 15 ;) (param $0 i32) (result i32)
289283
(local $1 i32)
290284
(local $2 i32)
291-
(local $3 i32)
292285
local.get $0
293286
local.tee $1
294287
if (result i32)
@@ -302,11 +295,6 @@
302295
local.get $2
303296
call_indirect (type $none_=>_i32)
304297
local.tee $1
305-
call $~lib/rt/stub/__retain
306-
local.set $3
307-
local.get $1
308-
call $~lib/rt/stub/__release
309-
local.get $3
310298
)
311299
(func $assert-nonnull/testRet (; 16 ;) (param $0 i32) (result i32)
312300
(local $1 i32)
@@ -340,10 +328,7 @@
340328
i32.load offset=4
341329
call_indirect (type $none_=>_i32)
342330
local.tee $1
343-
call $~lib/rt/stub/__retain
344331
local.set $2
345-
local.get $1
346-
call $~lib/rt/stub/__release
347332
local.get $0
348333
call $~lib/rt/stub/__release
349334
local.get $2

tests/compiler/extends-recursive.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"asc_flags": [
3+
"--runtime none"
4+
]
5+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
(module
2+
(type $i32_i32_=>_none (func (param i32 i32)))
3+
(type $i32_=>_i32 (func (param i32) (result i32)))
4+
(memory $0 0)
5+
(global $extends-recursive/Child i32 (i32.const 4))
6+
(export "memory" (memory $0))
7+
(export "Child" (global $extends-recursive/Child))
8+
(export "Child#get:child" (func $Child#get:child))
9+
(export "Child#set:child" (func $Child#set:child))
10+
(func $Child#get:child (; 0 ;) (param $0 i32) (result i32)
11+
local.get $0
12+
i32.load
13+
)
14+
(func $Child#set:child (; 1 ;) (param $0 i32) (param $1 i32)
15+
(local $2 i32)
16+
local.get $0
17+
local.get $1
18+
local.tee $0
19+
i32.load
20+
drop
21+
local.get $0
22+
i32.store
23+
)
24+
)

tests/compiler/extends-recursive.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class Parent {
2+
child: Child | null = null;
3+
}
4+
export class Child extends Parent { }
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
(module
2+
(type $i32_=>_i32 (func (param i32) (result i32)))
3+
(type $i32_=>_none (func (param i32)))
4+
(type $i32_i32_=>_none (func (param i32 i32)))
5+
(memory $0 0)
6+
(table $0 1 funcref)
7+
(global $extends-recursive/Child i32 (i32.const 4))
8+
(export "memory" (memory $0))
9+
(export "Child" (global $extends-recursive/Child))
10+
(export "Child#get:child" (func $Child#get:child))
11+
(export "Child#set:child" (func $Child#set:child))
12+
(func $~lib/rt/stub/__retain (; 0 ;) (param $0 i32) (result i32)
13+
local.get $0
14+
)
15+
(func $Child#get:child (; 1 ;) (param $0 i32) (result i32)
16+
local.get $0
17+
i32.load
18+
call $~lib/rt/stub/__retain
19+
)
20+
(func $~lib/rt/stub/__release (; 2 ;) (param $0 i32)
21+
nop
22+
)
23+
(func $Child#set:child (; 3 ;) (param $0 i32) (param $1 i32)
24+
local.get $0
25+
local.get $1
26+
local.tee $0
27+
local.get $0
28+
i32.load
29+
local.tee $1
30+
i32.ne
31+
if
32+
local.get $0
33+
call $~lib/rt/stub/__retain
34+
local.set $0
35+
local.get $1
36+
call $~lib/rt/stub/__release
37+
end
38+
local.get $0
39+
i32.store
40+
)
41+
)

0 commit comments

Comments
 (0)