Skip to content

Commit

Permalink
@wip IR: Node#type is now RType not Class
Browse files Browse the repository at this point in the history
`RType` = enhanced, fine-grained `Class` (added documentation on the "fine-grained" part).

Also refactored and fixed `InstrData`, need to do finish `StmtData` and `JumpData` but we really need an idea of how this IR works before then.

I'm probably now going to work on getting this into a merge-able state, even disabling the IR tests and commenting unfinished code.
  • Loading branch information
Jakobeha committed Aug 8, 2024
1 parent 7f273da commit 22093b8
Show file tree
Hide file tree
Showing 22 changed files with 618 additions and 462 deletions.
6 changes: 3 additions & 3 deletions src/main/java/org/prlprg/ir/cfg/Constant.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.google.common.collect.ImmutableBiMap;
import javax.annotation.Nullable;
import org.prlprg.ir.type.RType;
import org.prlprg.parseprint.ParseMethod;
import org.prlprg.parseprint.Parser;
import org.prlprg.parseprint.PrintMethod;
Expand All @@ -19,7 +20,6 @@
import org.prlprg.sexp.ScalarLglSXP;
import org.prlprg.sexp.ScalarRealSXP;
import org.prlprg.sexp.ScalarStrSXP;
import org.prlprg.util.Classes;

/**
* An IR node that, at runtime, is guaranteed to be a single statically-known constant value: {@link
Expand Down Expand Up @@ -113,8 +113,8 @@ record ConstantImpl<T>(T value) implements Constant<T> {
}

@Override
public Class<? extends T> type() {
return Classes.classOf(value);
public RType type() {
return RType.exact(value);
}

@Override
Expand Down
9 changes: 3 additions & 6 deletions src/main/java/org/prlprg/ir/cfg/GlobalNodeId.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
package org.prlprg.ir.cfg;

import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
import java.util.Objects;
import javax.annotation.Nonnull;
import org.prlprg.ir.type.RType;
import org.prlprg.parseprint.ParseMethod;
import org.prlprg.parseprint.Parser;
import org.prlprg.parseprint.PrintMethod;
import org.prlprg.parseprint.Printer;
import org.prlprg.parseprint.SkipWhitespace;
import org.prlprg.util.Strings;

/**
* Unique identifier for a {@link GlobalNode}.
Expand All @@ -31,7 +28,7 @@ public sealed interface GlobalNodeId<T> extends NodeId<T> {

@Override
@Nonnull
Class<? extends T> type();
RType type();

@ParseMethod
private static GlobalNodeId<?> parse(Parser p) {
Expand All @@ -41,7 +38,7 @@ private static GlobalNodeId<?> parse(Parser p) {

record GlobalNodeIdImpl<T>(GlobalNode<T> node) implements GlobalNodeId<T> {
@Override
public @Nonnull Class<? extends T> type() {
public @Nonnull RType type() {
return node.type();
}

Expand Down
18 changes: 16 additions & 2 deletions src/main/java/org/prlprg/ir/cfg/Instr.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@
import javax.annotation.Nullable;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.UnmodifiableView;
import org.prlprg.ir.cfg.instr.InputNodeTypeException;
import org.prlprg.ir.cfg.instr.InputTypeException;
import org.prlprg.ir.cfg.instr.InstrData;
import org.prlprg.ir.cfg.instr.JumpData;
import org.prlprg.ir.cfg.instr.StmtData;
import org.prlprg.ir.closure.CodeObject;
import org.prlprg.ir.effect.REffects;
import org.prlprg.ir.type.RType;

/**
* IR instruction: {@link Stmt} or {@link Jump}. An instruction is a single operation in a basic
Expand Down Expand Up @@ -43,6 +44,7 @@ public sealed abstract class Instr implements InstrOrPhi, InstrOrPhiImpl permits

protected IFun fun;
protected final Object[] inputs;
protected final RType[] requiredInputTypes;
private REffects effects;
private final ImmutableList<InstrOutput<?>> outputs;

Expand All @@ -68,6 +70,7 @@ public sealed interface Serial permits Stmt.Serial, Jump.Serial {

fun = data.fun();
inputs = data.inputs();
requiredInputTypes = data.requiredInputTypes();
effects = data.effects();
outputs = data.outputTypes()
.stream()
Expand Down Expand Up @@ -99,6 +102,17 @@ public IFun fun() {
return List.of(inputs);
}

/**
* (A view of) the required {@link RType} type of each of the instruction's inputs.
*
* <p>You usually don't need to use this directly, e.g. input types are automatically checked when
* an instruction is created or when one of its inputs is mutated in a way that changes its {@link
* RType}.
*/
public @UnmodifiableView List<RType> requiredInputTypes() {
return List.of(requiredInputTypes);
}

/**
* (A shallow copy of) the nodes in the instruction's {@link #inputs()}.
*
Expand Down Expand Up @@ -170,7 +184,7 @@ public CascadingInstrUpdate unsafeReplaceInInputs(CascadingUpdatedInstrs seen, N
* expected type, and you are responsible for updating instructions whose inputs contain outputs
* that the replacement changed the type of. Hence this is package-private and "unsafe".
*
* @throws InputNodeTypeException If an input's {@link Node#type()} has changed so that it's no
* @throws InputTypeException If an input's {@link Node#type()} has changed so that it's no
* longer a subtype of the required type.
* @throws InfiniteCascadingUpdateException if an instruction's updated outputs trigger more
* updates that eventually update that instruction's inputs so that its outputs update again. This
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/org/prlprg/ir/cfg/InstrOutput.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.prlprg.ir.cfg;

import org.prlprg.ir.type.RType;

/**
* A {@link Node} that was produced by an {@link Instr}.
*
Expand All @@ -23,7 +25,7 @@ public final class InstrOutput<T> extends LocalNode<T> {
public <U> InstrOutput<? extends U> cast(Class<U> clazz) {
return (InstrOutput<U>) super.cast(clazz);
}
InstrOutput(Instr origin, Class<? extends T> type) {
InstrOutput(Instr origin, RType type) {
super(origin.cfg(), type, origin.cfg().uniqueLocalId());
this.origin = origin;
}
Expand Down
16 changes: 8 additions & 8 deletions src/main/java/org/prlprg/ir/cfg/LocalNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.prlprg.ir.cfg.CFGEdit.SetLocalNodeId;
import org.prlprg.ir.cfg.builder.CFGCleanup;
import org.prlprg.ir.type.RType;
import org.prlprg.parseprint.ParseMethod;
import org.prlprg.parseprint.Parser;
import org.prlprg.parseprint.PrintMethod;
Expand All @@ -20,7 +21,7 @@
*/
public sealed class LocalNode<T> implements Node<T> permits Param, Phi, InstrOutput {
private final CFG cfg;
private Class<? extends T> type;
private RType type;
private LocalNodeId<T> id;

/**
Expand All @@ -37,7 +38,7 @@ public <U> LocalNode<? extends U> cast(Class<U> clazz) {
return (LocalNode<U>) Node.super.cast(clazz);
}

protected LocalNode(CFG cfg, Class<? extends T> type, LocalNodeId<T> id) {
protected LocalNode(CFG cfg, RType type, LocalNodeId<T> id) {
this.cfg = cfg;
this.type = type;
this.id = id;
Expand All @@ -48,12 +49,11 @@ protected LocalNode(CFG cfg, Class<? extends T> type, LocalNodeId<T> id) {
/**
* Unsafely change this node's type.
*
* <p>You must ensure that, either the type is a subtype of the current one, or all occurrences of
* the node have the new type checked, to ensure no occurrence's static type is more specific.
* <p>It's "unsafe" because you need to maintain that the node's runtime values are guaranteed to
* be subtypes of its type.
*/
@SuppressWarnings("unchecked")
protected void unsafeSetType(Class<?> type) {
this.type = (Class<? extends T>) type;
protected void unsafeSetType(RType type) {
this.type = type;
id.unsafeReassignType(type);
}

Expand Down Expand Up @@ -119,7 +119,7 @@ public CFG cfg() {
}

@Override
public Class<? extends T> type() {
public RType type() {
return type;
}

Expand Down
23 changes: 12 additions & 11 deletions src/main/java/org/prlprg/ir/cfg/LocalNodeId.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

import java.util.Objects;
import javax.annotation.Nullable;
import org.prlprg.ir.type.RType;
import org.prlprg.parseprint.ParseMethod;
import org.prlprg.parseprint.Parser;
import org.prlprg.parseprint.SkipWhitespace;

/** {@link NodeId} of a {@link LocalNode}. */
public sealed class LocalNodeId<T> implements NodeId<T> permits PhiId {
private @Nullable Class<? extends T> type;
private @Nullable RType type;
private final int disambiguator;
private final String name;
private final String toString;
Expand All @@ -29,28 +30,28 @@ String name() {
}

@Override
public @Nullable Class<? extends T> type() {
public @Nullable RType type() {
return type;
}

/**
* Assign a class to an ID which may have been parsed, or if not, just check that the class is the
* same as the one that would've been assigned.
*/
// Cast is valid if `this.clazz` is unset (means we dynamically delayed assigning a generic arg)
// or `this.clazz == clazz` (no-op);
@SuppressWarnings("unchecked")
void lateAssignType(Class<?> type) {
void lateAssignType(RType type) {
// `assert` because this is only used within the package.
assert this.type == null || this.type == type
: "node ID is for a different type: expected " + this.type + ", got " + type;
this.type = (Class<? extends T>) type;
this.type = type;
}

/** Unsafely change this ID's type. */
@SuppressWarnings("unchecked")
void unsafeReassignType(Class<?> type) {
this.type = (Class<? extends T>) type;
/** Change this ID's {@link #type()}.
*
* <p>It's "unsafe" because you need to maintain that the ID's runtime values are guaranteed to be
* subtypes of its type.
*/
void unsafeReassignType(RType type) {
this.type = type;
}

@Override
Expand Down
49 changes: 24 additions & 25 deletions src/main/java/org/prlprg/ir/cfg/Node.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.prlprg.ir.cfg;

import java.util.Objects;
import org.prlprg.ir.type.RType;
import org.prlprg.parseprint.Parser;

/**
Expand Down Expand Up @@ -27,15 +29,16 @@ static <U> Node<? extends U> cast(Node node, Class<U> clazz) {
*/
@SuppressWarnings("unchecked")
default <U> Node<? extends U> cast(Class<U> clazz) {
// `Void` is special-cased to allow `InvalidNode` to emulate subclassing every other `Node`,
// even though Java's type system can't encode BOTTOM.
if (!clazz.isAssignableFrom(type()) && type() != Void.class) {
if (type().getSimpleName().equals(clazz.getSimpleName())) {
if (!type().isSubsetOf(clazz)) {
var myClass = Objects.requireNonNull(type().clazz(), "the nothing type is a subset of all classes");
assert myClass != clazz;

if (myClass.getSimpleName().equals(clazz.getSimpleName())) {
throw new ClassCastException(
"Can't cast "
+ id()
+ " of type "
+ type().getName()
+ myClass.getName()
+ " to "
+ clazz.getName()
+ " (they have the same simple name, but are different classes)");
Expand All @@ -44,7 +47,7 @@ default <U> Node<? extends U> cast(Class<U> clazz) {
"Can't cast "
+ id()
+ " of type "
+ type().getSimpleName()
+ myClass.getSimpleName()
+ " to "
+ clazz.getSimpleName());
}
Expand All @@ -53,30 +56,26 @@ default <U> Node<? extends U> cast(Class<U> clazz) {
}

/**
* This is {@link T}: the type that the node represents an abstract runtime value of.
*
* <p>In some cases, it may be a subtype of {@link T}. For example, a {@link Phi}'s dynamic type
* is always the union of its inputs' types. Since phi inputs can change, this means that the
* phi's dynamic type can become more or less specific, but it's static type, being static, will
* always be the same (in the event a phi's type becomes less specific in a way that breaks
* occurrences in other instructions, a runtime exception is thrown).
*
* <p>For {@link GlobalNode}, which is the subtype of any other node, this returns {@link Void}.
* Java's type system can't encode a true BOTTOM type, but {@link Void} is a type with no
* (non-null) inhabitants, and we special-case it to emulate BOTTOM (e.g. {@link #cast(Class)}
* always works on {@link InvalidNode} even though {@link Void} isn't a subclass).
* This represents {@link T}: the type that the node represents an abstract runtime value of.
*
* <p>This is needed due to Java's type erasure: if you cast {@code Node<? extends A>} to {@code
* Node<? extends B>} where {@code B &lt;/: A} the compiler silently allows it, and if you upcast
* {@code Node<? extends B>} to {@code Node<? extends A>} there's no way to safely recover the
* original type and downcast. So, we store this data in {@link Node}, and periodically check it
* (to prevent the illegal upcast) and to recover the original type (to enable safe downcast).
* <p>It's used to check node types at runtime. Due to Java's type erasure and because some node
* types change when the node is mutated (e.g. {@link Phi}s when their inputs change), it's
* possible to have a node in {@code Node<? extends A>} whose runtime values aren't actually
* subtypes of {@code A}. To alleviate this issue, we periodically check the node's runtime {@code
* #type()} against its compile-time {@code T} (specifically, in {@link #cast(Class)} and in
* {@linkplain Instr#inputs() instruction inputs}.
*/
Class<? extends T> type();
RType type();

/** Whether this node's {@link #type()}} is a subtype of the given type (incl. identical). */
default boolean isSubtypeOf(RType otherType) {
return type().isSubsetOf(otherType);
}

/** Whether this node's {@link #type()}} is a subtype of the {@link RType} representing the given
* class (incl. identical). */
default boolean isSubtypeOf(Class<?> otherType) {
return otherType.isAssignableFrom(type());
return type().isSubsetOf(otherType);
}

/**
Expand Down
15 changes: 7 additions & 8 deletions src/main/java/org/prlprg/ir/cfg/NodeId.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import org.prlprg.ir.type.RType;
import org.prlprg.parseprint.ParseMethod;
import org.prlprg.parseprint.Parser;
import org.prlprg.parseprint.Scanner;
Expand Down Expand Up @@ -32,18 +33,16 @@ static boolean isNodeIdStart(Scanner s) {
}

/**
* This is {@link T}, the type of the value a node with this identifier represents.
* This represents {@link T}: the type that the node represents an abstract runtime value of.
*
* <p>It can be {@code null} if the ID hasn't been assigned to a node (e.g. was just parsed). In
* this case, it will be set when the ID is assigned to a node.
* this case, it will be set when the ID is assigned to a {@link Node}. Otherwise, it's equal to
* the assigned node's {@link Node#type()}.
*
* <p>This is necessary due to Java's type erasure: see {@link Node#type()} for more info.
*
* <p>This is {@code null} for IDs that have just been parsed so the class can't be determined.
* Once one of these "untyped" IDs is assigned to a node, the node will create a new ID with the
* correct class and the untyped one will be discarded.
* <p>It's purpose is the same as {@link Node#type()}'s, see that documentation for more details.
*/
@Nullable Class<? extends T> type();
@Nullable
RType type();

@ParseMethod(SkipWhitespace.NONE)
private static NodeId<?> parse(Parser p) {
Expand Down
Loading

0 comments on commit 22093b8

Please sign in to comment.