Skip to content

Commit 5dc20c9

Browse files
authored
Fix type confusion in logical expressions (#993)
1 parent bd54909 commit 5dc20c9

File tree

3 files changed

+197
-128
lines changed

3 files changed

+197
-128
lines changed

src/compiler.ts

Lines changed: 131 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -5016,86 +5016,89 @@ export class Compiler extends DiagnosticEmitter {
50165016
rightExpr = this.performAutoreleasesWithValue(rightFlow, rightExpr, rightType);
50175017
rightFlow.freeScopedLocals();
50185018
this.currentFlow = flow;
5019-
this.currentType = Type.bool;
50205019
expr = module.if(
50215020
this.makeIsTrueish(leftExpr, leftType),
50225021
this.makeIsTrueish(rightExpr, rightType),
50235022
module.i32(0)
50245023
);
5024+
this.currentType = Type.bool;
50255025

5026-
// references must properly retain and release, with the same outcome independent of the branch taken
5027-
} else if (leftType.isManaged) {
5028-
let leftAutoreleaseSkipped = this.skippedAutoreleases.has(leftExpr);
5029-
let rightAutoreleaseSkipped = this.skippedAutoreleases.has(rightExpr);
5030-
let temp = flow.getTempLocal(leftType);
5031-
leftExpr = module.local_tee(temp.index, leftExpr);
5032-
5033-
// instead of retaining left and releasing it again in right when right
5034-
// is taken, we can also just retain left if right is not taken
5035-
let retainLeftInElse = false;
5036-
if (leftAutoreleaseSkipped != rightAutoreleaseSkipped) { // xor
5037-
if (!leftAutoreleaseSkipped) {
5038-
retainLeftInElse = true;
5039-
} else {
5040-
rightExpr = this.makeRetain(rightExpr);
5041-
rightAutoreleaseSkipped = true;
5042-
}
5043-
} else if (!(constraints & Constraints.WILL_RETAIN)) { // otherwise keep right alive a little longer
5044-
rightExpr = this.delayAutorelease(rightExpr, rightType, rightFlow, flow);
5045-
}
5026+
} else {
50465027

5047-
let rightStmts = new Array<ExpressionRef>();
5048-
if (leftAutoreleaseSkipped) { // left turned out to be true'ish and is dropped
5049-
rightStmts.unshift(
5050-
this.makeRelease(
5051-
module.local_get(temp.index, leftType.toNativeType())
5052-
)
5053-
);
5054-
}
5055-
rightExpr = this.performAutoreleasesWithValue(rightFlow, rightExpr, rightType, rightStmts);
5056-
rightFlow.freeScopedLocals();
5057-
this.currentFlow = flow;
5028+
// references must properly retain and release, with the same outcome independent of the branch taken
5029+
if (leftType.isManaged) {
5030+
let leftAutoreleaseSkipped = this.skippedAutoreleases.has(leftExpr);
5031+
let rightAutoreleaseSkipped = this.skippedAutoreleases.has(rightExpr);
5032+
let temp = flow.getTempLocal(leftType);
5033+
leftExpr = module.local_tee(temp.index, leftExpr);
5034+
5035+
// instead of retaining left and releasing it again in right when right
5036+
// is taken, we can also just retain left if right is not taken
5037+
let retainLeftInElse = false;
5038+
if (leftAutoreleaseSkipped != rightAutoreleaseSkipped) { // xor
5039+
if (!leftAutoreleaseSkipped) {
5040+
retainLeftInElse = true;
5041+
} else {
5042+
rightExpr = this.makeRetain(rightExpr);
5043+
rightAutoreleaseSkipped = true;
5044+
}
5045+
} else if (!(constraints & Constraints.WILL_RETAIN)) { // otherwise keep right alive a little longer
5046+
rightExpr = this.delayAutorelease(rightExpr, rightType, rightFlow, flow);
5047+
}
50585048

5059-
expr = module.if(
5060-
this.makeIsTrueish(leftExpr, leftType),
5061-
rightExpr,
5062-
retainLeftInElse
5063-
? this.makeRetain(
5049+
let rightStmts = new Array<ExpressionRef>();
5050+
if (leftAutoreleaseSkipped) { // left turned out to be true'ish and is dropped
5051+
rightStmts.unshift(
5052+
this.makeRelease(
50645053
module.local_get(temp.index, leftType.toNativeType())
50655054
)
5066-
: module.local_get(temp.index, leftType.toNativeType())
5067-
);
5068-
if (leftAutoreleaseSkipped || rightAutoreleaseSkipped) this.skippedAutoreleases.add(expr);
5069-
if (temp) flow.freeTempLocal(temp);
5070-
5071-
// basic values can use more aggressive optimizations
5072-
} else {
5073-
rightExpr = this.performAutoreleasesWithValue(rightFlow, rightExpr, rightType);
5074-
rightFlow.freeScopedLocals();
5075-
this.currentFlow = flow;
5055+
);
5056+
}
5057+
rightExpr = this.performAutoreleasesWithValue(rightFlow, rightExpr, rightType, rightStmts);
5058+
rightFlow.freeScopedLocals();
5059+
this.currentFlow = flow;
50765060

5077-
// simplify if cloning left without side effects is possible
5078-
if (expr = module.cloneExpression(leftExpr, true, 0)) {
50795061
expr = module.if(
5080-
this.makeIsTrueish(leftExpr, this.currentType),
5062+
this.makeIsTrueish(leftExpr, leftType),
50815063
rightExpr,
5082-
expr
5064+
retainLeftInElse
5065+
? this.makeRetain(
5066+
module.local_get(temp.index, leftType.toNativeType())
5067+
)
5068+
: module.local_get(temp.index, leftType.toNativeType())
50835069
);
5070+
if (leftAutoreleaseSkipped || rightAutoreleaseSkipped) this.skippedAutoreleases.add(expr);
5071+
if (temp) flow.freeTempLocal(temp);
50845072

5085-
// if not possible, tee left to a temp
5073+
// basic values can use more aggressive optimizations
50865074
} else {
5087-
let tempLocal = flow.getTempLocal(leftType);
5088-
if (!flow.canOverflow(leftExpr, leftType)) flow.setLocalFlag(tempLocal.index, LocalFlags.WRAPPED);
5089-
if (flow.isNonnull(leftExpr, leftType)) flow.setLocalFlag(tempLocal.index, LocalFlags.NONNULL);
5090-
expr = module.if(
5091-
this.makeIsTrueish(module.local_tee(tempLocal.index, leftExpr), leftType),
5092-
rightExpr,
5093-
module.local_get(tempLocal.index, leftType.toNativeType())
5094-
);
5095-
flow.freeTempLocal(tempLocal);
5075+
rightExpr = this.performAutoreleasesWithValue(rightFlow, rightExpr, rightType);
5076+
rightFlow.freeScopedLocals();
5077+
this.currentFlow = flow;
5078+
5079+
// simplify if cloning left without side effects is possible
5080+
if (expr = module.cloneExpression(leftExpr, true, 0)) {
5081+
expr = module.if(
5082+
this.makeIsTrueish(leftExpr, this.currentType),
5083+
rightExpr,
5084+
expr
5085+
);
5086+
5087+
// if not possible, tee left to a temp
5088+
} else {
5089+
let tempLocal = flow.getTempLocal(leftType);
5090+
if (!flow.canOverflow(leftExpr, leftType)) flow.setLocalFlag(tempLocal.index, LocalFlags.WRAPPED);
5091+
if (flow.isNonnull(leftExpr, leftType)) flow.setLocalFlag(tempLocal.index, LocalFlags.NONNULL);
5092+
expr = module.if(
5093+
this.makeIsTrueish(module.local_tee(tempLocal.index, leftExpr), leftType),
5094+
rightExpr,
5095+
module.local_get(tempLocal.index, leftType.toNativeType())
5096+
);
5097+
flow.freeTempLocal(tempLocal);
5098+
}
50965099
}
5100+
this.currentType = leftType;
50975101
}
5098-
this.currentType = leftType;
50995102
break;
51005103
}
51015104
case Token.BAR_BAR: { // left || right -> ((t = left) ? t : right)
@@ -5115,88 +5118,91 @@ export class Compiler extends DiagnosticEmitter {
51155118
rightExpr = this.performAutoreleasesWithValue(rightFlow, rightExpr, leftType);
51165119
rightFlow.freeScopedLocals();
51175120
this.currentFlow = flow;
5118-
this.currentType = Type.bool;
51195121
expr = module.if(
51205122
this.makeIsTrueish(leftExpr, leftType),
51215123
module.i32(1),
51225124
this.makeIsTrueish(rightExpr, rightType)
51235125
);
5126+
this.currentType = Type.bool;
51245127

5125-
// references must properly retain and release, with the same outcome independent of the branch taken
5126-
} else if (leftType.isManaged) {
5127-
let leftAutoreleaseSkipped = this.skippedAutoreleases.has(leftExpr);
5128-
let rightAutoreleaseSkipped = this.skippedAutoreleases.has(rightExpr);
5129-
let temp = flow.getTempLocal(leftType);
5130-
leftExpr = module.local_tee(temp.index, leftExpr);
5131-
5132-
// instead of retaining left and releasing it again in right when right
5133-
// is taken, we can also just retain left if right is not taken
5134-
let retainLeftInThen = false;
5135-
if (leftAutoreleaseSkipped != rightAutoreleaseSkipped) { // xor
5136-
if (!leftAutoreleaseSkipped) {
5137-
retainLeftInThen = true;
5138-
} else {
5139-
rightExpr = this.makeRetain(rightExpr);
5140-
rightAutoreleaseSkipped = true;
5141-
}
5142-
} else if (!(constraints & Constraints.WILL_RETAIN)) { // otherwise keep right alive a little longer
5143-
rightExpr = this.delayAutorelease(rightExpr, rightType, rightFlow, flow);
5144-
}
5128+
} else {
51455129

5146-
let rightStmts = new Array<ExpressionRef>();
5147-
if (leftAutoreleaseSkipped) { // left turned out to be false'ish and is dropped
5148-
// TODO: usually, false'ish means left is null, but this might not hold
5149-
// once implicit conversion with strings is performed and left is "", so:
5150-
rightStmts.unshift(
5151-
this.makeRelease(
5152-
module.local_get(temp.index, leftType.toNativeType())
5153-
)
5154-
);
5155-
}
5156-
rightExpr = this.performAutoreleasesWithValue(rightFlow, rightExpr, rightType, rightStmts);
5157-
rightFlow.freeScopedLocals();
5158-
this.currentFlow = flow;
5130+
// references must properly retain and release, with the same outcome independent of the branch taken
5131+
if (leftType.isManaged) {
5132+
let leftAutoreleaseSkipped = this.skippedAutoreleases.has(leftExpr);
5133+
let rightAutoreleaseSkipped = this.skippedAutoreleases.has(rightExpr);
5134+
let temp = flow.getTempLocal(leftType);
5135+
leftExpr = module.local_tee(temp.index, leftExpr);
5136+
5137+
// instead of retaining left and releasing it again in right when right
5138+
// is taken, we can also just retain left if right is not taken
5139+
let retainLeftInThen = false;
5140+
if (leftAutoreleaseSkipped != rightAutoreleaseSkipped) { // xor
5141+
if (!leftAutoreleaseSkipped) {
5142+
retainLeftInThen = true;
5143+
} else {
5144+
rightExpr = this.makeRetain(rightExpr);
5145+
rightAutoreleaseSkipped = true;
5146+
}
5147+
} else if (!(constraints & Constraints.WILL_RETAIN)) { // otherwise keep right alive a little longer
5148+
rightExpr = this.delayAutorelease(rightExpr, rightType, rightFlow, flow);
5149+
}
51595150

5160-
expr = module.if(
5161-
this.makeIsTrueish(leftExpr, leftType),
5162-
retainLeftInThen
5163-
? this.makeRetain(
5151+
let rightStmts = new Array<ExpressionRef>();
5152+
if (leftAutoreleaseSkipped) { // left turned out to be false'ish and is dropped
5153+
// TODO: usually, false'ish means left is null, but this might not hold
5154+
// once implicit conversion with strings is performed and left is "", so:
5155+
rightStmts.unshift(
5156+
this.makeRelease(
51645157
module.local_get(temp.index, leftType.toNativeType())
51655158
)
5166-
: module.local_get(temp.index, leftType.toNativeType()),
5167-
rightExpr
5168-
);
5169-
if (leftAutoreleaseSkipped || rightAutoreleaseSkipped) this.skippedAutoreleases.add(expr);
5170-
if (temp) flow.freeTempLocal(temp);
5171-
5172-
// basic values can use more aggressive optimizations
5173-
} else {
5174-
rightExpr = this.performAutoreleasesWithValue(rightFlow, rightExpr, rightType);
5175-
rightFlow.freeScopedLocals();
5176-
this.currentFlow = flow;
5159+
);
5160+
}
5161+
rightExpr = this.performAutoreleasesWithValue(rightFlow, rightExpr, rightType, rightStmts);
5162+
rightFlow.freeScopedLocals();
5163+
this.currentFlow = flow;
51775164

5178-
// simplify if cloning left without side effects is possible
5179-
if (expr = module.cloneExpression(leftExpr, true, 0)) {
51805165
expr = module.if(
51815166
this.makeIsTrueish(leftExpr, leftType),
5182-
expr,
5167+
retainLeftInThen
5168+
? this.makeRetain(
5169+
module.local_get(temp.index, leftType.toNativeType())
5170+
)
5171+
: module.local_get(temp.index, leftType.toNativeType()),
51835172
rightExpr
51845173
);
5174+
if (leftAutoreleaseSkipped || rightAutoreleaseSkipped) this.skippedAutoreleases.add(expr);
5175+
if (temp) flow.freeTempLocal(temp);
51855176

5186-
// if not possible, tee left to a temp. local
5177+
// basic values can use more aggressive optimizations
51875178
} else {
5188-
let temp = flow.getTempLocal(leftType);
5189-
if (!flow.canOverflow(leftExpr, leftType)) flow.setLocalFlag(temp.index, LocalFlags.WRAPPED);
5190-
if (flow.isNonnull(leftExpr, leftType)) flow.setLocalFlag(temp.index, LocalFlags.NONNULL);
5191-
expr = module.if(
5192-
this.makeIsTrueish(module.local_tee(temp.index, leftExpr), leftType),
5193-
module.local_get(temp.index, leftType.toNativeType()),
5194-
rightExpr
5195-
);
5196-
flow.freeTempLocal(temp);
5179+
rightExpr = this.performAutoreleasesWithValue(rightFlow, rightExpr, rightType);
5180+
rightFlow.freeScopedLocals();
5181+
this.currentFlow = flow;
5182+
5183+
// simplify if cloning left without side effects is possible
5184+
if (expr = module.cloneExpression(leftExpr, true, 0)) {
5185+
expr = module.if(
5186+
this.makeIsTrueish(leftExpr, leftType),
5187+
expr,
5188+
rightExpr
5189+
);
5190+
5191+
// if not possible, tee left to a temp. local
5192+
} else {
5193+
let temp = flow.getTempLocal(leftType);
5194+
if (!flow.canOverflow(leftExpr, leftType)) flow.setLocalFlag(temp.index, LocalFlags.WRAPPED);
5195+
if (flow.isNonnull(leftExpr, leftType)) flow.setLocalFlag(temp.index, LocalFlags.NONNULL);
5196+
expr = module.if(
5197+
this.makeIsTrueish(module.local_tee(temp.index, leftExpr), leftType),
5198+
module.local_get(temp.index, leftType.toNativeType()),
5199+
rightExpr
5200+
);
5201+
flow.freeTempLocal(temp);
5202+
}
51975203
}
5204+
this.currentType = leftType;
51985205
}
5199-
this.currentType = leftType;
52005206
break;
52015207
}
52025208
default: {

tests/compiler/logical.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,15 @@ assert(isNaN(F));
6363

6464
F = NaN && 1.0;
6565
assert(isNaN(F));
66+
67+
// Test shortcutting to bool on contextual bool
68+
// see: https://github.com/AssemblyScript/assemblyscript/pull/993
69+
70+
function testShortcutAnd(a: i64, b: i32): bool {
71+
return a && b;
72+
}
73+
function testShortcutOr(a: i64, b: i32): bool {
74+
return a || b;
75+
}
76+
assert(testShortcutAnd(1, 1));
77+
assert(testShortcutOr(0, 1));

0 commit comments

Comments
 (0)