Skip to content

Commit 747ebde

Browse files
authored
Fix managed function expression return (#1027)
1 parent 104d2f5 commit 747ebde

12 files changed

+5663
-193
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/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

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/retain-return.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"asc_flags": [
3+
"--runtime half",
4+
"--explicitStart"
5+
]
6+
}

0 commit comments

Comments
 (0)