Skip to content

Commit a258b15

Browse files
committed
infer type for deopt + bugfixes
1 parent 012f181 commit a258b15

25 files changed

Lines changed: 206 additions & 106 deletions
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.prlprg.fir.intellij;
2+
3+
import com.intellij.ide.navigationToolbar.StructureAwareNavBarModelExtension;
4+
import com.intellij.lang.Language;
5+
import javax.swing.Icon;
6+
import org.jetbrains.annotations.NotNull;
7+
import org.jetbrains.annotations.Nullable;
8+
9+
/** Adds FIR function declarations and versions to the navigation bar. */
10+
public final class FirNavBarModelExtension extends StructureAwareNavBarModelExtension {
11+
@Override
12+
protected @NotNull Language getLanguage() {
13+
return FirLanguage.INSTANCE;
14+
}
15+
16+
@Override
17+
public @Nullable String getPresentableText(Object object) {
18+
if (object instanceof FirFunDeclaration fun) return fun.getHeaderText();
19+
if (object instanceof FirVersion ver) return ver.getSignatureText();
20+
return null;
21+
}
22+
23+
@Override
24+
public @Nullable Icon getIcon(Object object) {
25+
if (object instanceof FirFunDeclaration fun) {
26+
var pres = fun.getPresentation();
27+
if (pres != null) return pres.getIcon(false);
28+
}
29+
if (object instanceof FirVersion ver) {
30+
var pres = ver.getPresentation();
31+
if (pres != null) return pres.getIcon(false);
32+
}
33+
return null;
34+
}
35+
}

fir-intellij-plugin/src/main/resources/META-INF/plugin.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,7 @@
3939

4040
<breadcrumbsInfoProvider
4141
implementation="org.prlprg.fir.intellij.FirBreadcrumbsProvider"/>
42+
43+
<navbar implementation="org.prlprg.fir.intellij.FirNavBarModelExtension"/>
4244
</extensions>
4345
</idea-plugin>

server/src/main/java/org/prlprg/bc2fir/BC2FirCFGCompiler.java

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -533,12 +533,16 @@ case StartFor(var _, var elemName, var step) -> {
533533
seq));
534534
var init = new Constant(SEXPs.integer(0));
535535
var index = insertAndReturn("_idx", new Aea(init));
536+
// Must be pushed for deopt in body
537+
push(length);
536538
push(index);
537539
setJump(goto_(stepBb));
538540

539541
// For loop step
540542
moveTo(stepBb);
541543
// Increment the index
544+
index = pop();
545+
length = top();
542546
var index1 =
543547
insertAndReturn(
544548
"_idx",
@@ -548,7 +552,7 @@ case StartFor(var _, var elemName, var step) -> {
548552
ImmutableList.of(Type.BOXED_INTEGER, Type.BOXED_INTEGER),
549553
Type.BOXED_INTEGER,
550554
Effects.NONE),
551-
pop(),
555+
index,
552556
new Constant(SEXPs.integer(1))));
553557
push(index1);
554558
// Compare the index to the length
@@ -651,7 +655,8 @@ case StepFor(var body) -> {
651655
}
652656
case EndFor() -> {
653657
compileEndLoop(LoopType.FOR);
654-
// Pop index and replace with `NULL` (a SEXP, in case we use it)
658+
// Pop index and length and push `NULL` (loop's "output")
659+
pop();
655660
pop();
656661
push(new Constant(SEXPs.NULL));
657662
}
@@ -661,31 +666,34 @@ case EndFor() -> {
661666
case LdTrue() -> push(new Constant(SEXPs.TRUE));
662667
case LdFalse() -> push(new Constant(SEXPs.FALSE));
663668
case GetVar(var name) -> {
669+
var preStack = ImmutableList.copyOf(stack);
664670
pushInsert(getStr(name), new Load(LoadType.LOCAL_VAR, getVar(name)));
665-
tryAddCheckpoint(false);
671+
tryAddCheckpoint(false, preStack);
672+
666673
pushInsert(getStr(name), new Force(true, pop()));
667674
insert(intrinsic("checkMissing", top()));
668-
tryAddCheckpoint(true);
675+
tryAddCheckpoint(true, stack);
669676
}
670677
case DdVal(var name) -> {
678+
var preStack = ImmutableList.copyOf(stack);
671679
var ddIndex = NamedVariable.ddNum(get(name).ddNum());
672680
pushInsert(getStr(name), new Load(LoadType.LOCAL_VAR, ddIndex));
673-
tryAddCheckpoint(false);
681+
tryAddCheckpoint(false, preStack);
682+
674683
pushInsert(getStr(name), new Force(true, pop()));
675684
insert(intrinsic("checkMissing", top()));
676-
tryAddCheckpoint(true);
685+
tryAddCheckpoint(true, stack);
677686
}
678687
case SetVar(var name) -> insert(new Store(StoreType.LOCAL_VAR, getVar(name), top()));
679688
case GetFun(var name) -> {
680-
tryAddCheckpoint(false);
681689
var fun = insertAndReturn(getStr(name), new Load(LoadType.LOCAL_FUN, getVar(name)));
682690
pushCall(fun);
683-
tryAddCheckpoint(true);
691+
tryAddCheckpoint(true, stack);
684692
}
685693
case GetGlobFun(var name) -> {
686694
var fun = insertAndReturn(getStr(name), new Load(LoadType.GLOBAL_FUN, getVar(name)));
687695
pushCall(fun);
688-
tryAddCheckpoint(true);
696+
tryAddCheckpoint(true, stack);
689697
}
690698
case GetBuiltin(var name) -> pushCall(new Builtin(get(name).name()));
691699
case GetIntlBuiltin(var name) -> pushCall(new Builtin(get(name).name()));
@@ -890,14 +898,18 @@ case Or2nd(var _) -> {
890898
insert(this::goto_);
891899
}
892900
case GetVarMissOk(var name) -> {
901+
var preStack = ImmutableList.copyOf(stack);
893902
pushInsert(getStr(name), new Load(LoadType.LOCAL_VAR, getVar(name)));
894-
tryAddCheckpoint(false);
903+
tryAddCheckpoint(false, preStack);
904+
895905
pushInsertThenCp(getStr(name), new Force(true, pop()));
896906
}
897907
case DdValMissOk(var name) -> {
908+
var preStack = ImmutableList.copyOf(stack);
898909
var ddIndex = get(name).ddNum();
899910
pushInsert(getStr(name), new Load(LoadType.LOCAL_VAR, NamedVariable.ddNum(ddIndex)));
900-
tryAddCheckpoint(false);
911+
tryAddCheckpoint(false, preStack);
912+
901913
pushInsertThenCp(getStr(name), new Force(true, pop()));
902914
}
903915
case Visible() -> insert(intrinsic("setVisible"));
@@ -1196,9 +1208,9 @@ private Argument compilePromise(SEXP codeSexp) {
11961208
return insertAndReturn("_p", new Promise(Type.ANY_VALUE_SEXP, Effects.REFLECT, cfg));
11971209
}
11981210

1199-
/// End the previously-compiled for loop instruction (the latest [#pushWhileOrRepeatLoop(BB,
1200-
/// BB)] or [#pushForLoop(BB,BB,BB)]): pop its data, assert that the loop is of the correct
1201-
/// type, and that the cursor is right after its end.
1211+
/// End the previously-compiled for loop instruction (the latest
1212+
/// [#pushWhileOrRepeatLoop(BB, BB)] or [#pushForLoop(BB,BB,BB)]): pop its data, assert that
1213+
/// the loop is of the correct type, and that the cursor is right after its end.
12021214
private void compileEndLoop(LoopType type) {
12031215
var loop = popLoop();
12041216
if (loop.type != type) {
@@ -1288,27 +1300,33 @@ private void compileCall(Call call) {
12881300
// endregion compile instructions
12891301

12901302
// region checkpoints
1291-
void pushInsertThenCp(String name, Expression expression) {
1303+
private void pushInsertThenCp(String name, Expression expression) {
12921304
pushInsert(name, expression);
1293-
tryAddCheckpoint(true);
1305+
tryAddCheckpoint(true, stack);
12941306
}
12951307

1296-
void pushInsertThenCp(Expression expression) {
1308+
private void pushInsertThenCp(Expression expression) {
12971309
pushInsert(expression);
1298-
tryAddCheckpoint(true);
1310+
tryAddCheckpoint(true, stack);
12991311
}
13001312

1301-
void tryAddCheckpoint(boolean afterInstruction) {
1302-
var deoptBcPos = afterInstruction ? bcPos + 1 : bcPos;
1313+
/// Add a checkpoint at the current FIŘ position, before or after the current bytecode
1314+
/// position
1315+
///
1316+
/// - If `afterBcInstr = false`, `stack` must be the stack before the bytecode was compiled,
1317+
/// and every FIŘ instruction emitted for the bytecode so far must be pure.
1318+
/// - If `afterBcInstr = true`, `stack` must be the current stack, and no more FIŘ
1319+
/// instructions can be emitted for this bytecode instruction.
1320+
private void tryAddCheckpoint(boolean afterBcInstr, List<Argument> stack) {
1321+
var deoptBcPos = afterBcInstr ? bcPos + 1 : bcPos;
13031322
var deopt = cfg.addBB("D" + numDeopts++);
1304-
deopt.setJump(new Deopt(deoptBcPos, ImmutableList.copyOf(stack)));
1323+
deopt.setJump(new Deopt(deoptBcPos, stack));
13051324

13061325
// Don't add phis because they're never necessary.
13071326
// Don't use `insert` because it adds phis.
13081327
var success = cfg.addBB();
13091328
cursor.bb().setJump(new Checkpoint(new Target(success), new Target(deopt)));
13101329
cursor.moveToStart(success);
1311-
// TODO: Is the below correct?
13121330
bbsWithPhis.add(success);
13131331
}
13141332

@@ -1537,8 +1555,8 @@ private void pushInsert(String name, Expression expression) {
15371555
push(insertAndReturn(name, expression));
15381556
}
15391557

1540-
/// Push a value onto the "virtual stack" so that the next call to [#pop(Class)] or
1541-
/// [#top(Class)] will return it.
1558+
/// Push a value onto the "virtual stack" so that the next call to [#pop()] or
1559+
/// [#top()] will return it.
15421560
///
15431561
/// The bytecode is stack-based and IR is SSA-form. To convert properly, when the compiler
15441562
/// encounters `BcInstr.Ld...` and other instructions that push real values onto the
@@ -1767,8 +1785,8 @@ private Expression warning(String message) {
17671785
new Constant(SEXPs.NULL));
17681786
}
17691787

1770-
/// Stub for function guard (TODO what does [#BcInstr.CheckFun] do again?), which can fallback to
1771-
/// an intrinsic but usually gets caught and turned into [DynamicCallee].
1788+
/// Stub for function guard (TODO what does [BcInstr.CheckFun] do again?), which can fallback
1789+
/// to an intrinsic but usually gets caught and turned into [DynamicCallee].
17721790
private Expression checkFun(Argument fun) {
17731791
return intrinsic("checkFun", fun);
17741792
}

server/src/main/java/org/prlprg/fir/analyze/type/InferType.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.prlprg.fir.ir.expression.Store;
3232
import org.prlprg.fir.ir.expression.SubscriptRead;
3333
import org.prlprg.fir.ir.expression.SubscriptWrite;
34+
import org.prlprg.fir.ir.instruction.Deopt;
3435
import org.prlprg.fir.ir.instruction.Return;
3536
import org.prlprg.fir.ir.type.Concreteness;
3637
import org.prlprg.fir.ir.type.Kind.PrimitiveVector;
@@ -56,8 +57,12 @@ public InferType(Abstraction scope) {
5657
public @Nullable Type of(CFG cfg) {
5758
Type result = null;
5859
for (var bb : cfg.bbs()) {
59-
if (bb.jump() instanceof Return(_, var value)) {
60-
result = Type.union(result, of(value));
60+
switch (bb.jump()) {
61+
case Return(_, var value) -> result = Type.union(result, of(value));
62+
case Deopt _ -> {
63+
return Type.ANY_VALUE_SEXP;
64+
}
65+
default -> {}
6166
}
6267
}
6368
return result;

server/src/main/java/org/prlprg/fir/interpret/internal/Builtins.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ private static SEXP applyBinaryPreservingInt(
486486
}
487487
default -> {}
488488
}
489-
throw interpreter.fail("`" + ctx + "` generic requires logical or numeric args");
489+
throw interpreter.failUnsupported("`" + ctx + "` generic requires logical or numeric args");
490490
}
491491

492492
/// Apply unary math op on SEXP, preserving int type (for +, -)
@@ -520,7 +520,9 @@ private static SEXP applyUnaryPreservingInt(
520520
.map(i -> lv.get(i) == Logical.NA ? Constants.NA_INT : lv.get(i).toInt())
521521
.map(j -> (int) fn.applyAsDouble(j))
522522
.toArray());
523-
default -> throw interpreter.fail("`" + ctx + "` unary requires a logical or numeric arg");
523+
default ->
524+
throw interpreter.failUnsupported(
525+
"`" + ctx + "` unary requires a logical or numeric arg");
524526
};
525527
}
526528

@@ -2586,7 +2588,7 @@ private static double sexpToDouble(SEXP sexp, InternalInterpreter interpreter, S
25862588
: sexp.asScalarInteger().get();
25872589
if (sexp.asScalarReal().isPresent()) return sexp.asScalarReal().get();
25882590
if (sexp.asScalarLogical().isPresent()) return sexp.asScalarLogical().get().toInt();
2589-
throw interpreter.fail(ctx + " requires a numeric scalar");
2591+
throw interpreter.failUnsupported("Mock " + ctx + " only implemented for a numeric scalar");
25902592
}
25912593

25922594
private static @Nullable Double sexpToDoubleOpt(SEXP sexp) {
@@ -2606,7 +2608,7 @@ private static int sexpToInt(SEXP sexp, InternalInterpreter interpreter, String
26062608
? Constants.NA_INT
26072609
: (int) sexp.asScalarReal().get().doubleValue();
26082610
if (sexp.asScalarLogical().isPresent()) return sexp.asScalarLogical().get().toInt();
2609-
throw interpreter.fail(ctx + " requires a numeric scalar");
2611+
throw interpreter.failUnsupported("Mock " + ctx + " only implemented for numeric scalar");
26102612
}
26112613

26122614
private static Logical sexpToLogical(SEXP sexp, InternalInterpreter interpreter, String ctx) {
@@ -2621,7 +2623,8 @@ private static Logical sexpToLogical(SEXP sexp, InternalInterpreter interpreter,
26212623
if (Double.isNaN(v)) return Logical.NA;
26222624
return v == 0.0 ? Logical.FALSE : Logical.TRUE;
26232625
}
2624-
throw interpreter.failUnsupported(ctx + " requires a scalar convertible to logical");
2626+
throw interpreter.failUnsupported(
2627+
"Mock " + ctx + " only implemented for scalar convertible to logical");
26252628
}
26262629

26272630
private static String sexpToString(SEXP sexp) {

server/src/main/java/org/prlprg/fir/interpret/internal/InternalInterpreter.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static org.prlprg.fir.GlobalModules.BOX_FUN;
44
import static org.prlprg.fir.GlobalModules.UNBOX_FUN;
5+
import static org.prlprg.fir.ir.cfg.iterator.BbDfs.bbDfs;
56
import static org.prlprg.sexp.ArgumentMatcher.matchArguments;
67

78
import com.google.common.collect.ImmutableList;
@@ -1168,11 +1169,13 @@ private CFGCursor restoreDeopt(int pc, List<SEXP> deoptStack, @Nullable CFG deop
11681169
}
11691170

11701171
// Get corresponding deopt BC and run sanity checks
1172+
// Corresponding = same bytecode, and if there are multiple, the one with the dominating
1173+
// predecessor (the other(s) do speculative pure instructions that are redundant in
1174+
// bytecode, but may not be here).
11711175
var deoptBc =
1172-
deoptRestoreCfg.bbs().stream()
1176+
com.google.common.collect.Streams.stream(bbDfs(deoptRestoreCfg))
11731177
.filter(bb -> bb.jump() instanceof Deopt(_, var pc1, _) && pc == pc1)
1174-
.collect(
1175-
Streams.zeroOneOrThrow(() -> fail("multiple deopt branches with the same pc?")))
1178+
.findFirst()
11761179
.orElseThrow(
11771180
() ->
11781181
fail(

server/src/main/java/org/prlprg/fir/ir/abstraction/Abstraction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ case Consume(var register) -> {
324324
}
325325

326326
/// Converts the string into a register name that is valid and doesn't already exist.
327-
public Register resemblance(String prefix) {
327+
private Register resemblance(String prefix) {
328328
return Variable.register(
329329
nextLocalDisambiguator.disambiguate(Register.resemblance(prefix).name()));
330330
}

server/src/main/java/org/prlprg/fir/ir/abstraction/substitute/InlineSubstituter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ protected void commitAffectLocals() {
5858
var type = scope.removeLocal(oldRegister).type();
5959
newLocals.add(new Local(newRegister, type));
6060
}
61-
// Must add new locals after,
62-
// otherwise we may add a local that already exists, but will be removed later.
61+
// Must add new locals after
62+
// Otherwise we may add a local that initially existed, but was removed
6363
for (var local : newLocals) {
6464
scope.addLocal(local);
6565
}

server/src/main/java/org/prlprg/fir/opt/Inline.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,13 @@ private void tryInlineCall(
287287
// substitute parameters with arguments, and substitute locals with new locals.
288288
var body = new Abstraction(scope.module(), List.of());
289289
assert body.cfg() != null;
290+
var tempLocalsForDisambiguation = new ArrayList<Register>();
290291
for (var parameter : callee.parameters()) {
291292
body.addLocal(new Local(parameter.variable(), parameter.type()));
293+
if (!scope.contains(parameter.variable())) {
294+
scope.addLocal(new Local(parameter.variable(), Type.ANY_SEXP));
295+
tempLocalsForDisambiguation.add(parameter.variable());
296+
}
292297
}
293298
for (var local : callee.locals()) {
294299
if (!(local.variable() instanceof Register _)) {
@@ -307,6 +312,9 @@ private void tryInlineCall(
307312
localSubstituter.stage(oldVariable, disambiguatedVariable);
308313
}
309314
localSubstituter.commit();
315+
for (var tempLocalForDisambiguation : tempLocalsForDisambiguation) {
316+
scope.removeLocal(tempLocalForDisambiguation);
317+
}
310318
var parameterSubstituter = new Substituter(body);
311319
for (int i = 0; i < callee.parameters().size(); i++) {
312320
var parameter = callee.parameters().get(i).variable();

0 commit comments

Comments
 (0)