> worklist) {
+ var block = worklist.poll();
+ assert block != null;
+
+ var exitUsage = blockUsageMap.get(block);
+ exitUsage.processed = true;
+ var usage = new BlockUsage(exitUsage, true);
+
+ var instructions = blockInstructions.get(block);
+ for (var instruction : instructions.reversed()) {
+ switch (instruction) {
+ case RAVInstruction.LocationMove move -> handleMove(usage, move.from, move.to);
+ case RAVInstruction.Op op -> {
+ if (op.lirInstruction instanceof StandardOp.LabelOp) {
+ this.resolveLabel(usage, op, block);
+ continue;
+ }
+
+ if (firstUsages.containsKey(op)) {
+ for (RAVariable variable : firstUsages.get(op)) {
+ usage.locations.put(variable, initialLocations.get(variable));
+ }
+ }
+ }
+ default -> {
+ }
+ }
+ }
+
+ for (int i = 0; i < block.getPredecessorCount(); i++) {
+ var pred = block.getPredecessorAt(i);
+
+ if (this.blockUsageMap.get(pred) == null) {
+ this.blockUsageMap.put(pred, new BlockUsage(usage, false));
+ } else {
+ var predReached = this.blockUsageMap.get(pred);
+ if (!mergeInto(predReached, usage)) {
+ if (predReached.processed) {
+ continue;
+ }
+
+ /*
+ * Either it has not been processed, or the processed flag was set by alias
+ * resolution (see resolveLabel end) to force block to be processed again to
+ * resolve another variable.
+ */
+ }
+ }
+
+ worklist.add(pred);
+ }
+ }
+
+ /**
+ * Two blocks meet, a successor merges into it's predecessor to pass in newly reached variable
+ * and locations.
+ *
+ * @param block The base block, where information is being merged to
+ * @param successor The successor block, where new information is coming from
+ * @return Has the current block (predecessor) been changed?
+ */
+ protected boolean mergeInto(BlockUsage block, BlockUsage successor) {
+ boolean changed = false;
+ for (RAVariable variable : successor.locations.keySet()) {
+ if (!labelMap.containsKey(variable)) {
+ continue; // Do not push already resolved variables further
+ }
+
+ RAValue defValue = successor.locations.get(variable);
+ if (defValue.equals(block.locations.get(variable))) {
+ continue;
+ }
+
+ block.locations.put(variable, defValue);
+ changed = true;
+ }
+
+ return changed;
+ }
+
+ /**
+ * Initialize first usages for variables, top-down in-order to collect all necessary information
+ * for the resolution.
+ */
+ protected void initializeUsages() {
+ for (var block : lir.getControlFlowGraph().getBlocks()) {
+ var instructions = blockInstructions.get(block);
+ var label = (RAVInstruction.Op) instructions.getFirst();
+
+ for (var i = 0; i < label.dests.count; i++) {
+ if (label.dests.orig[i].isVariable() && label.dests.curr[i] == null) {
+ var variable = label.dests.orig[i].asVariable();
+ labelMap.put(variable, label);
+ }
+ }
+
+ for (var instruction : instructions) {
+ if (instruction instanceof RAVInstruction.Op op) {
+ if (op.lirInstruction instanceof StandardOp.JumpOp) {
+ continue;
+ }
+
+ handleUsages(op.uses, op, block);
+ handleUsages(op.alive, op, block);
+
+ if (op.hasCompleteState()) {
+ handleUsages(op.stateValues, op, block);
+ }
+ }
+ }
+
+ var jump = (RAVInstruction.Op) instructions.getLast();
+ for (var i = 0; i < jump.alive.count; i++) {
+ if (!jump.alive.orig[i].isVariable()) {
+ continue;
+ }
+
+ var variable = jump.alive.orig[i].asVariable();
+ if (labelMap.containsKey(variable) && !initialLocations.containsKey(variable)) {
+ // No usage found before this jump
+ var succ = block.getSuccessorAt(0);
+ var succLabel = (RAVInstruction.Op) blockInstructions.get(succ).getFirst();
+ var alias = succLabel.dests.orig[i].asVariable();
+ if (alias.equals(variable)) {
+ continue; // Loop
+ }
+
+ var aliasedVariables = aliasMap.getOrDefault(alias, new ArrayList<>());
+ aliasedVariables.add(new AliasPair(variable, block));
+
+ aliasMap.put(alias, aliasedVariables);
+ }
+ }
+
+ if (block.getSuccessorCount() == 0) {
+ endBlocks.add(block);
+ continue;
+ }
+
+ if (block.isLoopHeader()) {
+ /*
+ * Here we handle loops without any exit that might also need a resolution of label
+ * variables, but are not reachable from endBlocks set, so we add predecessors of
+ * such loops that are part of the loop into the endBlocks set to process them.
+ */
+ var loop = block.getLoop();
+ if (loop.getNaturalExits().isEmpty() && loop.getLoopExits().isEmpty()) {
+ var loopBlocks = loop.getBlocks();
+ for (int i = 0; i < block.getPredecessorCount(); i++) {
+ var pred = block.getPredecessorAt(i);
+ if (loopBlocks.contains(pred)) {
+ endBlocks.add(pred);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Find first usages for variables defined in labels.
+ *
+ * @param values Values of this instruction where are looking for usage
+ * @param op Instruction that holds values
+ * @param block Block where this instruction is in
+ */
+ protected void handleUsages(RAVInstruction.ValueArrayPair values, RAVInstruction.Op op, BasicBlock> block) {
+ for (var i = 0; i < values.count; i++) {
+ if (!values.orig[i].isVariable() || values.curr[i] == null) {
+ continue;
+ }
+
+ var variable = values.orig[i].asVariable();
+ if (labelMap.containsKey(variable) && !initialLocations.containsKey(variable)) {
+ if (!firstUsages.containsKey(op)) {
+ firstUsages.put(op, new EconomicHashSet<>());
+ }
+
+ firstUsages.get(op).add(variable);
+ initialLocations.put(variable, values.curr[i]);
+
+ for (var entry : aliasMap.entrySet()) {
+ var aliasedVariables = entry.getValue();
+
+ aliasedVariables.removeIf(pair -> pair.variable.equals(variable));
+ }
+ }
+ }
+ }
+
+ /**
+ * Handle a register allocator inserted move, change locations of variables based on the
+ * locations.
+ *
+ * If a variable is in location reg1 and a move is found reg1 = MOVE reg2, then
+ * said variable will now be in reg2, because reg1 will now have different content
+ * when walking through the instructions in reverse.
+ *
+ * @param usage Variable locations for this block
+ * @param from Source location
+ * @param to Destination location
+ */
+ protected void handleMove(BlockUsage usage, RAValue from, RAValue to) {
+ var updatedVariables = new EconomicHashMap();
+ for (var entry : usage.locations.entrySet()) {
+ var variable = entry.getKey();
+ var location = entry.getValue();
+
+ if (location.equals(to)) {
+ updatedVariables.put(variable, from);
+ }
+ }
+
+ usage.locations.putAll(updatedVariables);
+ }
+
+ /**
+ * Resolve locations for all variables in a label, also mark the first usage for aliased
+ * variables - variables used in predecessors that have no other usage.
+ *
+ * @param usage usage for the block we are resolving contains the locations
+ * @param label label we are resolving
+ * @param block block of the label
+ */
+ protected void resolveLabel(BlockUsage usage, RAVInstruction.Op label, BasicBlock> block) {
+ for (int i = 0; i < label.dests.count; i++) {
+ if (!label.dests.orig[i].isVariable()) {
+ continue;
+ }
+
+ var variable = label.dests.orig[i].asVariable();
+ if (label.dests.curr[i] != null) {
+ continue; // Already resolved
+ }
+
+ if (!usage.locations.containsKey(variable)) {
+ continue;
+ }
+
+ var location = usage.locations.get(variable);
+
+ label.dests.curr[i] = location;
+ for (int j = 0; j < block.getPredecessorCount(); j++) {
+ var pred = block.getPredecessorAt(j);
+ var jump = (RAVInstruction.Op) blockInstructions.get(pred).getLast();
+
+ var orig = jump.alive.orig[i];
+ if (!RAValue.kindsEqual(orig, location)) {
+ /*
+ * TestCase: DerivedOopTest B3: rdx|QWORD[*] = REGMOVE rcx|QWORD[.+] <--
+ * Type is cast here [] = JUMP [] [v8|QWORD[.+] -> rdx|QWORD[*]] [] <-- Needs
+ * the type change B5: [v12|QWORD[*] -> rdx|QWORD[*]] = LABEL [] [] [] [] =
+ * BLACKHOLE [v12|QWORD[*] -> rdx|QWORD[*]] [] []
+ */
+
+ jump.alive.orig[i] = RAValue.cast(orig, location);
+ }
+
+ jump.alive.curr[i] = location; // Set predecessor location
+ }
+
+ /*
+ * Variables that are passed into jumps without any other usage are resolved after
+ * variable, it's alias, in successor label.
+ */
+ if (aliasMap.containsKey(variable)) {
+ var aliasedVariables = aliasMap.get(variable);
+ for (var aliasPair : aliasedVariables) {
+ if (blockUsageMap.get(aliasPair.block) == null) {
+ this.blockUsageMap.put(aliasPair.block, new BlockUsage());
+ }
+
+ var aliasBlockUsage = blockUsageMap.get(aliasPair.block);
+ aliasBlockUsage.locations.put(aliasPair.variable, location);
+ aliasBlockUsage.processed = false; // Needs to be processed again
+ }
+ }
+
+ labelMap.remove(variable);
+ usage.locations.remove(variable);
+ }
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RAVConstant.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RAVConstant.java
new file mode 100644
index 000000000000..0754a914f58a
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RAVConstant.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier;
+
+import jdk.graal.compiler.lir.ConstantValue;
+import jdk.vm.ci.meta.Constant;
+
+public class RAVConstant extends RAValue {
+ protected final ConstantValue value;
+
+ /**
+ * Setting from
+ * {@link jdk.graal.compiler.lir.StandardOp.LoadConstantOp#canRematerializeToStack}, stored in
+ * here to be able to check, if it was not violated, if a constant was rematerialized by the
+ * allocator.
+ */
+ public final boolean canRematerializeToStack;
+
+ public RAVConstant(ConstantValue value, boolean canRematerializeToStack) {
+ super(value);
+
+ this.value = value;
+ this.canRematerializeToStack = canRematerializeToStack;
+ }
+
+ public Constant getConstant() {
+ return value.getConstant();
+ }
+
+ public ConstantValue getConstantValue() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof RAVConstant ravConstant) {
+ return value.getConstant().equals(ravConstant.value.getConstant());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return value.getConstant().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return value.getConstant().toString();
+ }
+
+ @Override
+ public boolean isConstant() {
+ return true;
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RAVError.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RAVError.java
new file mode 100644
index 000000000000..43797caf0431
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RAVError.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier;
+
+import jdk.graal.compiler.debug.GraalError;
+
+/**
+ * An internal error occurred within the verification process, not caused by the
+ * {@link jdk.graal.compiler.lir.alloc.RegisterAllocationPhase register allocation}.
+ */
+@SuppressWarnings("serial")
+public class RAVError extends GraalError {
+ public RAVError(String message) {
+ super(message);
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RAVInstruction.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RAVInstruction.java
new file mode 100644
index 000000000000..903f95056f78
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RAVInstruction.java
@@ -0,0 +1,551 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier;
+
+import jdk.graal.compiler.lir.ConstantValue;
+import jdk.graal.compiler.lir.InstructionValueProcedure;
+import jdk.graal.compiler.lir.LIRInstruction;
+import jdk.graal.compiler.lir.LIRValueUtil;
+import jdk.graal.compiler.lir.StandardOp;
+import jdk.graal.compiler.lir.VirtualStackSlot;
+import jdk.vm.ci.code.RegisterValue;
+import jdk.vm.ci.code.StackSlot;
+import jdk.vm.ci.meta.JavaKind;
+import jdk.vm.ci.meta.JavaValue;
+import jdk.vm.ci.meta.Value;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+public class RAVInstruction {
+ /**
+ * Base class for all RAV instructions.
+ */
+ public abstract static class Base {
+ /**
+ * Underlying LIR instruction that this is representing.
+ */
+ protected LIRInstruction lirInstruction;
+
+ /**
+ * List of speculative moves, these might be removed but still hold important information
+ * for us, so we add them to the verifier IR in case they are, this happens when a MOVE
+ * source and target locations are equal (and thus redundant) but before allocation their
+ * variable counter-parts are not equal.
+ *
+ *
+ * Before alloc: {@code v1 = MOVE v2} after alloc: {@code rax = MOVE rax}
+ *
+ */
+ protected List speculativeMoveList;
+
+ public Base(LIRInstruction lirInstruction) {
+ this.lirInstruction = lirInstruction;
+ this.speculativeMoveList = new ArrayList<>();
+ }
+
+ public LIRInstruction getLIRInstruction() {
+ return lirInstruction;
+ }
+
+ public void addSpeculativeMove(CoalescedMove speculativeMove) {
+ this.speculativeMoveList.add(speculativeMove);
+ }
+
+ public List getSpeculativeMoveList() {
+ return speculativeMoveList;
+ }
+ }
+
+ /**
+ * Helper class to handle pairs of locations and variables.
+ */
+ public static class ValueArrayPair {
+ /**
+ * Array of size count holding original variables before allocation.
+ */
+ public RAValue[] orig;
+ /**
+ * Array of size count holding current locations used after allocation.
+ */
+ public RAValue[] curr;
+
+ public ArrayList> operandFlags;
+
+ /**
+ * Number of pairs of values stored here.
+ */
+ public int count;
+
+ public ValueArrayPair(int count) {
+ this.count = count;
+ this.curr = new RAValue[count];
+ this.orig = new RAValue[count];
+ this.operandFlags = new ArrayList<>(count);
+ }
+
+ public InstructionValueProcedure copyOriginalProc = new InstructionValueProcedure() {
+ private int index = 0;
+
+ @Override
+ public Value doValue(LIRInstruction instruction, Value value, LIRInstruction.OperandMode mode, EnumSet flags) {
+ ValueArrayPair.this.addOrig(index, value);
+ ValueArrayPair.this.operandFlags.add(flags);
+ index++;
+ return value;
+ }
+ };
+
+ public InstructionValueProcedure copyCurrentProc = new InstructionValueProcedure() {
+ private int index = 0;
+
+ @Override
+ public Value doValue(LIRInstruction instruction, Value value, LIRInstruction.OperandMode mode, EnumSet flags) {
+ ValueArrayPair.this.addCurrent(index, value);
+
+ if (index < operandFlags.size() && !operandFlags.get(index).equals(flags)) {
+ throw new IllegalStateException();
+ }
+
+ index++;
+ return value;
+ }
+ };
+
+ public RAValue getCurrent(int index) {
+ assert index < this.count : "Index out of bounds";
+ return this.curr[index];
+ }
+
+ public RAValue getOrig(int index) {
+ assert index < this.count : "Index out of bounds";
+ return this.orig[index];
+ }
+
+ public void addCurrent(int index, Value value) {
+ if (index >= this.count) {
+ /*
+ * In test case DerivedOopTest, liveBasePointers has extra values after allocation
+ * in frame state, so we skip them
+ */
+ return;
+ }
+
+ this.curr[index] = RAValue.create(value);
+ }
+
+ public void addOrig(int index, Value value) {
+ assert index < this.orig.length : "Index out of bounds";
+ this.orig[index] = RAValue.create(value);
+ }
+
+ /**
+ * Verify that all presumed values are present and that both sides have them.
+ *
+ * @return true, if contents have been successfully verified, false if there's null in
+ * either array.
+ */
+ public boolean verifyContents() {
+ for (int i = 0; i < this.curr.length; i++) {
+ if (this.curr[i] == null || this.orig[i] == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder("[");
+ for (int i = 0; i < this.count; i++) {
+ var origVar = this.orig[i];
+ var currLoc = this.curr[i];
+
+ if (currLoc != null) {
+ result.append(origVar.getValue().toString()).append(" -> ").append(currLoc.getValue().toString());
+ } else {
+ result.append(origVar.getValue().toString());
+ }
+
+ result.append(", ");
+ }
+
+ if (this.count > 0) {
+ result.setLength(result.length() - 2);
+ }
+
+ result.append("]");
+ return result.toString();
+ }
+ }
+
+ public static class StateValuePair {
+ public JavaKind[] kinds;
+ public JavaValue[] curr;
+ public JavaValue[] orig;
+
+ StateValuePair(JavaValue[] orig, JavaKind[] kinds) {
+ this.orig = orig;
+ this.kinds = kinds;
+ }
+
+ public void setCurr(JavaValue[] curr) {
+ this.curr = curr;
+ }
+ }
+
+ /**
+ * RAV instruction that handles a regular operation in an abstract way - we do not care about
+ * the function of said operation.
+ */
+ public static class Op extends Base {
+ /**
+ * Pairs of outputs generated by this operation.
+ */
+ public ValueArrayPair dests;
+ /**
+ * Pairs of inputs used by this operation.
+ */
+ public ValueArrayPair uses;
+ /**
+ * Pairs of temporaries used by this operation.
+ */
+ public ValueArrayPair temp;
+ /**
+ * Pairs of inputs used by this operation that need to be alive after it completes.
+ */
+ public ValueArrayPair alive;
+
+ /**
+ * Pairs of values retrieved from LIRFrameState, verified the same as any other input to
+ * make sure GC has all necessary information.
+ */
+ public ValueArrayPair stateValues;
+
+ /**
+ * Bytecode frame information for this instruction.
+ */
+ public ArrayList bcFrames;
+
+ /**
+ * List of GC roots, calculated using LocationMarker class, other references in state maps
+ * need to be nullified.
+ */
+ public Set references;
+
+ /**
+ * Count the number of values stored.
+ */
+ private final class GetCountProcedure implements InstructionValueProcedure {
+ private int valueCount = 0;
+
+ public int getCount() {
+ int count = this.valueCount;
+ // Reset the count and go again for different argument type
+ this.valueCount = 0;
+ return count;
+ }
+
+ @Override
+ public Value doValue(LIRInstruction instruction, Value value, LIRInstruction.OperandMode mode, EnumSet flags) {
+ valueCount++;
+ return value;
+ }
+ }
+
+ public Op(LIRInstruction instruction) {
+ super(instruction);
+
+ /*
+ * We first count the number of entries, then allocate an array of said size for both
+ * current locations and original variables.
+ */
+ var countValuesProc = new GetCountProcedure();
+
+ instruction.forEachInput(countValuesProc);
+ this.uses = new ValueArrayPair(countValuesProc.getCount());
+
+ instruction.forEachOutput(countValuesProc);
+ this.dests = new ValueArrayPair(countValuesProc.getCount());
+
+ instruction.forEachTemp(countValuesProc);
+ this.temp = new ValueArrayPair(countValuesProc.getCount());
+
+ instruction.forEachAlive(countValuesProc);
+ this.alive = new ValueArrayPair(countValuesProc.getCount());
+
+ instruction.forEachState(countValuesProc);
+ this.stateValues = new ValueArrayPair(countValuesProc.getCount());
+
+ this.bcFrames = new ArrayList<>();
+ }
+
+ public boolean hasMissingDefinitions() {
+ for (int i = 0; i < this.dests.count; i++) {
+ if (this.dests.curr[i] == null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean isLabel() {
+ return lirInstruction instanceof StandardOp.LabelOp;
+ }
+
+ public boolean isJump() {
+ return lirInstruction instanceof StandardOp.JumpOp;
+ }
+
+ /**
+ * Check if stateValues have null values, if so the state is not complete. This happens
+ * because iterating over certain values in LIRFrameState is ignored because they are a
+ * concrete stack slot and not a virtual one (StackLockValue).
+ *
+ * @return true, if complete - non-null values after allocation
+ */
+ public boolean hasCompleteState() {
+ for (int i = 0; i < stateValues.count; i++) {
+ if (stateValues.curr[i] == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ var opString = lirInstruction.name();
+ if (opString.isEmpty()) {
+ opString = "OP";
+ }
+
+ StringBuilder output = new StringBuilder();
+ if (this.dests.count > 0) {
+ output.append(this.dests);
+ output.append(" = ");
+ }
+
+ output.append(opString.toUpperCase(Locale.ROOT));
+ if (this.uses.count > 0 || (this.alive.count > 0 || this.temp.count > 0)) {
+ output.append(" ");
+ output.append(this.uses);
+ }
+
+ if (this.alive.count > 0 || this.temp.count > 0) {
+ output.append(" alive: ");
+ output.append(this.alive);
+ }
+
+ if (this.temp.count > 0) {
+ output.append(" temp: ");
+ output.append(this.temp);
+ }
+
+ return output.toString();
+ }
+ }
+
+ /**
+ * A move between to concrete locations inserted by the register allocator.
+ */
+ public static class LocationMove extends Base {
+ public RAValue from;
+ public RAValue to;
+ public boolean validateRegisters;
+
+ public LocationMove(LIRInstruction instr, Value from, Value to) {
+ this(instr, from, to, true);
+ }
+
+ /**
+ * Create a location move instance.
+ *
+ * @param instr Underlying LIR instruction
+ * @param from Source value
+ * @param to Destination value
+ * @param validateRegisters If checking, if register can be allocated to, should be done.
+ * This is false for moves that are not inserted or changed by the allocator.
+ */
+ public LocationMove(LIRInstruction instr, Value from, Value to, boolean validateRegisters) {
+ super(instr);
+ this.from = RAValue.create(from);
+ this.to = RAValue.create(to);
+ this.validateRegisters = validateRegisters;
+ }
+
+ @Override
+ public String toString() {
+ return to.getValue().toString() + " = MOVE " + from.getValue().toString();
+ }
+ }
+
+ /**
+ * Move between two registers.
+ */
+ public static class RegMove extends LocationMove {
+ public RAValue from;
+ public RAValue to;
+
+ public RegMove(LIRInstruction instr, RegisterValue from, RegisterValue to) {
+ super(instr, from, to);
+ this.from = RAValue.create(from);
+ this.to = RAValue.create(to);
+ }
+
+ @Override
+ public String toString() {
+ return to.getValue().toString() + " = REGMOVE " + from.getValue().toString();
+ }
+ }
+
+ /**
+ * Move between two stack slots (virtual or not).
+ */
+ public static class StackMove extends LocationMove {
+ public RAValue from;
+ public RAValue to;
+
+ public RAValue backupSlot;
+
+ public StackMove(LIRInstruction instr, Value from, Value to) {
+ this(instr, from, to, null);
+ }
+
+ public StackMove(LIRInstruction instr, Value from, Value to, Value backupSlot) {
+ super(instr, from, to);
+
+ assert from instanceof StackSlot || from instanceof VirtualStackSlot : "StackMove needs to receive instanceof StackSlot or VirtualStackSlot";
+ assert to instanceof StackSlot || to instanceof VirtualStackSlot : "StackMove needs to receive instanceof StackSlot or VirtualStackSlot";
+
+ this.from = RAValue.create(from);
+ this.to = RAValue.create(to);
+ this.backupSlot = RAValue.create(backupSlot);
+ }
+
+ @Override
+ public String toString() {
+ return to.getValue().toString() + " = STACKMOVE " + from.getValue().toString();
+ }
+ }
+
+ /**
+ * Reload a symbol from a stack slot to a register.
+ */
+ public static class Reload extends LocationMove {
+ public RAValue from;
+ public RAValue to;
+
+ public Reload(LIRInstruction instr, RegisterValue to, Value from) {
+ super(instr, from, to);
+ this.from = RAValue.create(from);
+ this.to = RAValue.create(to);
+ }
+
+ @Override
+ public String toString() {
+ return to.getValue().toString() + " = RELOAD " + from.getValue().toString();
+ }
+ }
+
+ /**
+ * Spill symbol from register to a spill slot.
+ */
+ public static class Spill extends LocationMove {
+ public RAValue from;
+ public RAValue to;
+
+ public Spill(LIRInstruction instr, Value to, RegisterValue from) {
+ super(instr, from, to);
+ this.from = RAValue.create(from);
+ this.to = RAValue.create(to);
+ }
+
+ @Override
+ public String toString() {
+ return to.getValue().toString() + " = SPILL " + from.getValue().toString();
+ }
+ }
+
+ /**
+ * Move a value or variable to a concrete location, inserted by allocator (materialization) or
+ * not (virtual move).
+ */
+ public static class ValueMove extends Base {
+ /**
+ * Constant generated by materialization or variable from virtual/speculative move.
+ */
+ public final RAValue constant;
+ protected RAValue location;
+ public final boolean validateRegisters;
+
+ public ValueMove(LIRInstruction instr, ConstantValue constant, Value location) {
+ this(instr, constant, location, true);
+ }
+
+ public ValueMove(LIRInstruction instr, ConstantValue constant, Value location, boolean validateRegisters) {
+ super(instr);
+ this.constant = RAValue.create(constant);
+ this.location = RAValue.create(location);
+ this.validateRegisters = validateRegisters;
+ }
+
+ @Override
+ public String toString() {
+ return getLocation().getValue().toString() + " = VALUEMOVE " + constant.getValue().toString();
+ }
+
+ public void setLocation(RAValue location) {
+ this.location = location;
+ }
+
+ /**
+ * Where variable (or constant) is being stored.
+ */
+ public RAValue getLocation() {
+ return location;
+ }
+ }
+
+ /**
+ * Move between two variables that has been coalesced into one location, we keep this
+ * information to replace the destination variable with the source variable, it is never part of
+ * the verifier IR, so it is not subclass of {@link Base}.
+ */
+ public static class CoalescedMove {
+ public final LIRInstruction lirInstruction;
+ public final RAVariable srcVariable;
+ public final RAVariable dstVariable;
+
+ public CoalescedMove(LIRInstruction lirInstruction, Value dstVariable, Value srcVariable) {
+ this.lirInstruction = lirInstruction;
+ this.srcVariable = RAValue.create(LIRValueUtil.asVariable(srcVariable)).asVariable();
+ this.dstVariable = RAValue.create(dstVariable).asVariable();
+ }
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RAVRegister.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RAVRegister.java
new file mode 100644
index 000000000000..914f78f9a40d
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RAVRegister.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier;
+
+import jdk.vm.ci.code.Register;
+import jdk.vm.ci.code.RegisterValue;
+
+/**
+ * Wrap around {@link RegisterValue} to only index by the id of the {@link Register} it holds.
+ */
+public class RAVRegister extends RAValue {
+ protected final RegisterValue registerValue;
+
+ protected RAVRegister(RegisterValue registerValue) {
+ super(registerValue);
+
+ this.registerValue = registerValue;
+ }
+
+ public RegisterValue getRegisterValue() {
+ return registerValue;
+ }
+
+ public Register getRegister() {
+ return registerValue.getRegister();
+ }
+
+ @Override
+ public RAVRegister asRegister() {
+ return this;
+ }
+
+ @Override
+ public boolean isRegister() {
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.registerValue.getRegister().hashCode();
+ }
+
+ /**
+ * Equal RegisterValue on it's Register, not Register and kind, otherwise the same as a Value.
+ *
+ * @param other The reference object with which to compare.
+ * @return Are said values equal?
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof RAVRegister otherReg) {
+ return this.registerValue.getRegister().equals(otherReg.registerValue.getRegister());
+ }
+
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return this.registerValue.getRegister().toString();
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RAValue.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RAValue.java
new file mode 100644
index 000000000000..c324b8b63c57
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RAValue.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier;
+
+import jdk.graal.compiler.core.common.LIRKind;
+import jdk.graal.compiler.core.common.LIRKindWithCast;
+import jdk.graal.compiler.debug.GraalError;
+import jdk.graal.compiler.lir.ConstantValue;
+import jdk.graal.compiler.lir.LIRValueUtil;
+import jdk.graal.compiler.lir.Variable;
+import jdk.vm.ci.code.ValueUtil;
+import jdk.vm.ci.meta.Value;
+import jdk.vm.ci.meta.ValueKind;
+
+/**
+ * Wrapper around Value to change how indexing in data structures like {@link java.util.Map} or
+ * {@link java.util.Set} is done.
+ *
+ *
+ * Values are indexed without their {@link LIRKind kind} associated with them, this is necessary for
+ * {@link AllocationStateMap} because locations can change kinds and still be associated with one
+ * key/value pair in the map.
+ *
+ *
+ *
+ * Some value types like {@link RAVConstant constants}, {@link RAVariable variables},
+ * {@link RAVRegister registers} have their own subclass, to use them as types for keys and values
+ * in collections.
+ *
+ */
+public class RAValue {
+ /**
+ * Create a new RAValue instance from {@link Value}.
+ *
+ * @param value Value we are wrapping
+ * @return Instance of RAValue
+ */
+ public static RAValue create(Value value) {
+ if (LIRValueUtil.isVariable(value)) {
+ return new RAVariable(LIRValueUtil.asVariable(value));
+ }
+
+ if (ValueUtil.isRegister(value)) {
+ return new RAVRegister(ValueUtil.asRegisterValue(value));
+ }
+
+ if (LIRValueUtil.isConstantValue(value)) {
+ return new RAVConstant(LIRValueUtil.asConstantValue(value), true);
+ }
+
+ return new RAValue(value);
+ }
+
+ protected static boolean kindsEqual(RAValue orig, RAValue location) {
+ var origKind = orig.getLIRKind();
+ var currKind = location.getLIRKind();
+ if (currKind instanceof LIRKindWithCast castKind) {
+ currKind = (LIRKind) castKind.getActualKind();
+ }
+
+ return origKind.equals(currKind);
+ }
+
+ public static RAValue cast(RAValue symbol, RAValue kindSrc) {
+ RAValue castOrig;
+ if (symbol.isVariable()) {
+ castOrig = RAValue.create(new Variable(kindSrc.getLIRKind(), symbol.asVariable().getVariable().index));
+ } else if (symbol.isConstant()) {
+ castOrig = RAValue.create(new ConstantValue(kindSrc.getLIRKind(), symbol.asConstant().getConstant()));
+ } else {
+ throw new GraalError("should not reach here: cannot cast orig " + symbol);
+ }
+ return castOrig;
+ }
+
+ protected final Value value;
+
+ protected RAValue(Value value) {
+ this.value = value;
+ }
+
+ public Value getValue() {
+ return this.value;
+ }
+
+ public boolean isIllegal() {
+ return Value.ILLEGAL.equals(value);
+ }
+
+ public RAVariable asVariable() {
+ return (RAVariable) this;
+ }
+
+ public boolean isVariable() {
+ return false;
+ }
+
+ public boolean isConstant() {
+ return false;
+ }
+
+ public RAVConstant asConstant() {
+ return (RAVConstant) this;
+ }
+
+ public boolean isRegister() {
+ return false;
+ }
+
+ public RAVRegister asRegister() {
+ return (RAVRegister) this;
+ }
+
+ public LIRKind getLIRKind() {
+ if (isIllegal() || value.getValueKind().equals(ValueKind.Illegal)) {
+ return LIRKind.Illegal;
+ }
+
+ return value.getValueKind(LIRKind.class);
+ }
+
+ public boolean isLocation() {
+ return isRegister() || isStackSlot();
+ }
+
+ public boolean isStackSlot() {
+ return LIRValueUtil.isStackSlotValue(value);
+ }
+
+ @Override
+ public int hashCode() {
+ if (LIRValueUtil.isVirtualStackSlot(this.value)) {
+ return LIRValueUtil.asVirtualStackSlot(this.value).getId();
+ }
+
+ if (ValueUtil.isStackSlot(this.value)) {
+ var stackSlot = ValueUtil.asStackSlot(this.value);
+ return stackSlot.getRawOffset();
+ }
+
+ return this.value.hashCode();
+ }
+
+ /**
+ * Are two {@link RAValue values} equal? - check for offset for
+ * {@link jdk.vm.ci.code.StackSlot}, check for id for
+ * {@link jdk.graal.compiler.lir.VirtualStackSlot}, otherwise default to normal equals on
+ * {@link Value}
+ *
+ * @param other The reference object with which to compare.
+ * @return Are said values equal?
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof RAValue otherValueWrap) {
+ if (LIRValueUtil.isVirtualStackSlot(this.value) && LIRValueUtil.isVirtualStackSlot(otherValueWrap.value)) {
+ return LIRValueUtil.asVirtualStackSlot(this.value).getId() == LIRValueUtil.asVirtualStackSlot(otherValueWrap.value).getId();
+ }
+
+ if (ValueUtil.isStackSlot(this.value) && ValueUtil.isStackSlot(otherValueWrap.value)) {
+ var ourSlot = ValueUtil.asStackSlot(this.value);
+ var otherSlot = ValueUtil.asStackSlot(otherValueWrap.value);
+
+ return ourSlot.getRawOffset() == otherSlot.getRawOffset() && ourSlot.getRawAddFrameSize() == otherSlot.getRawAddFrameSize();
+ }
+
+ return this.value.equals(otherValueWrap.value);
+ }
+
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ if (LIRValueUtil.isVirtualStackSlot(this.value)) {
+ return "vstack:" + LIRValueUtil.asVirtualStackSlot(this.value).getId();
+ }
+
+ if (ValueUtil.isStackSlot(this.value)) {
+ return "stack:" + ValueUtil.asStackSlot(this.value).getRawOffset();
+ }
+
+ return value.toString();
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RAVariable.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RAVariable.java
new file mode 100644
index 000000000000..5c5b5fb00cfb
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RAVariable.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier;
+
+import jdk.graal.compiler.lir.Variable;
+
+/**
+ * Wrapper around {@link Variable} to change how indexing in data structures like
+ * {@link java.util.Map} or {@link java.util.Set} is done.
+ *
+ *
+ * We index only by the {@link Variable} index instead of including the kind as well.
+ *
+ */
+public class RAVariable extends RAValue {
+ protected final Variable variable;
+
+ protected RAVariable(Variable variable) {
+ super(variable);
+ this.variable = variable;
+ }
+
+ @Override
+ public RAVariable asVariable() {
+ return this;
+ }
+
+ @Override
+ public boolean isVariable() {
+ return true;
+ }
+
+ public Variable getVariable() {
+ return variable;
+ }
+
+ @Override
+ public int hashCode() {
+ return variable.index;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof RAVariable raVariable) {
+ return variable.index == raVariable.variable.index;
+ }
+
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "v" + variable.index;
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/ReferencesBuilder.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/ReferencesBuilder.java
new file mode 100644
index 000000000000..2396f6b77b10
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/ReferencesBuilder.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier;
+
+import static jdk.vm.ci.code.ValueUtil.asRegister;
+import static jdk.vm.ci.code.ValueUtil.isRegister;
+import static jdk.vm.ci.code.ValueUtil.isStackSlot;
+
+import jdk.graal.compiler.core.common.LIRKind;
+import jdk.graal.compiler.lir.LIR;
+import jdk.graal.compiler.lir.LIRFrameState;
+import jdk.graal.compiler.lir.LIRInstruction;
+import jdk.graal.compiler.lir.dfa.LocationMarker;
+import jdk.graal.compiler.lir.framemap.FrameMap;
+import jdk.graal.compiler.lir.util.ValueSet;
+import jdk.graal.compiler.util.EconomicHashSet;
+import jdk.vm.ci.code.Register;
+import jdk.vm.ci.code.RegisterAttributes;
+import jdk.vm.ci.meta.Value;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Build references list for operations that can be used to in-validate references that are not part
+ * of it to make sure GC-freed references are not used further.
+ *
+ *
+ * It uses the {@link LocationMarker} class that is used in the
+ * {@link jdk.graal.compiler.lir.phases.FinalCodeAnalysisPhase} where the actual set of references
+ * is built, we require this information earlier.
+ *
+ */
+public class ReferencesBuilder {
+ class ReferenceSet extends ValueSet {
+ protected Set references;
+
+ ReferenceSet() {
+ references = new EconomicHashSet<>();
+ }
+
+ ReferenceSet(ReferenceSet other) {
+ references = new EconomicHashSet<>(other.references);
+ }
+
+ @Override
+ public void put(Value v) {
+ if (v.getValueKind(LIRKind.class).isValue()) {
+ return;
+ }
+
+ references.add(RAValue.create(v));
+ }
+
+ @Override
+ public void remove(Value v) {
+ references.remove(RAValue.create(v));
+ }
+
+ @Override
+ public void putAll(ReferenceSet other) {
+ references.addAll(other.references);
+ }
+
+ @Override
+ public ReferenceSet copy() {
+ return new ReferenceSet(this);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ReferenceSet other) {
+ return other.references.equals(references);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ class ReferenceMarker extends LocationMarker {
+ private final List registerAttributes;
+ protected final Map preAllocMap;
+
+ protected ReferenceMarker(LIR lir, FrameMap frameMap, Map preAllocMap) {
+ super(lir, frameMap);
+ this.registerAttributes = frameMap.getRegisterConfig().getAttributesMap();
+ this.preAllocMap = preAllocMap;
+ }
+
+ @Override
+ protected ReferenceSet newLiveValueSet() {
+ return new ReferenceSet();
+ }
+
+ /**
+ * Only process values that are registers (that can contain references and are allocatable)
+ * and stack slots, where kind cannot be Illegal.
+ *
+ *
+ * Take from jdk.graal.compiler.lir.dfa.LocationMarkerPhase.Marker#shouldProcessValue
+ *
+ *
+ * @param operand Value to be processed
+ * @return If matches criteria for LocationMarker processing
+ */
+ @Override
+ protected boolean shouldProcessValue(Value operand) {
+ if (isRegister(operand)) {
+ Register reg = asRegister(operand);
+ if (!reg.mayContainReference() || !attributes(reg).isAllocatable()) {
+ // register that's not allocatable or not part of the reference map
+ return false;
+ }
+ } else if (!isStackSlot(operand)) {
+ // neither register nor stack slot
+ return false;
+ }
+
+ return !operand.getValueKind().equals(LIRKind.Illegal);
+ }
+
+ private RegisterAttributes attributes(Register reg) {
+ return registerAttributes.get(reg.number);
+ }
+
+ /**
+ * Set references to the operation if it can be found in the pre-allocation map.
+ *
+ * @param instruction LIR instruction that holds these references
+ * @param info LIR frame state
+ * @param values Set of references for this instruction
+ */
+ @Override
+ protected void processState(LIRInstruction instruction, LIRFrameState info, ReferenceSet values) {
+ if (!preAllocMap.containsKey(instruction)) {
+ return;
+ }
+
+ var op = (RAVInstruction.Op) preAllocMap.get(instruction);
+ op.references = new EconomicHashSet<>(values.references); // Has to be a copy here
+ }
+ }
+
+ /**
+ * Build references for {@link RAVInstruction.Op intructions} to check GC roots.
+ *
+ * @param lir LIR
+ * @param frameMap Frame map necessary to build references
+ * @param preAllocMap Map of instructions before allocation
+ */
+ public void build(LIR lir, FrameMap frameMap, Map preAllocMap) {
+ new ReferenceMarker(lir, frameMap, preAllocMap).build();
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RegAllocVerifier.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RegAllocVerifier.java
new file mode 100644
index 000000000000..8c089705b842
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RegAllocVerifier.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier;
+
+import jdk.graal.compiler.core.common.alloc.RegisterAllocationConfig;
+import jdk.graal.compiler.core.common.cfg.BasicBlock;
+import jdk.graal.compiler.core.common.cfg.BlockMap;
+import jdk.graal.compiler.lir.LIR;
+import jdk.graal.compiler.lir.alloc.verifier.exceptions.RAVException;
+import jdk.graal.compiler.lir.alloc.verifier.exceptions.RAVFailedVerificationException;
+import jdk.graal.compiler.lir.dfa.UniqueWorkList;
+
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class encapsulating the whole Register Allocation Verification. Maintaining entry states for
+ * blocks, resolving label variable locations, and checking the validity of every location to
+ * variable correspondence.
+ */
+public class RegAllocVerifier {
+ /**
+ * Verifier IR that abstracts LIR instructions and marks moves inserted by the allocator.
+ */
+ protected final BlockMap> blockInstructions;
+
+ /**
+ * State of the block on entry, calculated from its predecessors.
+ */
+ protected final BlockMap blockEntryStates;
+
+ /**
+ * LIR necessary for to access the program graph.
+ */
+ protected final LIR lir;
+
+ /**
+ * Register Allocator config used for validating if the allocator uses a valid register.
+ */
+ protected final RegisterAllocationConfig registerAllocationConfig;
+
+ /**
+ * Resolves locations for label variables by finding their first usage and walking back to the
+ * defining label.
+ */
+ protected final FromUsageResolverGlobal fromUsageResolverGlobal;
+
+ /**
+ * Track callee saved values from start block to exit blocks.
+ */
+ protected final CalleeSaveMap calleeSaveMap;
+
+ public RegAllocVerifier(LIR lir, BlockMap> blockInstructions, RegisterAllocationConfig registerAllocationConfig) {
+ this.lir = lir;
+ this.registerAllocationConfig = registerAllocationConfig;
+
+ var cfg = lir.getControlFlowGraph();
+ this.blockInstructions = blockInstructions;
+ this.blockEntryStates = new BlockMap<>(cfg);
+
+ this.fromUsageResolverGlobal = new FromUsageResolverGlobal(lir, blockInstructions);
+
+ this.calleeSaveMap = new CalleeSaveMap(registerAllocationConfig.getRegisterConfig());
+ }
+
+ /**
+ * For every block, we need to calculate its entry state, which is a combination of states of
+ * blocks that are its predecessors; we get after reached a fixed point state, where no entry
+ * state is changed.
+ *
+ *
+ * This is necessary to verify instruction inputs correctly.
+ *
+ */
+ public void computeEntryStates() {
+ var worklist = new UniqueWorkList(lir.getBlocks().length);
+
+ var startBlock = this.lir.getControlFlowGraph().getStartBlock();
+ var startBlockState = createNewBlockState(startBlock);
+ this.blockEntryStates.put(startBlock, startBlockState);
+
+ worklist.add(startBlock);
+ while (!worklist.isEmpty()) {
+ var block = worklist.poll();
+ if (block.getSuccessorCount() == 0) {
+ continue; // No entry state to compute for successors
+ }
+
+ var instructions = this.blockInstructions.get(block);
+
+ // Create new entry state for successor blocks out of current block state
+ var state = new BlockVerifierState(block, this.blockEntryStates.get(block));
+ for (var instr : instructions) {
+ state.update(instr);
+ }
+
+ for (int i = 0; i < block.getSuccessorCount(); i++) {
+ var succ = block.getSuccessorAt(i);
+
+ BlockVerifierState succState;
+ if (this.blockEntryStates.get(succ) == null) {
+ succState = new BlockVerifierState(succ, state);
+ } else {
+ succState = this.blockEntryStates.get(succ);
+ if (!succState.meetWith(state)) {
+ continue;
+ }
+ }
+
+ this.blockEntryStates.put(succ, succState);
+
+ /*
+ * Remove block from the worklist to delay processing this can reduce the number of
+ * times that merge block is processed due to changes.
+ */
+ worklist.remove(succ);
+ worklist.add(succ);
+ }
+ }
+ }
+
+ protected BlockVerifierState createNewBlockState(BasicBlock> block) {
+ return new BlockVerifierState(block, registerAllocationConfig, calleeSaveMap);
+ }
+
+ /**
+ * By using the entry states calculated in a step beforehand, we check the input of every
+ * instruction to see that it matches symbols before allocation; after wards we update the state
+ * so the next instruction has the correct state at said instruction input.
+ */
+ public void verifyInstructionInputs() {
+ for (var blockId : this.lir.getBlocks()) {
+ var block = this.lir.getBlockById(blockId);
+ var state = new BlockVerifierState(block, this.blockEntryStates.get(block));
+ var instructions = this.blockInstructions.get(block);
+
+ for (var instr : instructions) {
+ state.check(instr);
+ state.update(instr);
+ }
+
+ if (block.getSuccessorCount() == 0) {
+ state.checkCalleeSavedRegisters();
+ }
+ }
+ }
+
+ /**
+ * Verify every instruction and collect every exception that has occurred.
+ */
+ public void verifyInstructionsAndCollectErrors() {
+ List exceptions = new ArrayList<>();
+ for (var blockId : this.lir.getBlocks()) {
+ var block = this.lir.getBlockById(blockId);
+ var state = new BlockVerifierState(block, this.blockEntryStates.get(block));
+ var instructions = this.blockInstructions.get(block);
+
+ for (var instr : instructions) {
+ try {
+ state.check(instr);
+ state.update(instr);
+ } catch (RAVException e) {
+ exceptions.add(e);
+ }
+ }
+
+ try {
+ if (block.getSuccessorCount() == 0) {
+ state.checkCalleeSavedRegisters();
+ }
+ } catch (RAVException e) {
+ exceptions.add(e);
+ }
+ }
+
+ if (!exceptions.isEmpty()) {
+ throw new RAVFailedVerificationException(exceptions);
+ }
+ }
+
+ /**
+ * Run verification based on verifier IR created in phase beforehand, resolving stripped label
+ * variable locations back, calculating entry state for every block so that at the end we can
+ * verify inputs of instructions match variables present before allocation.
+ *
+ *
+ * The issues we are looking to catch are mostly about making sure that the order of spills,
+ * reloads, and moves is correct and that used location after stores the symbol that is supposed
+ * to be there.
+ *
+ *
+ *
+ * We also make sure that kinds are still matching, operand flags aren't violated, alive
+ * location not being used as temp or output of the same instruction.
+ *
+ */
+ @SuppressWarnings("try")
+ public void run(boolean failOnFirst) {
+ this.computeEntryStates();
+
+ if (failOnFirst) {
+ this.verifyInstructionInputs();
+ } else {
+ this.verifyInstructionsAndCollectErrors();
+ }
+ }
+
+ public VerifierPrinter getPrinter(OutputStream out) {
+ return new VerifierPrinter(out, this);
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RegAllocVerifierPhase.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RegAllocVerifierPhase.java
new file mode 100644
index 000000000000..c753cae2d2a6
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/RegAllocVerifierPhase.java
@@ -0,0 +1,656 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier;
+
+import jdk.graal.compiler.core.common.LIRKind;
+import jdk.graal.compiler.core.common.alloc.RegisterAllocationConfig;
+import jdk.graal.compiler.core.common.cfg.BasicBlock;
+import jdk.graal.compiler.core.common.cfg.BlockMap;
+import jdk.graal.compiler.debug.DebugCloseable;
+import jdk.graal.compiler.debug.DebugContext;
+import jdk.graal.compiler.debug.TimerKey;
+import jdk.graal.compiler.lir.ConstantValue;
+import jdk.graal.compiler.lir.LIR;
+import jdk.graal.compiler.lir.LIRFrameState;
+import jdk.graal.compiler.lir.LIRInstruction;
+import jdk.graal.compiler.lir.LIRValueUtil;
+import jdk.graal.compiler.lir.StandardOp;
+import jdk.graal.compiler.lir.StateProcedure;
+import jdk.graal.compiler.lir.Variable;
+import jdk.graal.compiler.lir.alloc.RegisterAllocationPhase;
+import jdk.graal.compiler.lir.alloc.verifier.exceptions.RAVException;
+import jdk.graal.compiler.lir.alloc.verifier.exceptions.RAVFailedVerificationException;
+import jdk.graal.compiler.lir.amd64.AMD64Move;
+import jdk.graal.compiler.lir.gen.LIRGenerationResult;
+import jdk.graal.compiler.lir.phases.LIRPhase;
+import jdk.graal.compiler.options.Option;
+import jdk.graal.compiler.options.OptionKey;
+import jdk.graal.compiler.options.OptionType;
+import jdk.graal.compiler.util.EconomicHashMap;
+import jdk.graal.compiler.util.EconomicHashSet;
+import jdk.vm.ci.code.BytecodeFrame;
+import jdk.vm.ci.code.TargetDescription;
+import jdk.vm.ci.code.ValueUtil;
+import jdk.vm.ci.meta.Value;
+import org.graalvm.collections.Equivalence;
+
+import java.io.FileNotFoundException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Verification phase for Register Allocation, wraps around the actual allocator and validates that
+ * the order of spills, reloads, and moves is correct and that variables before allocation are
+ * actually stored in current locations chosen by the allocator.
+ *
+ *
+ * Needs to extend the RegisterAllocationPhase to not throw an exception.
+ *
+ */
+@SuppressWarnings("try")
+public class RegAllocVerifierPhase extends RegisterAllocationPhase {
+ public static class Options {
+ // @formatter:off
+ @Option(help = "Verify that register allocation is indeed, correct", type = OptionType.Debug)
+ public static final OptionKey EnableRAVerifier = new OptionKey<>(true);
+
+ @Option(help = "Verify output of stack allocator with register allocator", type = OptionType.Debug)
+ public static final OptionKey VerifyStackAllocator = new OptionKey<>(true);
+
+ @Option(help = "Collect reference map information to verify", type = OptionType.Debug)
+ public static final OptionKey CollectReferences = new OptionKey<>(true);
+
+ @Option(help = "Fail on first verification failure", type = OptionType.Debug)
+ public static final OptionKey RAVFailOnFirst = new OptionKey<>(true);
+ // @formatter:on
+ }
+
+ /**
+ * Register allocator we are verifying output of.
+ */
+ protected RegisterAllocationPhase allocator;
+
+ /**
+ * Stack allocator, if not null, being verified simultaneously with reg allocator.
+ */
+ protected LIRPhase stackSlotAllocator;
+
+ private static final TimerKey PreallocTimer = DebugContext.timer("RAV_PreAlloc");
+ private static final TimerKey VerifierTimer = DebugContext.timer("RAV_Verification");
+ private static final TimerKey PhiResolverTimer = DebugContext.timer("RAV_PhiResolver");
+ private static final TimerKey AllocationTimer = DebugContext.timer("RAV_Allocation");
+
+ public RegAllocVerifierPhase(RegisterAllocationPhase allocator, LIRPhase stackSlotAllocator) {
+ this.allocator = allocator;
+ this.stackSlotAllocator = stackSlotAllocator;
+ }
+
+ @Override
+ public void setNeverSpillConstants(boolean neverSpillConstants) {
+ allocator.setNeverSpillConstants(neverSpillConstants);
+ }
+
+ @Override
+ public boolean getNeverSpillConstants() {
+ return allocator.getNeverSpillConstants();
+ }
+
+ /**
+ * Get the allocator that is being verified.
+ *
+ * @return Register allocator
+ */
+ public RegisterAllocationPhase getAllocator() {
+ return allocator;
+ }
+
+ /**
+ * Set allocator to verify, used by tests.
+ *
+ * @param allocator Register allocator
+ */
+ public void setAllocator(RegisterAllocationPhase allocator) {
+ this.allocator = allocator;
+ }
+
+ /**
+ * Get stack allocator being verified with register allocator if not null.
+ *
+ * @return Stack allocator or null
+ */
+ public LIRPhase getStackSlotAllocator() {
+ return stackSlotAllocator;
+ }
+
+ /**
+ * Set stack allocator, used by tests.
+ *
+ * @param stackSlotAllocator Stack allocator
+ */
+ public void setStackSlotAllocator(LIRPhase stackSlotAllocator) {
+ this.stackSlotAllocator = stackSlotAllocator;
+ }
+
+ @Override
+ protected void run(TargetDescription target, LIRGenerationResult lirGenRes, AllocationContext context) {
+ assert allocator != null : "No register allocator present for verification";
+
+ var lir = lirGenRes.getLIR();
+
+ Map preAllocMap;
+ try (DebugCloseable t = PreallocTimer.start(lir.getDebug())) {
+ preAllocMap = saveInstructionsPreAlloc(lir);
+ }
+
+ boolean verifyStackAlloc = Options.VerifyStackAllocator.getValue(lir.getOptions());
+
+ try (DebugCloseable t = AllocationTimer.start(lir.getDebug())) {
+ allocator.apply(target, lirGenRes, context);
+ if (stackSlotAllocator != null && verifyStackAlloc) {
+ stackSlotAllocator.apply(target, lirGenRes, context);
+
+ if (Options.CollectReferences.getValue(lir.getOptions())) {
+ // Frame map is only built after stack allocator has run
+ new ReferencesBuilder().build(lir, lirGenRes.getFrameMap(), preAllocMap);
+ }
+ }
+ }
+
+ verifyAllocation(lir, preAllocMap, context);
+
+ if (stackSlotAllocator != null && !verifyStackAlloc) {
+ stackSlotAllocator.apply(target, lirGenRes, context);
+ }
+ }
+
+ /**
+ * Which operation generated register as a symbol and the index of it, in the output array.
+ */
+ class OutValue {
+ int idx;
+ RAVInstruction.Op op;
+
+ OutValue(int idx, RAVInstruction.Op op) {
+ this.idx = idx;
+ this.op = op;
+ }
+ }
+
+ /**
+ * Save instruction before allocation to keep track of symbols used in instructions.
+ *
+ * @param lir LIR
+ * @return Map of LIRInstruction to RAVInstruction that keeps symbols
+ */
+ protected Map saveInstructionsPreAlloc(LIR lir) {
+ Map preallocMap = new EconomicHashMap<>(Equivalence.IDENTITY);
+ for (var blockId : lir.getBlocks()) {
+ BasicBlock> block = lir.getBlockById(blockId);
+ ArrayList instructions = lir.getLIRforBlock(block);
+
+ Map inputMap = new EconomicHashMap<>();
+ Map outputMap = new EconomicHashMap<>();
+
+ RAVInstruction.Base previousInstr = null;
+ for (var instruction : instructions) {
+ boolean outputSpeculative = false;
+ boolean inputSpeculative = false;
+ if (instruction.isValueMoveOp()) {
+ var valueMov = StandardOp.ValueMoveOp.asValueMoveOp(instruction);
+ var input = valueMov.getInput();
+ var result = valueMov.getResult();
+
+ if (LIRValueUtil.isVariable(result) && isLocation(input)) {
+ /*
+ * Speculative move that outputs a new variable from a concrete location. We
+ * assign the variable to the instruction that uses the same register as
+ * output (with no symbol). For example, [rsi] = LABEL followed by v0 = MOVE
+ * rsi, will get mapped to [v0 -> rsi] = LABEL, internally.
+ */
+ var locValue = RAValue.create(input);
+ var outputValue = outputMap.get(locValue);
+ if (outputValue != null) {
+ outputValue.op.dests.orig[outputValue.idx] = RAValue.create(result);
+ outputMap.remove(locValue);
+ continue;
+ }
+
+ outputSpeculative = true;
+ }
+
+ if (LIRValueUtil.isVariable(input)) {
+ /*
+ * Speculative move that has an existing variable as its input, output can
+ * be either a variable or a concrete location.
+ *
+ * If a concrete location is input, then it can be used to assign a symbol
+ * to an instruction that has none for this register - typically function
+ * calls.
+ *
+ * If it is a variable to variable move, then we save it, in case it was
+ * coalesced.
+ *
+ * For example, rsi = MOVE v1, followed by CALL [rsi], will get mapped to
+ * CALL [rsi -> v1].
+ */
+ inputSpeculative = true;
+
+ Variable variable = LIRValueUtil.asVariable(input);
+ if (isLocation(result)) {
+ var regKind = result.getValueKind();
+ var varKind = input.getValueKind();
+ if (!regKind.equals(varKind)) {
+ variable = new Variable(regKind, variable.index);
+ }
+
+ // What if type cast information missing?
+ inputMap.put(RAValue.create(result), RAValue.create(variable));
+ } else {
+ var virtualMove = new RAVInstruction.CoalescedMove(instruction, result, variable);
+
+ assert previousInstr != null;
+ previousInstr.addSpeculativeMove(virtualMove);
+ }
+ }
+ }
+
+ if (instruction.isLoadConstantOp()) {
+ var loadConstOp = StandardOp.LoadConstantOp.asLoadConstantOp(instruction);
+ var location = RAValue.create(loadConstOp.getResult());
+ if (location.isLocation()) {
+ // Speculative input move that sets variable to concrete location.
+ var constant = new ConstantValue(loadConstOp.getResult().getValueKind(LIRKind.class), loadConstOp.getConstant());
+ inputMap.put(location, RAValue.create(constant));
+ }
+ }
+
+ var op = new RAVInstruction.Op(instruction);
+
+ instruction.forEachInput(op.uses.copyOriginalProc);
+ instruction.forEachOutput(op.dests.copyOriginalProc);
+ instruction.forEachTemp(op.temp.copyOriginalProc);
+ instruction.forEachAlive(op.alive.copyOriginalProc);
+ instruction.forEachState(op.stateValues.copyOriginalProc);
+ instruction.forEachState(new StateProcedure() {
+ @Override
+ public void doState(LIRFrameState state) {
+ if (state.topFrame == null) {
+ return;
+ }
+
+ BytecodeFrame frame = state.topFrame;
+ while (frame != null) {
+ var kinds = frame.getSlotKinds();
+ if (kinds.length != frame.numLocals + frame.numStack) {
+ frame = frame.caller();
+ continue;
+ }
+
+ var values = frame.values.clone();
+ op.bcFrames.add(new RAVInstruction.StateValuePair(values, kinds));
+ frame = frame.caller();
+ }
+ }
+ });
+
+ changeOriginalInputToVariable(inputMap, op.uses, outputSpeculative);
+ changeOriginalInputToVariable(inputMap, op.alive, outputSpeculative);
+
+ preallocMap.put(instruction, op);
+
+ if (!inputSpeculative) {
+ for (int i = 0; i < op.dests.count; i++) {
+ var orig = op.dests.orig[i];
+ if (orig.isLocation()) {
+ outputMap.put(orig, new OutValue(i, op));
+ }
+ }
+
+ previousInstr = op;
+ }
+ }
+ }
+
+ return preallocMap;
+ }
+
+ /**
+ * Changes input of an instruction that is a concrete location (before allocation) to
+ * variable/constant from input mapping, that is created by speculative moves before this
+ * instruction.
+ *
+ * @param inputMap Map of location to variable assigned from speculative input move
+ * @param values Input values
+ * @param outputSpeculative If this input is from a speculative instruction that creates a new
+ * variable
+ */
+ protected void changeOriginalInputToVariable(Map inputMap, RAVInstruction.ValueArrayPair values, boolean outputSpeculative) {
+ for (int i = 0; i < values.count; i++) {
+ var orig = values.orig[i];
+ if (!orig.isLocation()) {
+ continue;
+ }
+
+ var inputVar = inputMap.get(orig);
+ if (inputVar == null) {
+ continue;
+ }
+
+ values.orig[i] = RAValue.cast(inputVar, values.orig[i]);
+
+ if (!outputSpeculative) {
+ // Do not remove from input map if this operation could be removed.
+ // This value can be re-used.
+ inputMap.remove(orig);
+ }
+ }
+ }
+
+ /**
+ * Normalizes the values in this input array pair to remove any variables that can be
+ * substituted for constants or variables that are aliased by different ones.
+ *
+ *
+ * We do this to make the internal verifier IR more consistent because sometimes a constant
+ * value can be used as an input, while at other times it's substituted behind a variable, which
+ * can also re-materialize later.
+ *
+ *
+ *
+ * As for variable aliasing, sometimes a move is coalesced but the register allocator, but the
+ * mentioned of the alias variable still remain; we make sure to remove them.
+ *
+ *
+ * @param values Input array pair
+ * @param synonymMap Variable synonym map, used to find the first concrete variable.
+ * @param constantMap Constant map, map variables to their respective constant values.
+ */
+ protected void normalizeValues(RAVInstruction.ValueArrayPair values, VariableSynonymMap synonymMap, Map constantMap) {
+ for (int i = 0; i < values.count; i++) {
+ var value = values.orig[i];
+ if (!value.isVariable()) {
+ continue;
+ }
+
+ RAValue substitute;
+ var variable = value.asVariable();
+ if (constantMap.containsKey(variable)) {
+ substitute = constantMap.get(variable);
+ } else if (synonymMap.isAliased(variable)) {
+ var synonym = synonymMap.find(variable);
+ substitute = constantMap.getOrDefault(synonym, synonym);
+ } else {
+ continue;
+ }
+
+ if (!values.orig[i].getLIRKind().equals(substitute.getLIRKind())) {
+ /*
+ * We cast the kind here, because we expect preceeding move to cast the value inside
+ * the state, for example, rax|QWORD = MOVE rax|QWORD[.] size: QWORD should cast the
+ * contents of rax to be QWORD, to be consistent with this, we need to change the
+ * kind here.
+ */
+ substitute = RAValue.cast(substitute, values.orig[i]);
+ }
+
+ values.orig[i] = substitute;
+ }
+ }
+
+ /**
+ * Use information before allocation to verify the output of allocator(s).
+ *
+ * @param lir LIR
+ * @param preallocMap Map of instructions before allocation
+ * @param context Allocation context
+ */
+ protected void verifyAllocation(LIR lir, Map preallocMap, AllocationContext context) {
+ var instructions = getVerifierInstructions(lir, preallocMap, context);
+ var phiResolver = new FromUsageResolverGlobal(lir, instructions);
+ var verifier = new RegAllocVerifier(lir, instructions, getRegisterAllocationConfig(context));
+
+ boolean failOnFirst = Options.RAVFailOnFirst.getValue(lir.getOptions());
+
+ try (DebugCloseable t = PhiResolverTimer.start(lir.getDebug())) {
+ phiResolver.resolvePhiFromUsage();
+ }
+
+ try (DebugCloseable t = VerifierTimer.start(lir.getDebug())) {
+ verifier.run(failOnFirst);
+ } catch (RAVException e) {
+ var debugCtx = lir.getDebug();
+
+ if (debugCtx.isDumpEnabled(DebugContext.VERBOSE_LEVEL)) {
+ var debugPath = debugCtx.getDumpPath(".rav.txt", false);
+
+ try (PrintStream output = new PrintStream(debugPath)) {
+ verifier.getPrinter(output).printIRWithException(e);
+ } catch (FileNotFoundException ignored) {
+ }
+
+ // Keep original message with class path prefix and add debug path info
+ // to the end so it's easier to access.
+ throw new RAVException(e + ", see debug file " + debugPath, e);
+ }
+
+ throw e;
+ } catch (RAVFailedVerificationException e) {
+ var debugCtx = lir.getDebug();
+
+ if (debugCtx.isDumpEnabled(DebugContext.VERBOSE_LEVEL)) {
+ var debugPath = debugCtx.getDumpPath(".rav.txt", false);
+
+ try (PrintStream output = new PrintStream(debugPath)) {
+ verifier.getPrinter(output).printIRWithMultiExceptions(e);
+ } catch (FileNotFoundException ignored) {
+ }
+ }
+
+ throw e;
+ }
+ }
+
+ /**
+ * Retrieve RegisterAllocationConfig from context, this function is here, so it can be
+ * overwritten by a test when it needs to change the config.
+ *
+ * @param context Current phase context
+ * @return Instance of RegisterAllocationConfig
+ */
+ protected RegisterAllocationConfig getRegisterAllocationConfig(AllocationContext context) {
+ return context.registerAllocationConfig;
+ }
+
+ /**
+ * Process instructions after allocation and create the Verifier IR. Using previously stored
+ * instructions from the PreAlloc phase.
+ *
+ * @param lir LIR
+ * @param preallocMap Pre-allocation map to keep track of virtual values
+ * @param ctx Context of the allocation, kept here so tests can access this value here
+ * @return Verifier IR
+ */
+ protected BlockMap> getVerifierInstructions(LIR lir, Map preallocMap, AllocationContext ctx) {
+ VariableSynonymMap synonymMap = new VariableSynonymMap();
+ Map constMap = new EconomicHashMap<>();
+
+ var presentInstructions = getPresentInstructionSet(lir);
+ BlockMap> blockInstructions = new BlockMap<>(lir.getControlFlowGraph());
+ for (var blockId : lir.getBlocks()) {
+ BasicBlock> block = lir.getBlockById(blockId);
+ var instructionList = new ArrayList();
+
+ ArrayList instructions = lir.getLIRforBlock(block);
+ for (var instruction : instructions) {
+ var rAVInstr = preallocMap.get(instruction);
+ if (rAVInstr == null) {
+ var movOp = this.getRAVMoveInstruction(instruction);
+ if (movOp != null) {
+ instructionList.add(movOp);
+ continue;
+ }
+
+ throw new UnknownInstructionError(instruction, block);
+ }
+
+ var opRAVInstr = (RAVInstruction.Op) rAVInstr;
+
+ instruction.forEachInput(opRAVInstr.uses.copyCurrentProc);
+ instruction.forEachOutput(opRAVInstr.dests.copyCurrentProc);
+ instruction.forEachTemp(opRAVInstr.temp.copyCurrentProc);
+ instruction.forEachAlive(opRAVInstr.alive.copyCurrentProc);
+ instruction.forEachState(opRAVInstr.stateValues.copyCurrentProc);
+ instruction.forEachState(new StateProcedure() {
+ @Override
+ public void doState(LIRFrameState state) {
+ if (state.topFrame == null) {
+ return;
+ }
+
+ int i = 0;
+ BytecodeFrame frame = state.topFrame;
+ while (frame != null) {
+ if (i >= opRAVInstr.bcFrames.size()) {
+ break;
+ }
+
+ opRAVInstr.bcFrames.get(i).setCurr(frame.values);
+ frame = frame.caller();
+ i++;
+ }
+ }
+ });
+
+ normalizeValues(opRAVInstr.uses, synonymMap, constMap);
+ normalizeValues(opRAVInstr.alive, synonymMap, constMap);
+
+ if (instruction.isLoadConstantOp()) {
+ var loadConstOp = StandardOp.LoadConstantOp.asLoadConstantOp(instruction);
+ var location = loadConstOp.getResult();
+
+ ConstantValue constant = new ConstantValue(location.getValueKind(LIRKind.class), loadConstOp.getConstant());
+
+ var orig = opRAVInstr.dests.orig[0];
+ if (orig.isVariable()) {
+ var variable = orig.asVariable();
+ constMap.put(variable, new RAVConstant(constant, loadConstOp.canRematerializeToStack()));
+ }
+
+ var curr = opRAVInstr.dests.curr[0];
+ boolean validateRegs = !orig.equals(curr); // Only validate actual changes
+ var valMove = new RAVInstruction.ValueMove(instruction, constant, location, validateRegs);
+
+ instructionList.add(valMove);
+ } else {
+ instructionList.add(opRAVInstr);
+ }
+
+ var speculativeMoves = opRAVInstr.getSpeculativeMoveList();
+ for (var move : speculativeMoves) {
+ if (presentInstructions.contains(move.lirInstruction)) {
+ continue;
+ }
+
+ synonymMap.addSynonym(move.srcVariable, move.dstVariable);
+ }
+ }
+
+ blockInstructions.put(block, instructionList);
+ }
+
+ return blockInstructions;
+ }
+
+ /**
+ * Iterate over every instruction after the allocation, save it to a set to see if speculative
+ * moves should be re-added or not and also track if a variable has been defined before.
+ *
+ * @param lir LIR
+ * @return Set of instructions present after allocation
+ */
+ protected Set getPresentInstructionSet(LIR lir) {
+ Set presentInstructions = new EconomicHashSet<>(Equivalence.IDENTITY);
+ for (var blockId : lir.getBlocks()) {
+ BasicBlock> block = lir.getBlockById(blockId);
+ ArrayList instructions = lir.getLIRforBlock(block);
+
+ presentInstructions.addAll(instructions);
+ }
+ return presentInstructions;
+ }
+
+ /**
+ * Create Register Verifier Instruction that was created by the {@link RegisterAllocationPhase
+ * register allocator}. Generally speaking, it's always a move instruction; other ones return
+ * null.
+ *
+ * @param instruction LIR Instruction newly created by {@link RegisterAllocationPhase register
+ * allocator}
+ * @return Spill, Reload, Move or null if instruction is not a move
+ */
+ protected RAVInstruction.Base getRAVMoveInstruction(LIRInstruction instruction) {
+ if (!instruction.isValueMoveOp()) {
+ if (instruction.isLoadConstantOp()) {
+ var constatLoad = StandardOp.LoadConstantOp.asLoadConstantOp(instruction);
+ var constant = constatLoad.getConstant();
+ var result = constatLoad.getResult(); // Concrete location
+
+ // Constant materialization result
+ return new RAVInstruction.ValueMove(instruction, new ConstantValue(result.getValueKind(), constant), result);
+ }
+
+ return null;
+ }
+ var valueMov = StandardOp.ValueMoveOp.asValueMoveOp(instruction);
+
+ var input = valueMov.getInput();
+ var result = valueMov.getResult();
+
+ if (LIRValueUtil.isStackSlotValue(input) && ValueUtil.isRegister(result)) {
+ return new RAVInstruction.Reload(instruction, ValueUtil.asRegisterValue(result), input);
+ } else if (LIRValueUtil.isStackSlotValue(result) && ValueUtil.isRegister(input)) {
+ return new RAVInstruction.Spill(instruction, result, ValueUtil.asRegisterValue(input));
+ } else if (ValueUtil.isRegister(input) && ValueUtil.isRegister(result)) {
+ return new RAVInstruction.RegMove(instruction, ValueUtil.asRegisterValue(input), ValueUtil.asRegisterValue(result));
+ } else if (LIRValueUtil.isStackSlotValue(input) && LIRValueUtil.isStackSlotValue(result)) {
+ if (valueMov instanceof AMD64Move.AMD64StackMove stackMove) {
+ // Cannot access the isScratchAlwaysZero to see if a backup slot is used.
+ // We use the backup slot to set the allocation state to unknown.
+ return new RAVInstruction.StackMove(instruction, input, result, stackMove.getBackupSlot());
+ }
+
+ return new RAVInstruction.StackMove(instruction, input, result);
+ }
+
+ return null;
+ }
+
+ protected boolean isLocation(Value value) {
+ return LIRValueUtil.isStackSlotValue(value) || ValueUtil.isRegister(value);
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/UnknownAllocationState.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/UnknownAllocationState.java
new file mode 100644
index 000000000000..ed4e635cce27
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/UnknownAllocationState.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier;
+
+import jdk.graal.compiler.core.common.cfg.BasicBlock;
+
+/**
+ * Default allocation state for all locations, nothing was yet inserted.
+ */
+public class UnknownAllocationState extends AllocationState {
+ /**
+ * Single instance used for all occurrences of {@link UnknownAllocationState unknown state}.
+ */
+ public static UnknownAllocationState INSTANCE = new UnknownAllocationState();
+
+ @Override
+ public boolean isUnknown() {
+ return true;
+ }
+
+ /**
+ * Meet state from a predecessor, if both are unknown then unknown is returned, otherwise
+ * {@link ConflictedAllocationState conflict} occurs.
+ *
+ * @param other The other state coming from a predecessor edge
+ * @return null, if both states are Unknown, otherwise a new {@link ConflictedAllocationState}
+ */
+ @Override
+ public AllocationState meet(AllocationState other, BasicBlock> otherBlock, BasicBlock> block) {
+ if (other.isUnknown()) {
+ return null;
+ }
+
+ if (other instanceof ConflictedAllocationState conflictedState) {
+ var newConfState = new ConflictedAllocationState(conflictedState.conflictedStates);
+ newConfState.addConflictedValue(ValueAllocationState.createUndefined(block));
+ return newConfState;
+ }
+
+ return new ConflictedAllocationState((ValueAllocationState) other, ValueAllocationState.createUndefined(block));
+ }
+
+ @Override
+ public AllocationState clone() {
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == INSTANCE;
+ }
+
+ @Override
+ public int hashCode() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "Unknown";
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/UnknownInstructionError.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/UnknownInstructionError.java
new file mode 100644
index 000000000000..a52e242c23e0
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/UnknownInstructionError.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier;
+
+import jdk.graal.compiler.core.common.cfg.BasicBlock;
+import jdk.graal.compiler.lir.LIRInstruction;
+import jdk.graal.compiler.lir.StandardOp;
+
+/**
+ * Unknown instruction was found after the allocation, this usually means that it's a different
+ * instruction from a move, and we do not know how to handle it.
+ */
+@SuppressWarnings("serial")
+public class UnknownInstructionError extends RAVError {
+ public final LIRInstruction instruction;
+ public final BasicBlock> block;
+
+ public UnknownInstructionError(LIRInstruction instruction, BasicBlock> block) {
+ super(UnknownInstructionError.getErrorMessage(instruction, block));
+
+ this.instruction = instruction;
+ this.block = block;
+ }
+
+ static String getErrorMessage(LIRInstruction instruction, BasicBlock> block) {
+ if (instruction.isMoveOp()) {
+ return "Unknown MOVE " + instruction + " in " + block;
+ }
+
+ if (instruction instanceof StandardOp.LabelOp) {
+ return "Unknown LABEL " + instruction + " in " + block;
+ }
+
+ return "Unknown instruction for RAV " + instruction + " in " + block;
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/ValueAllocationState.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/ValueAllocationState.java
new file mode 100644
index 000000000000..d518b76ebf08
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/ValueAllocationState.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier;
+
+import jdk.graal.compiler.core.common.LIRKind;
+import jdk.graal.compiler.core.common.cfg.BasicBlock;
+import jdk.graal.compiler.debug.GraalError;
+import jdk.graal.compiler.lir.LIRValueUtil;
+import jdk.vm.ci.code.ValueUtil;
+import jdk.vm.ci.meta.Value;
+
+/**
+ * Allocation state holding a single, concrete {@link Value} (wrapped with {@link RAValue}), also
+ * accompanied by {@link RAVInstruction instruction} and {@link BasicBlock block} where it was
+ * created.
+ */
+public class ValueAllocationState extends AllocationState {
+ protected final RAValue value;
+ protected final RAVInstruction.Base source;
+ protected final BasicBlock> block;
+
+ /**
+ * Kind that this value was cast to, this is done by move instruction.
+ */
+ protected LIRKind castKind;
+
+ public ValueAllocationState(RAValue raValue, RAVInstruction.Base source, BasicBlock> block) {
+ var v = raValue.getValue();
+ if (ValueUtil.isRegister(v) || LIRValueUtil.isVariable(v) || LIRValueUtil.isConstantValue(v) || LIRValueUtil.isStackSlotValue(v) || Value.ILLEGAL.equals(v)) {
+ /*
+ * Making sure only allowed values are flowing to here. Although, the relevant ones are
+ * only the constant and the variable.
+ */
+ this.value = raValue;
+ this.source = source;
+ this.block = block;
+ } else {
+ throw GraalError.shouldNotReachHere("Invalid type of value used " + v);
+ }
+ }
+
+ public ValueAllocationState(ValueAllocationState other) {
+ this.value = other.getRAValue();
+ this.source = other.getSource();
+ this.block = other.getBlock();
+ this.castKind = other.castKind;
+ }
+
+ public ValueAllocationState(ValueAllocationState other, LIRKind castKind) {
+ this(other);
+ this.castKind = castKind;
+ }
+
+ public boolean isCast() {
+ return castKind != null;
+ }
+
+ /**
+ * Get the lir kind of this state, can be cast by a move.
+ *
+ * @return Value kind or cast kind
+ */
+ public LIRKind getKind() {
+ if (isCast()) {
+ return castKind;
+ }
+
+ return value.getLIRKind();
+ }
+
+ public boolean isReference() {
+ if (isCast()) {
+ return !castKind.isValue();
+ }
+
+ return !value.getLIRKind().isValue();
+ }
+
+ /**
+ * Create an illegal value allocation state, used as a substitute for
+ * {@link UnknownAllocationState unknown} when creating a {@link ConflictedAllocationState
+ * conflict}.
+ *
+ * @return instance of {@link ValueAllocationState} holding {@link Value#ILLEGAL}.
+ */
+ public static ValueAllocationState createUndefined(BasicBlock> block) {
+ return new ValueAllocationState(new RAValue(Value.ILLEGAL), null, block);
+ }
+
+ public Value getValue() {
+ return value.getValue();
+ }
+
+ public RAValue getRAValue() {
+ return value;
+ }
+
+ public RAVInstruction.Base getSource() {
+ return source;
+ }
+
+ public BasicBlock> getBlock() {
+ return block;
+ }
+
+ /**
+ * Meet a state from predecessor block, if it's {@link ValueAllocationState} and contents are
+ * equal, then same state is returned, otherwise a {@link ConflictedAllocationState conflict} is
+ * created between said states.
+ *
+ * @param other The other state coming from a predecessor edge
+ * @param otherBlock Where the other state is coming from
+ * @param currBlock Where the current state is coming from
+ * @return Newly allocated {@link ConflictedAllocationState} or null, if nothing changed.
+ */
+ @Override
+ public AllocationState meet(AllocationState other, BasicBlock> otherBlock, BasicBlock> currBlock) {
+ if (other.isUnknown()) {
+ // Unknown is coming from different predecessor where this location
+ // is undefined, meaning this value is not always accessible in the successor
+ // and thus conflict is created.
+ return new ConflictedAllocationState(createUndefined(otherBlock), this);
+ }
+
+ if (other.isConflicted()) {
+ var oldConfState = (ConflictedAllocationState) other;
+ var newConfState = new ConflictedAllocationState(oldConfState.conflictedStates);
+ newConfState.addConflictedValue(this);
+ return newConfState;
+ }
+
+ var otherValueAllocState = (ValueAllocationState) other;
+ if (!this.value.equals(otherValueAllocState.getRAValue())) {
+ // Does not take kind into account.
+ return new ConflictedAllocationState(this, otherValueAllocState);
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof ValueAllocationState otherVal) {
+ if (isUndefinedFromBlock()) {
+ return otherVal.isUndefinedFromBlock() && otherVal.getBlock().equals(this.block);
+ } else {
+ return otherVal.getRAValue().equals(this.value);
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public ValueAllocationState clone() {
+ return new ValueAllocationState(this);
+ }
+
+ @Override
+ public String toString() {
+ if (isUndefinedFromBlock()) {
+ return "Value {undefined from " + block + "}";
+ }
+
+ return "Value {" + this.value + "}";
+ }
+
+ @Override
+ public int hashCode() {
+ return this.value.hashCode();
+ }
+
+ public boolean isUndefinedFromBlock() {
+ return Value.ILLEGAL.equals(this.value.getValue()) && source == null;
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/VariableSynonymMap.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/VariableSynonymMap.java
new file mode 100644
index 000000000000..c08e11df5b79
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/VariableSynonymMap.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier;
+
+import jdk.graal.compiler.util.EconomicHashMap;
+
+import java.util.Map;
+
+/**
+ * Simplified disjoint union set implementation. We could on the fact that the destination variable
+ * is only defined once in variable to variable moves.
+ *
+ *
+ * The representative of every union is the original source variable not created by a coalesced
+ * move.
+ *
+ */
+public class VariableSynonymMap {
+ protected final Map parent;
+
+ protected VariableSynonymMap() {
+ parent = new EconomicHashMap<>();
+ }
+
+ /**
+ * Add a synonym to the disjoint union set.
+ *
+ *
+ * The destination variable is only defined once and is always linked to the root of the source
+ * variable.
+ *
+ *
+ *
+ * This method counts on the fact that the source variable was already defined by either a
+ * different coalesced move or by an existing instruction v2 = MOVE v1 and then
+ * followed by v1 = MOVE v3 will cause a failure.
+ *
+ *
+ * @param src Source variable
+ * @param dst Destination variable
+ */
+ protected void addSynonym(RAVariable src, RAVariable dst) {
+ assert !parent.containsKey(dst) : "Cannot redefine variable again.";
+
+ RAVariable rootSrc = find(src);
+ parent.put(dst, rootSrc);
+ }
+
+ /**
+ * Find the root of the variable. If none is found, then the variable itself is returned.
+ *
+ * @param x Variable to find the root of
+ * @return Root of the variable
+ */
+ public RAVariable find(RAVariable x) {
+ parent.putIfAbsent(x, x);
+ return parent.get(x);
+ }
+
+ /**
+ * Check if a variable is being aliased by some other variable.
+ *
+ *
+ * The parent map has to have an entry, and it has to be different from the variable itself.
+ *
+ *
+ * @param variable Input variable
+ * @return Is aliased?
+ */
+ protected boolean isAliased(RAVariable variable) {
+ return parent.containsKey(variable) && !parent.get(variable).equals(variable);
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/VerifierPrinter.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/VerifierPrinter.java
new file mode 100644
index 000000000000..d9c621275621
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/VerifierPrinter.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier;
+
+import jdk.graal.compiler.core.common.cfg.BasicBlock;
+import jdk.graal.compiler.debug.LogStream;
+import jdk.graal.compiler.lir.alloc.verifier.exceptions.CalleeSavedRegisterNotRetrievedException;
+import jdk.graal.compiler.lir.alloc.verifier.exceptions.MissingReferenceException;
+import jdk.graal.compiler.lir.alloc.verifier.exceptions.RAVException;
+import jdk.graal.compiler.lir.alloc.verifier.exceptions.RAVFailedVerificationException;
+import jdk.graal.compiler.lir.alloc.verifier.exceptions.ValueNotInRegisterException;
+
+import java.io.OutputStream;
+
+public class VerifierPrinter {
+ public static int PADDING = 4;
+ public static int INDENT = 4;
+
+ protected final LogStream out;
+ protected final RegAllocVerifier verifier;
+
+ protected VerifierPrinter(OutputStream out, RegAllocVerifier verifier) {
+ this.out = new LogStream(out);
+ this.verifier = verifier;
+ }
+
+ public void print() {
+ int longestRAVInstruction = 0;
+ for (var blockId : verifier.lir.getBlocks()) {
+ var block = verifier.lir.getBlockById(blockId);
+
+ for (var instruction : verifier.blockInstructions.get(block)) {
+ int instructionLength = instruction.toString().length();
+ if (instructionLength > longestRAVInstruction) {
+ longestRAVInstruction = instruction.toString().length();
+ }
+ }
+ }
+
+ for (var blockId : verifier.lir.getBlocks()) {
+ var block = verifier.lir.getBlockById(blockId);
+
+ printBlockHeader(block);
+
+ out.adjustIndentation(INDENT);
+ printEntryState(block);
+ for (var instruction : verifier.blockInstructions.get(block)) {
+ var instructionString = instruction.toString();
+ var difference = longestRAVInstruction - instructionString.length();
+
+ var space = new String(new char[difference + PADDING]).replace("\0", " ");
+
+ String lirInstrString;
+ if (instruction.lirInstruction == null) {
+ /*
+ * Test cases do this, here we just indicate that it's not part of LIR and never
+ * was.
+ */
+ lirInstrString = "";
+ } else {
+ lirInstrString = instruction.lirInstruction.toString();
+ }
+
+ out.println(instructionString + space + lirInstrString);
+ if (instruction instanceof RAVInstruction.Op op) {
+ out.adjustIndentation(INDENT);
+ if (op.lirInstruction.hasState()) {
+ out.println("State: " + op.stateValues);
+ }
+
+ if (op.references != null) {
+ out.println("References: " + op.references);
+ }
+ out.adjustIndentation(-INDENT);
+ }
+
+ }
+ out.adjustIndentation(-INDENT);
+ out.println();
+ }
+ }
+
+ protected void printBlockHeader(BasicBlock> block) {
+ var blockHeaderSB = new StringBuilder();
+ blockHeaderSB.append(block.toString()).append(": ");
+
+ if (block.getPredecessorCount() > 0) {
+ blockHeaderSB.append(block);
+ blockHeaderSB.append(" <- ");
+ for (int i = 0; i < block.getPredecessorCount(); i++) {
+ blockHeaderSB.append(block.getPredecessorAt(i)).append(", ");
+ }
+
+ if (!blockHeaderSB.isEmpty()) {
+ blockHeaderSB.setLength(blockHeaderSB.length() - 2);
+ }
+ }
+
+ if (block.getSuccessorCount() > 0) {
+ if (block.getPredecessorCount() > 0) {
+ blockHeaderSB.append(" | ");
+ }
+
+ blockHeaderSB.append(block);
+ blockHeaderSB.append(" -> ");
+ for (int i = 0; i < block.getSuccessorCount(); i++) {
+ blockHeaderSB.append(block.getSuccessorAt(i)).append(", ");
+ }
+
+ if (block.getSuccessorCount() > 0 && !blockHeaderSB.isEmpty()) {
+ blockHeaderSB.setLength(blockHeaderSB.length() - 2);
+ }
+ }
+
+ if (block.isLoopHeader()) {
+ blockHeaderSB.append(" | Loop {");
+ var loop = block.getLoop();
+ for (var member : loop.getBlocks()) {
+ blockHeaderSB.append(member);
+ blockHeaderSB.append(", ");
+ }
+ blockHeaderSB.setLength(blockHeaderSB.length() - 2); // always at least one member
+ blockHeaderSB.append("}");
+ }
+
+ out.println(blockHeaderSB.toString());
+ }
+
+ protected void printEntryState(BasicBlock> block) {
+ var blockVerifierState = verifier.blockEntryStates.get(block);
+ if (blockVerifierState == null) {
+ // If error occurred during creation of entry states, it might not be defined.
+ return;
+ }
+
+ if (block.getId() == 0) {
+ return; // Start block is always empty
+ }
+
+ out.println("Entry state:");
+ out.adjustIndentation(INDENT);
+ for (var location : blockVerifierState.values.internalMap.keySet()) {
+ var state = blockVerifierState.values.get(location);
+ if (state.isUnknown()) {
+ continue;
+ }
+
+ printAllocationState(location, state);
+ }
+ out.adjustIndentation(-INDENT);
+ out.println();
+ }
+
+ protected void printAllocationState(RAValue location, AllocationState state) {
+ String stateStr = switch (state) {
+ case ValueAllocationState st -> {
+ if (st.isUndefinedFromBlock()) {
+ yield "Value unknown from " + st.block;
+ } else {
+ yield "Value {" + st.getValue() + "} from " + st.source + " in " + st.block;
+ }
+ }
+ case ConflictedAllocationState st -> {
+ StringBuilder str = new StringBuilder();
+ str.append("Conflicted {\n");
+ for (var valueAllocState : st.getConflictedStates()) {
+ if (valueAllocState.isUndefinedFromBlock()) {
+ str.append("Value unknown from ").append(valueAllocState.block);
+ continue;
+ } else {
+ str.append(valueAllocState.getValue());
+ if (valueAllocState.block != null) {
+ str.append(" from ").append(valueAllocState.block);
+ }
+ }
+
+ str.append(", ");
+ }
+
+ if (!st.getConflictedStates().isEmpty()) {
+ str.setLength(str.length() - 2);
+ }
+
+ yield str.append("}").toString();
+ }
+ case UnknownAllocationState st -> "Unknown";
+ default -> throw new RAVError("Unexpected value: " + state);
+ };
+
+ out.println(location + " -> " + stateStr);
+ }
+
+ protected void printAllocationStateInDetail(RAValue location, AllocationState state) {
+ String stateStr = switch (state) {
+ case ValueAllocationState st -> {
+ if (st.isUndefinedFromBlock()) {
+ // Undefined value from a certain block.
+ yield "Value unknown from " + st.block;
+ } else {
+ yield "Value {" + st.getValue() + "} from " + st.source + " in " + st.block;
+ }
+ }
+ case ConflictedAllocationState st -> {
+ StringBuilder str = new StringBuilder();
+ str.append("Conflicted: \n");
+ for (var valueAllocState : st.getConflictedStates()) {
+ if (valueAllocState.isUndefinedFromBlock()) {
+ str.append(" - Value unknown from ").append(valueAllocState.block).append('\n');
+ continue;
+ }
+
+ str.append(" - ").append(valueAllocState.getValue()).append(" from ").append(valueAllocState.source).append(" in ").append(valueAllocState.block).append("\n");
+ }
+ yield str.toString();
+ }
+ case UnknownAllocationState st -> "Unknown";
+ default -> throw new RAVError("Unexpected value: " + state);
+ };
+
+ out.println(location + " -> " + stateStr);
+ }
+
+ protected void printIRWithException(RAVException exception) {
+ out.println("Register Allocation Verification failure:");
+ out.println(exception.getMessage());
+ out.println("Sourced from " + exception.getLocationString());
+ out.println();
+
+ for (var blockId : verifier.lir.getBlocks()) {
+ var block = verifier.lir.getBlockById(blockId);
+
+ printBlockHeader(block);
+ if (block.equals(exception.block)) {
+ out.println("^------------ Exception thrown here");
+ out.println();
+ }
+
+ out.adjustIndentation(INDENT);
+ printEntryState(block);
+ for (var instruction : verifier.blockInstructions.get(block)) {
+ if (exception.instruction == instruction) {
+ out.println("");
+ }
+
+ printInstruction(instruction);
+ if (exception.instruction == instruction) {
+ printExceptionInformation(exception);
+ }
+ }
+
+ if (exception.instruction == null) {
+ printExceptionInformation(exception);
+ }
+
+ out.adjustIndentation(-INDENT);
+ out.println();
+ }
+ }
+
+ protected void printIRWithMultiExceptions(RAVFailedVerificationException exception) {
+ out.println("Register Allocation Verification failure:");
+ out.println(exception.getMessage());
+ out.println();
+
+ for (var blockId : verifier.lir.getBlocks()) {
+ var block = verifier.lir.getBlockById(blockId);
+
+ printBlockHeader(block);
+
+ boolean inBlock = false;
+ for (var e : exception.exceptions) {
+ if (block.equals(e.block)) {
+ inBlock = true;
+ break;
+ }
+ }
+
+ if (inBlock) {
+ out.println("^------------ Exception thrown here");
+ out.println();
+ }
+
+ out.adjustIndentation(INDENT);
+ printEntryState(block);
+ for (var instruction : verifier.blockInstructions.get(block)) {
+
+ boolean inInstruction = false;
+ for (var e : exception.exceptions) {
+ if (e.instruction == instruction) {
+ inInstruction = true;
+ break;
+ }
+ }
+
+ if (inInstruction) {
+ out.println("");
+ }
+
+ printInstruction(instruction);
+ for (var e : exception.exceptions) {
+ if (e.instruction == instruction) {
+ printExceptionInformation(e);
+ }
+ }
+ }
+
+ for (var e : exception.exceptions) {
+ if (e.instruction == null) {
+ printExceptionInformation(e);
+ }
+ }
+
+ out.adjustIndentation(-INDENT);
+ out.println();
+ }
+ }
+
+ protected void printInstruction(RAVInstruction.Base instruction) {
+ out.println(instruction.toString());
+ if (instruction instanceof RAVInstruction.Op op) {
+ out.adjustIndentation(INDENT);
+ if (op.lirInstruction.hasState()) {
+ out.println("State: " + op.stateValues);
+ }
+
+ if (op.references != null) {
+ StringBuilder refString = new StringBuilder("References: ");
+ for (var reference : op.references) {
+ refString.append(reference.getValue()).append(", ");
+ }
+
+ if (!op.references.isEmpty()) {
+ refString.setLength(refString.length() - 2);
+ }
+
+ out.println(refString.toString());
+ }
+
+ out.adjustIndentation(-INDENT);
+ }
+ }
+
+ protected void printExceptionInformation(RAVException exception) {
+ out.println("^-------- " + exception.getMessage());
+
+ switch (exception) {
+ case ValueNotInRegisterException e -> printOtherStates(e);
+ case CalleeSavedRegisterNotRetrievedException e -> printCalleeSavedValues(e);
+ case MissingReferenceException e -> printOtherReferences(e);
+ default -> {
+ }
+ }
+
+ out.println();
+ }
+
+ private void printOtherStates(ValueNotInRegisterException exception) {
+ out.println("Other states:");
+
+ var locations = exception.blockVerifierState.values.getValueLocations(exception.variable);
+ out.adjustIndentation(INDENT);
+ for (var location : locations) {
+ var state = exception.blockVerifierState.values.get(location);
+ printAllocationStateInDetail(location, state);
+ }
+ out.adjustIndentation(-INDENT);
+ }
+
+ private void printCalleeSavedValues(CalleeSavedRegisterNotRetrievedException exception) {
+ out.println("Callee-saved values:");
+ var registers = verifier.registerAllocationConfig.getRegisterConfig().getCalleeSaveRegisters();
+ out.adjustIndentation(INDENT);
+ for (var reg : registers) {
+ var regValue = new RAVRegister(reg.asValue());
+ var state = exception.blockVerifierState.values.get(regValue);
+ printAllocationStateInDetail(regValue, state);
+ }
+ out.adjustIndentation(-INDENT);
+ }
+
+ private void printOtherReferences(MissingReferenceException exception) {
+ out.println("Other references:");
+ out.adjustIndentation(INDENT);
+ for (var location : exception.blockVerifierState.values.internalMap.keySet()) {
+ var state = exception.blockVerifierState.values.get(location);
+ if (state.isUnknown() || state.isConflicted()) {
+ continue;
+ }
+
+ var valueAllocState = (ValueAllocationState) state;
+ if (valueAllocState.getRAValue().getLIRKind().isValue()) {
+ continue;
+ }
+
+ // Print all available references.
+ printAllocationStateInDetail(location, state);
+ }
+ out.adjustIndentation(-INDENT);
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/AliveConstraintViolationException.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/AliveConstraintViolationException.java
new file mode 100644
index 000000000000..cfa7f0d76001
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/AliveConstraintViolationException.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier.exceptions;
+
+import jdk.graal.compiler.core.common.cfg.BasicBlock;
+import jdk.graal.compiler.lir.alloc.verifier.RAVInstruction;
+import jdk.graal.compiler.lir.alloc.verifier.RAValue;
+
+/**
+ * Violation of the alive inputs occurred, the same location was marked as alive argument as well as
+ * temp or output.
+ */
+@SuppressWarnings("serial")
+public class AliveConstraintViolationException extends RAVException {
+ public final RAVInstruction.Op instruction;
+
+ /**
+ * Construct an AliveConstraintViolationException.
+ *
+ * @param instruction Instruction where violation occurred
+ * @param block Block where violation occurred
+ * @param location Location that is being shared
+ * @param asDest Alive location was used as an output
+ */
+ public AliveConstraintViolationException(RAVInstruction.Op instruction, BasicBlock> block, RAValue location, boolean asDest) {
+ super(AliveConstraintViolationException.getErrorMessage(location, asDest), instruction, block);
+ this.instruction = instruction;
+ }
+
+ static String getErrorMessage(RAValue location, boolean asDest) {
+ if (asDest) {
+ return "Location " + location + " used as both alive and output";
+ }
+
+ return "Location " + location + " used as both alive and temp";
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/CalleeSavedRegisterNotRetrievedException.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/CalleeSavedRegisterNotRetrievedException.java
new file mode 100644
index 000000000000..fb8860f4f1f9
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/CalleeSavedRegisterNotRetrievedException.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier.exceptions;
+
+import jdk.graal.compiler.core.common.cfg.BasicBlock;
+import jdk.graal.compiler.lir.alloc.verifier.BlockVerifierState;
+import jdk.graal.compiler.lir.alloc.verifier.RAVRegister;
+
+/**
+ * Callee-saved register was not retrieved on an exit block.
+ */
+@SuppressWarnings("serial")
+public class CalleeSavedRegisterNotRetrievedException extends RAVException {
+ public final RAVRegister register;
+ public final BlockVerifierState blockVerifierState;
+
+ public CalleeSavedRegisterNotRetrievedException(RAVRegister register, BasicBlock> block, BlockVerifierState blockVerifierState) {
+ super(getErrorMessage(register), null, block);
+ this.register = register;
+ this.blockVerifierState = new BlockVerifierState(block, blockVerifierState);
+ }
+
+ public static String getErrorMessage(RAVRegister register) {
+ return "Callee saved register " + register + " not retrieved on exit";
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/ConstantRematerializedToStackException.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/ConstantRematerializedToStackException.java
new file mode 100644
index 000000000000..7a28da50bfa3
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/ConstantRematerializedToStackException.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier.exceptions;
+
+import jdk.graal.compiler.lir.alloc.verifier.RAVConstant;
+import jdk.graal.compiler.lir.alloc.verifier.RAValue;
+import jdk.graal.compiler.lir.alloc.verifier.ValueAllocationState;
+
+/**
+ * Constant was rematerialized to a stack location, which was forbidden for this variable/constant.
+ */
+@SuppressWarnings("serial")
+public class ConstantRematerializedToStackException extends RAVException {
+ public final RAVConstant constant;
+ public final RAValue location;
+ public final ValueAllocationState state;
+
+ public ConstantRematerializedToStackException(RAVConstant constant, RAValue location, ValueAllocationState state) {
+ super("Constant " + constant + " cannot be rematerialized to stack location " + location, state.getSource(), state.getBlock());
+
+ this.constant = constant;
+ this.location = location;
+ this.state = state;
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/InvalidRegisterUsedException.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/InvalidRegisterUsedException.java
new file mode 100644
index 000000000000..c166c99040d7
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/InvalidRegisterUsedException.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier.exceptions;
+
+import jdk.graal.compiler.core.common.cfg.BasicBlock;
+import jdk.graal.compiler.lir.alloc.verifier.RAVInstruction;
+import jdk.vm.ci.code.Register;
+
+/**
+ * Invalid register was used in allocation as defined by the
+ * {@link jdk.graal.compiler.core.common.alloc.RegisterAllocationConfig}.
+ */
+@SuppressWarnings("serial")
+public class InvalidRegisterUsedException extends RAVException {
+ public final Register register;
+
+ public InvalidRegisterUsedException(Register register, RAVInstruction.Base instruction, BasicBlock> block) {
+ super(getErrorMessage(register), instruction, block);
+ this.register = register;
+ }
+
+ static String getErrorMessage(Register register) {
+ return "Register " + register + " is not allowed to be used by RegisterAllocatorConfig";
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/JavaKindReferenceMismatchException.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/JavaKindReferenceMismatchException.java
new file mode 100644
index 000000000000..17d271cfbd2c
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/JavaKindReferenceMismatchException.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier.exceptions;
+
+import jdk.graal.compiler.core.common.cfg.BasicBlock;
+import jdk.graal.compiler.lir.alloc.verifier.RAVInstruction;
+import jdk.vm.ci.meta.AllocatableValue;
+import jdk.vm.ci.meta.JavaKind;
+
+@SuppressWarnings("serial")
+public class JavaKindReferenceMismatchException extends RAVException {
+ public final AllocatableValue orig;
+ public final AllocatableValue curr;
+ public final JavaKind kind;
+
+ public JavaKindReferenceMismatchException(AllocatableValue orig, AllocatableValue curr, JavaKind kind, RAVInstruction.Base instruction, BasicBlock> block) {
+ super(getMessage(orig, curr, kind), instruction, block);
+ this.orig = orig;
+ this.curr = curr;
+ this.kind = kind;
+ }
+
+ public static String getMessage(AllocatableValue orig, AllocatableValue curr, JavaKind kind) {
+ if (JavaKind.Object.equals(kind)) {
+ return orig + " -> " + curr + " not an object java kind when marked as a reference";
+ } else {
+ return orig + " -> " + curr + " is a reference but not marked as a reference";
+ }
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/KindsMismatchException.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/KindsMismatchException.java
new file mode 100644
index 000000000000..bff25656e3cb
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/KindsMismatchException.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier.exceptions;
+
+import jdk.graal.compiler.core.common.cfg.BasicBlock;
+import jdk.graal.compiler.lir.alloc.verifier.RAVInstruction;
+import jdk.graal.compiler.lir.alloc.verifier.RAValue;
+
+/**
+ * Kinds are not matching between two values.
+ */
+@SuppressWarnings("serial")
+public class KindsMismatchException extends RAVException {
+ public final RAValue value1;
+ public final RAValue value2;
+ public final boolean origVsCurr;
+
+ /**
+ * Construct a KindsMismatchException.
+ *
+ * @param instruction Instruction where violation occurred
+ * @param block Block where violation occurred
+ * @param value1 First value in comparison, original variable.
+ * @param value2 Second value in comparison, either current location or value stored in state
+ * @param origVsCurr Comparing the original variable to the current location
+ */
+ public KindsMismatchException(RAVInstruction.Base instruction, BasicBlock> block, RAValue value1, RAValue value2, boolean origVsCurr) {
+ super(KindsMismatchException.getErrorMessage(value1, value2, origVsCurr), instruction, block);
+
+ this.value1 = value1;
+ this.value2 = value2;
+ this.origVsCurr = origVsCurr;
+ }
+
+ static String getErrorMessage(RAValue value1, RAValue value2, boolean origVsCurr) {
+ if (origVsCurr) {
+ return value1.getValue() + " has different kind after allocation: " + value2.getValue();
+ }
+
+ return "Value in location has different kind: " + value1.getValue().getValueKind() + " vs. " + value2.getValue().getValueKind();
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/MissingLocationException.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/MissingLocationException.java
new file mode 100644
index 000000000000..dac8418d1c74
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/MissingLocationException.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier.exceptions;
+
+import jdk.graal.compiler.core.common.cfg.BasicBlock;
+import jdk.graal.compiler.lir.alloc.verifier.RAVInstruction;
+import jdk.graal.compiler.lir.alloc.verifier.RAValue;
+
+/**
+ * No location found in an instruction after allocation for certain variable.
+ */
+@SuppressWarnings("serial")
+public class MissingLocationException extends RAVException {
+ /**
+ * Construct a MissingLocationError.
+ *
+ * @param instruction Instruction where violation occurred
+ * @param block Block where violation occurred
+ * @param variable Variable before allocation that has no location afterward
+ */
+ public MissingLocationException(RAVInstruction.Op instruction, BasicBlock> block, RAValue variable) {
+ super(MissingLocationException.getMessage(variable), instruction, block);
+ }
+
+ static String getMessage(RAValue variable) {
+ return "Variable " + variable + " is missing a location";
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/MissingReferenceException.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/MissingReferenceException.java
new file mode 100644
index 000000000000..67f73fb3ed9b
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/MissingReferenceException.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier.exceptions;
+
+import jdk.graal.compiler.core.common.cfg.BasicBlock;
+import jdk.graal.compiler.lir.alloc.verifier.AllocationState;
+import jdk.graal.compiler.lir.alloc.verifier.BlockVerifierState;
+import jdk.graal.compiler.lir.alloc.verifier.RAVInstruction;
+import jdk.graal.compiler.lir.alloc.verifier.RAValue;
+
+@SuppressWarnings("serial")
+public class MissingReferenceException extends RAVException {
+ public final RAValue reference;
+ public final AllocationState state;
+ public final RAVInstruction.Op instruction;
+ public final BlockVerifierState blockVerifierState;
+
+ public MissingReferenceException(RAVInstruction.Op instruction, BasicBlock> block, RAValue reference, AllocationState state, BlockVerifierState blockVerifierState) {
+ super(getMessage(reference, state), instruction, block);
+ this.reference = reference;
+ this.state = state;
+ this.instruction = instruction;
+ this.blockVerifierState = new BlockVerifierState(block, blockVerifierState);
+ }
+
+ public static String getMessage(RAValue reference, AllocationState state) {
+ return "Missing reference in " + reference + " actually is " + state;
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/OperandFlagMismatchException.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/OperandFlagMismatchException.java
new file mode 100644
index 000000000000..5c09badad475
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/OperandFlagMismatchException.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier.exceptions;
+
+import jdk.graal.compiler.core.common.cfg.BasicBlock;
+import jdk.graal.compiler.lir.LIRInstruction;
+import jdk.graal.compiler.lir.alloc.verifier.RAVInstruction;
+import jdk.vm.ci.meta.Value;
+
+import java.util.EnumSet;
+
+/**
+ * Value used in instruction does not satisfy it's
+ * {@link jdk.graal.compiler.lir.LIRInstruction.OperandFlag operand flags}, for example if an
+ * operand is a stack slot, but should only be a register.
+ */
+@SuppressWarnings("serial")
+public class OperandFlagMismatchException extends RAVException {
+ public final RAVInstruction.Op instruction;
+ public final Value value;
+ public final EnumSet flags;
+
+ public OperandFlagMismatchException(RAVInstruction.Op op, BasicBlock> block, Value value, EnumSet flags) {
+ super(getErrorMessage(value, flags), op, block);
+ this.value = value;
+ this.flags = flags;
+ this.instruction = op;
+ }
+
+ static String getErrorMessage(Value value, EnumSet flags) {
+ return "Value " + value + " does not satisfy operand flags " + flags.toString();
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/RAVException.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/RAVException.java
new file mode 100644
index 000000000000..d0db7688f1a5
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/RAVException.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier.exceptions;
+
+import jdk.graal.compiler.core.common.cfg.BasicBlock;
+import jdk.graal.compiler.debug.GraalError;
+import jdk.graal.compiler.lir.alloc.verifier.RAVInstruction;
+
+/**
+ * Register Allocation Verification Exception - a violation made by the
+ * {@link jdk.graal.compiler.lir.alloc.RegisterAllocationPhase register allocation} occurred and
+ * will be thrown in verification phase.
+ */
+@SuppressWarnings("serial")
+public class RAVException extends GraalError {
+ public final RAVInstruction.Base instruction;
+ public final BasicBlock> block;
+
+ public RAVException(String message, RAVInstruction.Base instruction, BasicBlock> block) {
+ super(message);
+
+ this.instruction = instruction;
+ this.block = block;
+ }
+
+ public RAVException(String message, RAVException cause) {
+ super(cause, message);
+
+ this.instruction = cause.instruction;
+ this.block = cause.block;
+ }
+
+ @Override
+ public synchronized RAVException getCause() {
+ return (RAVException) super.getCause();
+ }
+
+ public String getLocationString() {
+ return instruction + " in " + block;
+ }
+
+ public String getFullMessage() {
+ return getMessage() + " in " + getLocationString();
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/RAVFailedVerificationException.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/RAVFailedVerificationException.java
new file mode 100644
index 000000000000..1aa67563acff
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/RAVFailedVerificationException.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier.exceptions;
+
+import jdk.graal.compiler.debug.GraalError;
+
+import java.util.List;
+
+/**
+ * Composite exception taking every {@link RAVException exception} that occurred (exceptions caused
+ * by the {@link jdk.graal.compiler.lir.alloc.RegisterAllocationPhase}) and combining them to one
+ * exception.
+ */
+@SuppressWarnings("serial")
+public class RAVFailedVerificationException extends GraalError {
+ public final List exceptions;
+
+ public RAVFailedVerificationException(List exceptions) {
+ super(RAVFailedVerificationException.getMessage(exceptions));
+
+ this.exceptions = exceptions;
+ }
+
+ static String getMessage(List exceptions) {
+ StringBuilder sb = new StringBuilder("Failed to verify ");
+ sb.append(":");
+ for (var e : exceptions) {
+ sb.append(" - ");
+ sb.append(e.getMessage());
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/ValueNotInRegisterException.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/ValueNotInRegisterException.java
new file mode 100644
index 000000000000..47b0946b4bba
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/exceptions/ValueNotInRegisterException.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.graal.compiler.lir.alloc.verifier.exceptions;
+
+import jdk.graal.compiler.core.common.cfg.BasicBlock;
+import jdk.graal.compiler.lir.alloc.verifier.AllocationState;
+import jdk.graal.compiler.lir.alloc.verifier.BlockVerifierState;
+import jdk.graal.compiler.lir.alloc.verifier.RAVInstruction;
+import jdk.graal.compiler.lir.alloc.verifier.RAValue;
+
+/**
+ * Value was not found in the location we needed it in.
+ */
+@SuppressWarnings("serial")
+public class ValueNotInRegisterException extends RAVException {
+ public final RAVInstruction.Op instruction;
+
+ /**
+ * Symbol that was not found at the location.
+ *
+ *
+ * Can be a constant, variable, or other symbolic value.
+ *
+ */
+ public final RAValue variable;
+
+ /**
+ * Location where the symbol was not found.
+ *
+ *
+ * Can be a register or a (virtual) stack slot.
+ *
+ */
+ public final RAValue location;
+ public final AllocationState state;
+ public final BlockVerifierState blockVerifierState;
+
+ /**
+ * Construct a ValueNotInRegisterException.
+ *
+ * @param instruction Instruction where violation occurred
+ * @param block Block where violation occurred
+ * @param variable Target variable we are looking for
+ * @param location Location where we couldn't find it
+ * @param state The actual state that the location is in
+ */
+ public ValueNotInRegisterException(RAVInstruction.Op instruction, BasicBlock> block, RAValue variable, RAValue location, AllocationState state, BlockVerifierState blockVerifierState) {
+ super(ValueNotInRegisterException.getErrorMessage(variable, location, state), instruction, block);
+
+ this.variable = variable;
+ this.location = location;
+ this.state = state;
+ this.blockVerifierState = new BlockVerifierState(block, blockVerifierState);
+ this.instruction = instruction;
+ }
+
+ static String getErrorMessage(RAValue variable, RAValue location, AllocationState state) {
+ return "Value " + variable + " not found in " + location + " the actual state is " + state;
+ }
+}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/package-info.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/package-info.java
new file mode 100644
index 000000000000..2eb6eb517ed0
--- /dev/null
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/package-info.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * Register Allocation Verifier
+ *
+ * This package verifies the output of the
+ * {@link jdk.graal.compiler.lir.alloc.RegisterAllocationPhase register allocator} by checking that
+ * the values flowing into instructions are the same as in the program before allocation. It is
+ * implemented as a phase in {@link jdk.graal.compiler.lir.alloc.verifier.RegAllocVerifierPhase},
+ * wrapping around both the register and stack allocators to gain access to both pre- and
+ * post-allocation LIR.
+ *
+ * The principle
+ *
+ * {@link jdk.graal.compiler.lir.Variable Variables} and
+ * {@link jdk.graal.compiler.lir.ConstantValue} from pre-allocation LIR act as symbols that are
+ * tracked in concrete locations during the verification process, implemented in
+ * {@link jdk.graal.compiler.lir.alloc.verifier.RegAllocVerifier} and
+ * {@link jdk.graal.compiler.lir.alloc.verifier.BlockVerifierState}.
+ *
+ *
+ * The core algorithm performs symbolic execution on abstract instructions that boil down the
+ * functionality of LIR instruction to only symbols and locations they read and write, as follows:
+ *
+ * [(output_symbol1, output_location1), ...] = Op [(input_symbol1, input_location1), ...]
+ * dst_location = Move src_location
+ *
+ *
+ * Where {@link jdk.graal.compiler.lir.alloc.verifier.RAVInstruction.Op Op} is an operation that was
+ * present before allocation and only had its operands changed. The allocator has to make sure that
+ * symbols read by the instruction are present at those selected locations. Output symbols are
+ * written at the output locations, when being symbolically executed.
+ * {@link jdk.graal.compiler.lir.alloc.verifier.RAVInstruction.LocationMove Move} is an
+ * allocator-inserted instruction that transfers symbols, created by Op, from source location to the
+ * destination, keeping the source unchanged.
+ *
+ *
+ *
+ * The execution of these instructions goes as follows:
+ *
+ * - Op puts output_symbol(s) to the output_location(s) set by the allocator
+ * - Move puts the symbol from src_location to dst_location
+ *
+ *
+ *
+ *
+ * Instead of only maintaining a symbol for a location, an allocation state is stored instead:
+ *
+ * - {@link jdk.graal.compiler.lir.alloc.verifier.UnknownAllocationState Unknown} - no information
+ * about the contents
+ * - {@link jdk.graal.compiler.lir.alloc.verifier.ValueAllocationState Value(s)} - symbol v is
+ * stored at this location
+ * - {@link jdk.graal.compiler.lir.alloc.verifier.ConflictedAllocationState Conflict} -
+ * approximation, multiple symbols can be present
+ *
+ *
+ * Every block has its own {@link jdk.graal.compiler.lir.alloc.verifier.AllocationStateMap
+ * allocation map}: location -> state. Where states are stored for every location. If blocks meet at
+ * their common successor, two different symbols can be present at the same location. This is
+ * handled by a {@link jdk.graal.compiler.lir.alloc.verifier.AllocationState#meet} function. The
+ * logic of the two-state meeting can be described in this way:
+ *
+ *
+ * meet(x, x) = x
+ * meet(x, y) = Conflicted, if x != y
+ *
+ *
+ * Where both states need to be equal to be passed to the merge block, otherwise a conflict is
+ * created. Conflict does not mean an error state, rather that it is forbidden to read from this
+ * location. Conflict can be overwritten by Op generating a new symbol to the same location.
+ *
+ *
+ *
+ * The algorithm works in two stages:
+ *
+ * - Compute the initial state of every block until a fixed point is reached, only generates
+ * symbols
+ * - Verify that read symbols are present at their locations
+ *
+ *
+ *
+ *
+ * {@link jdk.graal.compiler.lir.alloc.verifier.RegAllocVerifier#computeEntryStates} runs a queue of
+ * basic blocks, starting from the start block. Every block takes its current entry state and runs
+ * symbolic execution on its instructions. The new state is then passed to its successors. Successor
+ * is enqueued only if a change was made to its entry state.
+ *
+ *
+ *
+ * When every block has a fixed entry state (the allocation map), then we can linearly iterate over
+ * every block, take its entry state, and for every instruction, first check that read symbols are
+ * at read locations in the state map, second generate or move symbols. Implemented in
+ * {@link jdk.graal.compiler.lir.alloc.verifier.RegAllocVerifier#verifyInstructionInputs}.
+ *
+ *
+ * Preprocessing stage and the IR
+ *
+ * Before the validation process can begin, the intermediate representation needs to be created.
+ * Pre-allocation instructions are processed and stored in a map: LIRInstruction -> Op. Before
+ * allocation, we also map symbols to fixed registers, as in this example:
+ *
+ *
+ * rdi|DWORD = MOVE input: int[1|0x1]
+ * rsi|QWORD = MOVE input: v7|QWORD moveKind: QWORD
+ * rax = CALL parameters: [rdi|DWORD, rsi|QWORD] temps: [rax|ILLEGAL, ...]
+ * v8 = MOVE input: rax|QWORD
+ *
+ *
+ * Maps the symbols 0x1 and v7 to the rdi and rsi in the CALL, so if these moves are coalesced,
+ * information is not lost. It also maps v8 to the rax output of the CALL.
+ *
+ *
+ * After allocation is performed, the IR can now be completed. Every existing lir instruction has a
+ * {@link jdk.graal.compiler.lir.alloc.verifier.RAVInstruction.Base} counterpart. If it was present
+ * before allocation, an {@link jdk.graal.compiler.lir.alloc.verifier.RAVInstruction.Op} otherwise a
+ * {@link jdk.graal.compiler.lir.alloc.verifier.RAVInstruction.LocationMove}.
+ *
+ *
+ *
+ * Variable to variable moves, e.g. v2|DWORD = MOVE v1|DWORD can be removed by the
+ * allocator. If this happens, every occurrence of the output variable is replaced with the source
+ * variable. {@link jdk.graal.compiler.lir.alloc.verifier.VariableSynonymMap} also handles chained
+ * coalesced moves, always uses the root source variable created by an existing instruction for
+ * substitution.
+ *
+ *
+ * Constants
+ *
+ * Constants are created by {@link jdk.graal.compiler.lir.StandardOp.LoadConstantOp}, where the
+ * output before allocation can be a variable. They can also be used as operands before allocation
+ * and be put into a register by the allocator. For example, JUMP [int[0|0x0]] as the initial
+ * condition of a for loop.
+ *
+ *
+ * To normalize this behavior, variables defined by
+ * {@link jdk.graal.compiler.lir.StandardOp.LoadConstantOp} are substituted for their constant
+ * values inside the verifier IR. Furthermore, every
+ * {@link jdk.graal.compiler.lir.StandardOp.LoadConstantOp} has its own instruction
+ * {@link jdk.graal.compiler.lir.alloc.verifier.RAVInstruction.ValueMove} in the IR, which puts the
+ * constant symbol into specified location. Doing this also handles constants re-materialized by the
+ * allocator.
+ *
+ *
+ * Inferring label-defined variable locations
+ *
+ * The allocator strips information about locations of label-defined variables, as well as locations
+ * of symbols in the preceding jumps. This information is necessary to correctly verify symbol
+ * reads.
+ *
+ *
+ * This information is reconstructed from the verifier's IR by finding the first usage of the
+ * label-defined variable and going back through the CFG to the label. Handling any
+ * allocator-inserted moves that might have moved the symbol.
+ *
+ *
+ *
+ * The preceding jumps then assert the result of this reconstruction. Their location matches the one
+ * in the label. If the symbols flowing through the JUMP into the LABEL are not contained in the
+ * selected location, for every predecessor, then a failure occurs.
+ *
+ *
+ *
+ * Implemented in {@link jdk.graal.compiler.lir.alloc.verifier.FromUsageResolverGlobal}, uses a
+ * worklist, walking from every end block to the start, resolving label variable locations.
+ *
+ *
+ * Exceptions
+ *
+ * Exceptions are in the package {@link jdk.graal.compiler.lir.alloc.verifier.exceptions}. Each of
+ * them stores debugging information about the fault that was detected. Block, instruction where it
+ * occurred, and other exception-specific information.
+ *
+ *
+ * {@link jdk.graal.compiler.lir.alloc.verifier.AllocationState} maintains debug information.
+ * {@link jdk.graal.compiler.lir.alloc.verifier.ValueAllocationState} contains information about the
+ * instruction that created the symbol.
+ * {@link jdk.graal.compiler.lir.alloc.verifier.ConflictedAllocationState} contains all the value
+ * allocation states that are in conflict. New information added to the set of conflicting value
+ * states is not propagated through the CFG.
+ *
+ *
+ *
+ * When an exception is thrown, a debug file with the extension ".rax.txt" is created in the
+ * graal_dumps directory. The file contains the CFG and verifier's instructions in a text format.
+ * Describing in which block and which instruction has the exception occurred. Optionally, the
+ * process can gather multiple exceptions, always at most one per instruction, using
+ * {@link jdk.graal.compiler.lir.alloc.verifier.exceptions.RAVFailedVerificationException}.
+ *
+ *
+ * Other checking
+ *
+ *
+ * Checking that other constraints, the register allocator needs to follow, were not violated.
+ *
+ * - Check kinds between the original symbol and stored symbol
+ * - Check if collected GC roots are actually references
+ * - Check that "alive" inputs live past the instruction
+ * - Check that only allocatable registers are used
+ * - Check if callee-saved registers are retrieved at exit
+ * - Check JavaKind and LIRKind correspondence in {@link jdk.vm.ci.code.BytecodeFrame}
+ * - Check for operand flags
+ *
+ *
+ *
+ * Options
+ *
+ * - {@link jdk.graal.compiler.lir.alloc.verifier.RegAllocVerifierPhase.Options#EnableRAVerifier}
+ * - enable this process during compilation
+ * - {@link jdk.graal.compiler.lir.alloc.verifier.RegAllocVerifierPhase.Options#VerifyStackAllocator}
+ * - verify the output of stack allocator
+ * - {@link jdk.graal.compiler.lir.alloc.verifier.RegAllocVerifierPhase.Options#CollectReferences}
+ * - use {@link jdk.graal.compiler.lir.dfa.LocationMarker} interface to collect reference
+ * information
+ * - {@link jdk.graal.compiler.lir.alloc.verifier.RegAllocVerifierPhase.Options#RAVFailOnFirst} -
+ * if set to true, first failure is thrown; otherwise all failures are collected at thrown at once
+ * using
+ * {@link jdk.graal.compiler.lir.alloc.verifier.exceptions.RAVFailedVerificationException}
+ *
+ *
+ * Collect references
+ *
+ * The verifier uses {@link jdk.graal.compiler.lir.dfa.LocationMarker} to collect live object
+ * reference information before {@link jdk.graal.compiler.lir.phases.FinalCodeAnalysisStage final
+ * code analysis} runs. {@link jdk.graal.compiler.lir.alloc.verifier.RAVInstruction.Op Op} holds a
+ * set of references. When being symbolically executed
+ * ({@link jdk.graal.compiler.lir.alloc.verifier.BlockVerifierState#update} and the reference set is
+ * not null, all references that are not part of the set are deleted from the
+ * {@link jdk.graal.compiler.lir.alloc.verifier.AllocationStateMap allocation map}. Checking stage,
+ * then verifies that all references in the set are actually references.
+ */
+package jdk.graal.compiler.lir.alloc.verifier;
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/dfa/LocationMarker.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/dfa/LocationMarker.java
index f8a8020afa10..4d25165f1ef8 100644
--- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/dfa/LocationMarker.java
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/dfa/LocationMarker.java
@@ -70,7 +70,7 @@ protected LocationMarker(LIR lir, FrameMap frameMap) {
protected abstract void processState(LIRInstruction op, LIRFrameState info, S values);
- void build() {
+ public void build() {
BasicBlock>[] blocks = lir.getControlFlowGraph().getBlocks();
UniqueWorkList worklist = new UniqueWorkList(blocks.length);
for (int i = blocks.length - 1; i >= 0; i--) {
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/dfa/UniqueWorkList.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/dfa/UniqueWorkList.java
index ec9a11ab5809..a422142380ef 100644
--- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/dfa/UniqueWorkList.java
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/dfa/UniqueWorkList.java
@@ -34,11 +34,11 @@
* Ensures that an element is only in the worklist once.
*
*/
-class UniqueWorkList extends ArrayDeque> {
+public class UniqueWorkList extends ArrayDeque> {
private static final long serialVersionUID = 8009554570990975712L;
BitSet valid;
- UniqueWorkList(int size) {
+ public UniqueWorkList(int size) {
this.valid = new BitSet(size);
}
@@ -72,4 +72,19 @@ public boolean addAll(Collection extends BasicBlock>> collection) {
}
return changed;
}
+
+ @Override
+ public boolean contains(Object o) {
+ return valid.get(((BasicBlock>) o).getId());
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ if (!contains(o)) {
+ return false;
+ }
+
+ valid.set(((BasicBlock>) o).getId(), false);
+ return super.remove(o);
+ }
}
diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/phases/AllocationStage.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/phases/AllocationStage.java
index 801ba4519cf1..9b8f04f623fe 100644
--- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/phases/AllocationStage.java
+++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/phases/AllocationStage.java
@@ -26,6 +26,8 @@
import jdk.graal.compiler.debug.Assertions;
import jdk.graal.compiler.lir.alloc.AllocationStageVerifier;
+import jdk.graal.compiler.lir.alloc.RegisterAllocationPhase;
+import jdk.graal.compiler.lir.alloc.verifier.RegAllocVerifierPhase;
import jdk.graal.compiler.lir.stackslotalloc.LSStackSlotAllocator;
import jdk.graal.compiler.lir.stackslotalloc.SimpleStackSlotAllocator;
import jdk.graal.compiler.lir.alloc.lsra.LinearScanPhase;
@@ -40,10 +42,24 @@ public AllocationStage(OptionValues options) {
appendPhase(new LinearScanPhase());
// build frame map
+ LIRPhase stackAllocator;
if (LSStackSlotAllocator.Options.LIROptLSStackSlotAllocator.getValue(options)) {
- appendPhase(new LSStackSlotAllocator());
+ stackAllocator = new LSStackSlotAllocator();
} else {
- appendPhase(new SimpleStackSlotAllocator());
+ stackAllocator = new SimpleStackSlotAllocator();
+ }
+
+ if (RegAllocVerifierPhase.Options.EnableRAVerifier.getValue(options)) {
+ // Wrap used register allocator with the verifier to check it's output
+ // based on the input with variables
+ var iterator = this.findPhase(RegisterAllocationPhase.class);
+ if (iterator != null) {
+ var allocator = (RegisterAllocationPhase) iterator.previous();
+ // If not found, then let later reg alloc check throw
+ iterator.set(new RegAllocVerifierPhase(allocator, stackAllocator));
+ }
+ } else {
+ appendPhase(stackAllocator);
}
if (Assertions.detailedAssertionsEnabled(options)) {