diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/RegAllocVerifierTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/RegAllocVerifierTest.java new file mode 100644 index 000000000000..2e4d33a2191d --- /dev/null +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/RegAllocVerifierTest.java @@ -0,0 +1,1690 @@ +/* + * 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.core.test; + +import static jdk.graal.compiler.lir.LIRInstruction.OperandFlag.REG; +import static jdk.graal.compiler.lir.LIRInstruction.OperandFlag.STACK; + +import jdk.graal.compiler.code.CompilationResult; +import jdk.graal.compiler.core.common.CompilationIdentifier; +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.core.phases.HighTier; +import jdk.graal.compiler.java.BytecodeParserOptions; +import jdk.graal.compiler.lir.ConstantValue; +import jdk.graal.compiler.lir.LIR; +import jdk.graal.compiler.lir.LIRInstruction; +import jdk.graal.compiler.lir.LIRInstructionClass; +import jdk.graal.compiler.lir.LIRValueUtil; +import jdk.graal.compiler.lir.StandardOp; +import jdk.graal.compiler.lir.Variable; +import jdk.graal.compiler.lir.alloc.RegisterAllocationPhase; +import jdk.graal.compiler.lir.alloc.verifier.UnknownAllocationState; +import jdk.graal.compiler.lir.alloc.verifier.exceptions.AliveConstraintViolationException; +import jdk.graal.compiler.lir.alloc.verifier.exceptions.CalleeSavedRegisterNotRetrievedException; +import jdk.graal.compiler.lir.alloc.verifier.ConflictedAllocationState; +import jdk.graal.compiler.lir.alloc.verifier.exceptions.ConstantRematerializedToStackException; +import jdk.graal.compiler.lir.alloc.verifier.exceptions.InvalidRegisterUsedException; +import jdk.graal.compiler.lir.alloc.verifier.exceptions.KindsMismatchException; +import jdk.graal.compiler.lir.alloc.verifier.exceptions.MissingLocationException; +import jdk.graal.compiler.lir.alloc.verifier.exceptions.MissingReferenceException; +import jdk.graal.compiler.lir.alloc.verifier.exceptions.OperandFlagMismatchException; +import jdk.graal.compiler.lir.alloc.verifier.RAVConstant; +import jdk.graal.compiler.lir.alloc.verifier.exceptions.RAVException; +import jdk.graal.compiler.lir.alloc.verifier.RAVInstruction; +import jdk.graal.compiler.lir.alloc.verifier.RAValue; +import jdk.graal.compiler.lir.alloc.verifier.RAVariable; +import jdk.graal.compiler.lir.alloc.verifier.RegAllocVerifierPhase; +import jdk.graal.compiler.lir.alloc.verifier.ValueAllocationState; +import jdk.graal.compiler.lir.alloc.verifier.exceptions.ValueNotInRegisterException; +import jdk.graal.compiler.lir.asm.CompilationResultBuilder; +import jdk.graal.compiler.lir.framemap.SimpleVirtualStackSlot; +import jdk.graal.compiler.lir.gen.LIRGenerationResult; +import jdk.graal.compiler.lir.phases.AllocationPhase; +import jdk.graal.compiler.lir.phases.LIRPhase; +import jdk.graal.compiler.lir.phases.LIRSuites; +import jdk.graal.compiler.nodes.StructuredGraph; +import jdk.graal.compiler.options.OptionValues; +import jdk.graal.compiler.util.EconomicHashSet; +import jdk.vm.ci.amd64.AMD64; +import jdk.vm.ci.aarch64.AArch64; +import jdk.vm.ci.amd64.AMD64Kind; +import jdk.vm.ci.code.CallingConvention; +import jdk.vm.ci.code.Register; +import jdk.vm.ci.code.RegisterAttributes; +import jdk.vm.ci.code.RegisterConfig; +import jdk.vm.ci.code.TargetDescription; +import jdk.vm.ci.code.ValueKindFactory; +import jdk.vm.ci.meta.AllocatableValue; +import jdk.vm.ci.meta.Constant; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.PlatformKind; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ValueKind; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Test cases for {@link RegAllocVerifierPhase}, these work by injecting errors into incoming + * snippets and detecting that the exact exception is thrown. + * + *

+ * The test case first runs the {@link RegAllocVerifierPhase} on the same exact snippet, with the + * original {@link RegisterAllocationPhase register allocator} to make sure there are no issues with + * it. Then, the {@link RAVPhaseWrapper tainted verifier phase} is used, that injects a fault into + * verifier's instructions. {@link RegisterAllocationPhase Verifier phase} runs, and we expect it to + * throw an exception, this is then tested with assertions, and we look at its contents. + *

+ * + *

+ * Most of these tainted phases find the first candidate instruction and corrupt it. Some look for + * control-flow patterns to corrupt. Few change the {@link RegisterAllocationConfig register + * allocator config} to see if generated code adheres to it, while allocator runs with the correct + * config. Sometimes a scenario is easier to simulate directly with overwritten instructions. + *

+ */ +public class RegAllocVerifierTest extends GraalCompilerTest { + /** + * Should the valid set of compiler phase suites be used? + */ + boolean validSuites = true; + + /** + * Exception thrown during the verification process. + */ + RAVException exception; + + /** + * Phase that causes RAVException to be thrown, by modifying LIR or Verifier State. + */ + RAVPhaseWrapper phase; + + /** + * Disable inlining to force function calls during verification, off by default. + */ + boolean disableInlining = false; + + /** + * Base for tainted verifier phases, with helper functions. The + * {@link RAVPhaseWrapper#modifyVerifierInstructions} is where modifications are performed for + * detection. + */ + abstract class RAVPhaseWrapper extends RegAllocVerifierPhase { + @FunctionalInterface + interface InstructionScanFunction { + boolean apply(BasicBlock block, RAVInstruction.Base instruction); + } + + @FunctionalInterface + interface OpScanFunction { + boolean apply(BasicBlock block, RAVInstruction.Op op); + } + + RAVPhaseWrapper() { + super(null, null); + } + + @Override + protected final BlockMap> getVerifierInstructions(LIR lir, Map preallocMap, AllocationContext context) { + var instructions = super.getVerifierInstructions(lir, preallocMap, context); + modifyVerifierInstructions(lir, instructions, context); + return instructions; + } + + protected void modifyVerifierInstructions(LIR lir, BlockMap> instructions, AllocationContext context) { + // Overwritten when a modification is inserted into the resulting verifier instructions + // Does not always have to be used. + } + + @Override + protected void run(TargetDescription target, LIRGenerationResult lirGenRes, AllocationContext context) { + try { + super.run(target, lirGenRes, context); + } catch (RAVException e) { + exception = e; + } + } + + /** + * Last used variable id, incremented whenever {@link RAVPhaseWrapper#createNewVariable} is + * called. + */ + private int lastVariableId = -1; + + protected RAVariable createNewVariable(LIR lir, ValueKind kind) { + if (lastVariableId == -1) { + lastVariableId = lir.numVariables(); + } + + var newVariable = new Variable(kind, ++lastVariableId); + return (RAVariable) RAValue.create(newVariable); + } + + protected RAVariable createNewVariable(LIR lir) { + return createNewVariable(lir, ValueKind.Illegal); + } + + protected RAVInstruction.Op createSymbolUsage(RAValue symbol, RAValue location) { + var usage = new RAVInstruction.Op(new StandardOp.NoOp(null, 0)); + + usage.uses = new RAVInstruction.ValueArrayPair(1); + usage.uses.curr[0] = location; + usage.uses.orig[0] = symbol; + usage.uses.operandFlags = new ArrayList<>(List.of(EnumSet.of(REG, STACK))); + + return usage; + } + + protected RAVInstruction.Base createSymbolSpawnOp(RAValue symbol, RAValue location) { + var lirInstruction = new StandardOp.NoOp(null, 0); + + if (symbol.isConstant()) { + return new RAVInstruction.ValueMove(lirInstruction, symbol.asConstant().getConstantValue(), location.getValue()); + } + + var op = new RAVInstruction.Op(lirInstruction); + + op.dests = new RAVInstruction.ValueArrayPair(1); + op.dests.curr[0] = location; + op.dests.orig[0] = symbol; + op.dests.operandFlags = new ArrayList<>(List.of(EnumSet.of(REG, STACK))); + + return op; + } + + /** + * Helper class for finding a fresh new location. + */ + static class UnusedValueTracker { + int highestVstackId; + Set allocatableRegs; + + UnusedValueTracker(RegisterAllocationConfig regAllocConfig) { + highestVstackId = -1; + allocatableRegs = new EconomicHashSet<>(regAllocConfig.getAllocatableRegisters()); + } + + void handleInstruction(RAVInstruction.Base instruction) { + switch (instruction) { + case RAVInstruction.Op op -> { + for (int i = 0; i < op.dests.count; i++) { + var curr = op.dests.curr[i]; + if (curr == null) { + continue; + } + + handleValue(curr); + } + } + case RAVInstruction.LocationMove move -> { + handleValue(move.to); + } + case RAVInstruction.ValueMove move -> { + handleValue(move.getLocation()); + } + default -> { + } + } + } + + void handleValue(RAValue value) { + if (value.isRegister()) { + allocatableRegs.remove(value.asRegister().getRegister()); + } else if (LIRValueUtil.isVirtualStackSlot(value.getValue())) { + var vstack = LIRValueUtil.asVirtualStackSlot(value.getValue()); + highestVstackId = Math.max(vstack.getId(), highestVstackId); + } + } + + RAValue getResult(ValueKind kind) { + if (allocatableRegs.isEmpty()) { + return RAValue.create(new SimpleVirtualStackSlot(highestVstackId + 1, kind)); + } + + return RAValue.create(allocatableRegs.stream().iterator().next().asValue(kind)); + } + } + + /** + * Get unused value by other instructions. If a {@link Register} from + * {@link RegisterConfig#getAllocatableRegisters() allocatable registers} is not used, then + * it's prefered. Otherwise, a new {@link jdk.graal.compiler.lir.VirtualStackSlot}, with the + * highest id will be used. + * + * @param lir LIR + * @param instructions Verifier instructions + * @param context Allocation context + * @return New, unused value + */ + protected RAValue getUnusedValue(LIR lir, BlockMap> instructions, AllocationContext context) { + return getUnusedValue(lir, instructions, context, ValueKind.Illegal); + } + + protected RAValue getUnusedValue(LIR lir, BlockMap> instructions, AllocationContext context, ValueKind kind) { + var tracker = new UnusedValueTracker(context.registerAllocationConfig); + for (var blockId : lir.getBlocks()) { + var block = lir.getBlockById(blockId); + var instructionsForBlock = instructions.get(block); + for (var instruction : instructionsForBlock) { + tracker.handleInstruction(instruction); + } + } + + return tracker.getResult(kind); + } + + /** + * Go over instructions, run the scanFuntion, if true a modification occurred and stop. + * + * @param lir LIR + * @param instructions Verifier instructions + * @param scanFunction Modification function + * @return true, if modification occurred, otherwise false + */ + protected boolean scanOverInstructions(LIR lir, BlockMap> instructions, InstructionScanFunction scanFunction) { + for (var blockId : lir.getBlocks()) { + var block = lir.getBlockById(blockId); + var instructionsForBlock = instructions.get(block); + for (var instruction : instructionsForBlock) { + if (scanFunction.apply(block, instruction)) { + return true; + } + } + } + + return false; + } + + /** + * Same as {@link RAVPhaseWrapper#scanOverInstructions}, but only for + * {@link jdk.graal.compiler.lir.alloc.verifier.RAVInstruction.Op}. + * + * @param lir LIR + * @param instructions Verifier instructions + * @param scanFunction Modification function + * @return true, if modification occurred, otherwise false + */ + protected boolean scanOps(LIR lir, BlockMap> instructions, OpScanFunction scanFunction) { + return scanOverInstructions(lir, instructions, (block, instruction) -> { + if (instruction instanceof RAVInstruction.Op op) { + return scanFunction.apply(block, op); + } + + return false; + }); + } + } + + /** + * Overwrite first seen variable with a fresh new one, to detect + * {@link ValueNotInRegisterException} with @{link {@link ValueAllocationState}}. + */ + class ChangeVariablePhase extends RAVPhaseWrapper { + protected RAVariable originalVariable; + protected RAVariable newVariable; + + @Override + protected void modifyVerifierInstructions(LIR lir, BlockMap> instructions, AllocationContext ctx) { + scanOps(lir, instructions, (block, op) -> { + for (int i = 0; i < op.dests.count; i++) { + var curr = op.dests.curr[i]; + var orig = op.dests.orig[i]; + if (curr.equals(orig) || !orig.isVariable()) { + continue; + } + + var variable = op.dests.orig[i].asVariable(); + var newVariable = createNewVariable(lir, variable.getLIRKind()); + + op.dests.orig[i] = newVariable; + + this.originalVariable = variable; + this.newVariable = newVariable; + + return true; + } + + return false; + }); + } + } + + /** + * Change the register allocator config the verifier sees with one register being restricted + * from allocation. This register is chosen simply by finding the first register the allocator + * used. + */ + class DisallowedRegisterPhase extends RAVPhaseWrapper { + protected Register ignoredReg; + protected RegisterAllocationConfig regAllocConfig; + + @Override + protected void modifyVerifierInstructions(LIR lir, BlockMap> instructions, AllocationContext context) { + setFirstAllocatedRegister(lir, instructions); + var restrictedTo = getAllocationRestrictedToArray(context.registerAllocationConfig); + regAllocConfig = new RegisterAllocationConfig(context.registerAllocationConfig.getRegisterConfig(), restrictedTo); + } + + @Override + protected RegisterAllocationConfig getRegisterAllocationConfig(AllocationContext context) { + return regAllocConfig; + } + + /** + * Create allocationRestrictedTo array for {@link RegisterAllocationConfig} without the + * selected register. + * + * @param cfg Original config + * @return New allocationRestrictedTo array without ignored register + */ + protected String[] getAllocationRestrictedToArray(RegisterAllocationConfig cfg) { + var allocatableRegs = cfg.getAllocatableRegisters(); + String[] restrictedTo = new String[allocatableRegs.size() - 1]; + + int i = 0; + for (var reg : allocatableRegs) { + if (reg.equals(ignoredReg)) { + continue; + } + + restrictedTo[i++] = reg.toString(); + if (i >= allocatableRegs.size() - 1) { + break; + } + } + + return restrictedTo; + } + + /** + * Find the first register the allocator used, set it and stop. + * + * @param lir LIR + * @param instructions Verifier instructions + */ + protected void setFirstAllocatedRegister(LIR lir, BlockMap> instructions) { + scanOverInstructions(lir, instructions, (block, instruction) -> { + switch (instruction) { + case RAVInstruction.Op op -> { + for (int i = 0; i < op.dests.count; i++) { + var curr = op.dests.curr[i]; + var orig = op.dests.orig[i]; + if (curr.equals(orig)) { + continue; + } + + if (curr.isRegister()) { + ignoredReg = curr.asRegister().getRegister(); + return true; + } + } + } + case RAVInstruction.LocationMove move -> { + if (move.to.isRegister()) { + ignoredReg = move.to.asRegister().getRegister(); + return true; + } + } + default -> { + } + } + + return false; + }); + } + } + + /** + * Change output of a first instruction to a different register, so that when it's output is + * used somewhere, the state is {@link UnknownAllocationState}. This output needs to generate a + * symbol if true, no symbol is generated. + */ + class ForceUnknownStateInRegister extends RAVPhaseWrapper { + protected RAValue symbol; + protected RAValue oldLocation; + protected RAValue newLocation; + + @Override + protected void modifyVerifierInstructions(LIR lir, BlockMap> instructions, AllocationContext context) { + var regValue = getUnusedValue(lir, instructions, context); + + scanOps(lir, instructions, (block, op) -> { + for (int i = 0; i < op.dests.count; i++) { + var curr = op.dests.curr[i]; + var orig = op.dests.orig[i]; + if (curr.equals(orig)) { + continue; + } + + symbol = orig; + oldLocation = curr; + newLocation = regValue; + op.dests.curr[i] = regValue; + + return true; + } + + return false; + }); + } + } + + /** + * Change the kind of operand to trigger a {@link KindsMismatchException}, very simply, find the + * first instruction and look through its operand array to find the first variable and change + * its type to Illegal. + */ + abstract class ChangeKindPhase extends RAVPhaseWrapper { + protected Variable variable; + + @Override + protected void modifyVerifierInstructions(LIR lir, BlockMap> instructions, AllocationContext ctx) { + scanOps(lir, instructions, (block, op) -> { + var values = getTargetValueArrayPair(op); + return changeVariableKind(values); + }); + } + + protected abstract RAVInstruction.ValueArrayPair getTargetValueArrayPair(RAVInstruction.Op op); + + /** + * Change the first-found variable and change its kind to {@link ValueKind#Illegal}. + * + * @param values Values we are changing kind of + * @return true, if change occured, otherwise false. + */ + protected boolean changeVariableKind(RAVInstruction.ValueArrayPair values) { + for (int i = 0; i < values.count; i++) { + var orig = values.orig[i]; + if (!orig.isVariable()) { + continue; + } + + var raVar = orig.asVariable(); + var variable = raVar.getVariable(); + if (variable.getValueKind().equals(ValueKind.Illegal)) { + continue; + } + + // Set the kind as illegal (should trigger an exception in most cases) + values.orig[i] = RAValue.create(new Variable(ValueKind.Illegal, variable.index)); + this.variable = variable; + return true; + } + + return false; + } + } + + /** + * Change the kind for a variable that is being used as an input. + */ + class ChangeInputKindPhase extends ChangeKindPhase { + @Override + protected RAVInstruction.ValueArrayPair getTargetValueArrayPair(RAVInstruction.Op op) { + return op.uses; + } + } + + /** + * Change the kind for a variable that is being used as an output. + */ + class ChangeOutputKindPhase extends ChangeKindPhase { + @Override + protected RAVInstruction.ValueArrayPair getTargetValueArrayPair(RAVInstruction.Op op) { + return op.dests; + } + } + + /** + * First the first instruction that has alive inputs, as well as an output. Change the output + * and alive operand to use the same location so that the input operand does not survive. + */ + abstract class ViolateAliveConstraint extends RAVPhaseWrapper { + RAValue location; + + @Override + protected void modifyVerifierInstructions(LIR lir, BlockMap> instructions, AllocationContext context) { + boolean found = scanOps(lir, instructions, (block, op) -> { + if (op.alive.count == 0) { + return false; + } + + var values = getValues(op); + if (values.count == 0) { + return false; + } + + if (values.curr[0].isIllegal()) { + return false; + } + + location = op.alive.curr[0]; + values.curr[0] = location; + + return true; + }); + + if (found) { + return; + } + + /* + * Create an operation that has alive and output with same location to see that it can + * be detected, if it was not found in the input snippet. + */ + var lirInstruction = new StandardOp.NoOp(null, 0); + var op = new RAVInstruction.Op(lirInstruction); + op.temp = new RAVInstruction.ValueArrayPair(1); + op.alive = new RAVInstruction.ValueArrayPair(1); + + location = getUnusedValue(lir, instructions, context); + op.temp.curr[0] = location; + op.temp.orig[0] = location; + op.temp.operandFlags = new ArrayList<>(List.of(EnumSet.of(REG, STACK))); + op.alive.curr[0] = location; + op.alive.orig[0] = location; + op.alive.operandFlags = new ArrayList<>(List.of(EnumSet.of(REG, STACK))); + + var b0Instructions = instructions.get(0); + b0Instructions.add(b0Instructions.size() - 1, op); + } + + protected abstract RAVInstruction.ValueArrayPair getValues(RAVInstruction.Op op); + } + + /** + * Changes LIR instruction to use the same register as alive and output. + */ + class ViolateAliveConstraintInDstPhase extends ViolateAliveConstraint { + @Override + protected RAVInstruction.ValueArrayPair getValues(RAVInstruction.Op op) { + return op.dests; + } + } + + /** + * Changes LIR instruction to use the same register as alive and temporary output. + */ + class ViolateAliveConstraintInTempPhase extends ViolateAliveConstraint { + @Override + protected RAVInstruction.ValueArrayPair getValues(RAVInstruction.Op op) { + return op.temp; + } + } + + /** + * Base for creating conflict in block merge points. Uses an unused location to create a + * conflict from predecessor blocks of a conflict block. Conflict block is the merge block, this + * is where a usage of a value is inserted. The location contains + * {@link ConflictedAllocationState} from the predecessors. + */ + abstract class ConflictPhase extends RAVPhaseWrapper { + RAVariable targetVariable; + Set conflictVariables; + + @Override + protected void modifyVerifierInstructions(LIR lir, BlockMap> instructions, AllocationContext ctx) { + var conflictLocation = getUnusedValue(lir, instructions, ctx); + var conflictBlock = getFirstConflictBlock(lir); + + conflictVariables = new EconomicHashSet<>(); + for (int i = 0; i < conflictBlock.getPredecessorCount(); i++) { + var pred = conflictBlock.getPredecessorAt(i); + var instructionsForPred = instructions.get(pred); + + var idx = instructionsForPred.size() - 2; + var variable = createNewVariable(lir); + var op = createSymbolSpawnOp(variable, conflictLocation); + + // New symbol inserted into same conflicting location. + conflictVariables.add(new ValueAllocationState(variable, null, null)); + instructionsForPred.add(idx, op); + } + + targetVariable = createNewVariable(lir); + // Here the target varible will not be read, instead it will be conflicted + var usage = createSymbolUsage(targetVariable, conflictLocation); + var usageIdx = 1; + instructions.get(conflictBlock).add(usageIdx, usage); + } + + protected BasicBlock getFirstConflictBlock(LIR lir) { + for (var blockId : lir.getBlocks()) { + var block = lir.getBlockById(blockId); + if (block.getPredecessorCount() > 1 && isConflictBlock(block)) { + return block; + } + } + return null; + } + + protected abstract boolean isConflictBlock(BasicBlock block); + } + + /** + * Cause a conflict in an if-statement merge block. + */ + class DiamondConflictPhase extends ConflictPhase { + @Override + protected boolean isConflictBlock(BasicBlock block) { + return !block.isLoopHeader(); + } + } + + /** + * Cause a conflict in a loop header. + */ + class LoopConflictPhase extends ConflictPhase { + @Override + protected boolean isConflictBlock(BasicBlock block) { + return block.isLoopHeader(); + } + } + + /** + * Trigger {@link CalleeSavedRegisterNotRetrievedException}, by creating a new + * {@link RegisterAllocationConfig} with a new callee-saved register. This register will be + * overwritten by the body of the method and will not be retrieved. + * + *

+ * The selected register is dependent on the architecture, using the first argument register, + * that will be used. + *

+ */ + class CalleeSavePhase extends RAVPhaseWrapper { + Register register; + + @Override + protected void run(TargetDescription target, LIRGenerationResult lirGenRes, AllocationContext context) { + var name = target.arch.toString(); + + // Select first paramter register for this + switch (name) { + case "amd64" -> register = AMD64.rsi; + case "aarch64" -> register = AArch64.r1; + } + + super.run(target, lirGenRes, context); + } + + @Override + protected RegisterAllocationConfig getRegisterAllocationConfig(AllocationContext context) { + assert register != null : "Callee save register not selected"; + + var regCfg = context.registerAllocationConfig.getRegisterConfig(); + var newRegCfg = new RegisterConfig() { + + @Override + public Register getReturnRegister(JavaKind kind) { + return regCfg.getReturnRegister(kind); + } + + @Override + public Register getFrameRegister() { + return regCfg.getFrameRegister(); + } + + @Override + public CallingConvention getCallingConvention(CallingConvention.Type type, JavaType returnType, JavaType[] parameterTypes, ValueKindFactory valueKindFactory) { + return regCfg.getCallingConvention(type, returnType, parameterTypes, valueKindFactory); + } + + @Override + public List getCallingConventionRegisters(CallingConvention.Type type, JavaKind kind) { + return regCfg.getCallingConventionRegisters(type, kind); + } + + @Override + public List getAllocatableRegisters() { + return regCfg.getAllocatableRegisters(); + } + + @Override + public List filterAllocatableRegisters(PlatformKind kind, List registers) { + return regCfg.filterAllocatableRegisters(kind, registers); + } + + @Override + public List getCallerSaveRegisters() { + return regCfg.getCallerSaveRegisters(); + } + + @Override + public List getCalleeSaveRegisters() { + return List.of(register); + } + + @Override + public List getAttributesMap() { + return regCfg.getAttributesMap(); + } + + @Override + public boolean areAllAllocatableRegistersCallerSaved() { + return regCfg.areAllAllocatableRegistersCallerSaved(); + } + }; + + return new RegisterAllocationConfig(newRegCfg, null); + } + } + + /** + * Trigger a rematerialization of a constant to a stack slot, where its forbidden via + * {@link LoadConstOp#canRematerializeToStack()}. Triggered by inserting the scenario of a + * constant that is used in this way. + */ + class PhantomConstRematerializationPhase extends RAVPhaseWrapper { + RAVariable variableValue; + RAValue stackSlotValue; + ConstantValue constant; + + static class LoadConstOp extends LIRInstruction implements StandardOp.LoadConstantOp { + public static final LIRInstructionClass TYPE = LIRInstructionClass.create(LoadConstOp.class); + + @Def({REG, STACK}) protected AllocatableValue result; + JavaConstant input; + + LoadConstOp(AllocatableValue result, JavaConstant input) { + super(TYPE); + + this.result = result; + this.input = input; + } + + @Override + public Constant getConstant() { + return input; + } + + @Override + public boolean canRematerializeToStack() { + return false; + } + + @Override + public AllocatableValue getResult() { + return result; + } + + @Override + public void emitCode(CompilationResultBuilder crb) { + } + } + + @Override + protected void modifyVerifierInstructions(LIR lir, BlockMap> instructions, AllocationContext ctx) { + var kind = LIRKind.value(AMD64Kind.V32_BYTE); + + constant = new ConstantValue(kind, getConstant()); + var stackSlot = new SimpleVirtualStackSlot(1, kind); + + stackSlotValue = RAValue.create(stackSlot); + var constantValue = new RAVConstant(constant, false); + + var remMove = new RAVInstruction.ValueMove(new LoadConstOp(stackSlot, constant.getJavaConstant()), constant, stackSlot); + + var usage = createSymbolUsage(constantValue, stackSlotValue); + + var blockInstructions = instructions.get(0); + blockInstructions.add(blockInstructions.size() - 1, remMove); + blockInstructions.add(blockInstructions.size() - 1, usage); + } + + protected JavaConstant getConstant() { + return new JavaConstant() { + @Override + public JavaKind getJavaKind() { + return JavaKind.Int; + } + + @Override + public boolean isNull() { + return false; + } + + @Override + public boolean isDefaultForKind() { + return false; + } + + @Override + public Object asBoxedPrimitive() { + return null; + } + + @Override + public int asInt() { + return 0; + } + + @Override + public boolean asBoolean() { + return false; + } + + @Override + public long asLong() { + return 0; + } + + @Override + public float asFloat() { + return 0; + } + + @Override + public double asDouble() { + return 0; + } + }; + } + } + + /** + * Detect a violation of {@link LIRInstruction.OperandFlag} being missing/different. Done by + * finding the first instruction that has an input that is changed by the allocator. + */ + class ForceOperandFlagPhase extends RAVPhaseWrapper { + @Override + protected void modifyVerifierInstructions(LIR lir, BlockMap> instructions, AllocationContext ctx) { + scanOps(lir, instructions, (block, op) -> { + if (op.isLabel()) { + return false; + } + + for (int i = 0; i < op.uses.count; i++) { + var curr = op.uses.curr[i]; + var orig = op.uses.orig[i]; + + if (orig.equals(curr)) { + continue; + } + + EnumSet opFlags = EnumSet.noneOf(LIRInstruction.OperandFlag.class); + opFlags.addAll(op.uses.operandFlags.get(i)); + + if (curr.isRegister()) { + opFlags.remove(REG); + } else if (LIRValueUtil.isStackSlotValue(curr.getValue())) { + opFlags.remove(STACK); + } else if (LIRValueUtil.isConstantValue(curr.getValue())) { + opFlags.remove(LIRInstruction.OperandFlag.CONST); + } else { + continue; + } + + op.uses.operandFlags.set(i, opFlags); + return true; + } + + return false; + }); + } + } + + /** + * Force a missing location (the case where the allocation location is null). Again, the first + * instruction touched by the allocator is chosen. + */ + class ForceMissingLocationExceptionPhase extends RAVPhaseWrapper { + @Override + protected void modifyVerifierInstructions(LIR lir, BlockMap> instructions, AllocationContext ctx) { + scanOps(lir, instructions, (block, op) -> { + if (op.isLabel()) { + return false; + } + + for (int i = 0; i < op.uses.count; i++) { + var curr = op.uses.curr[i]; + var orig = op.uses.orig[i]; + + if (orig.equals(curr)) { + continue; + } + + op.uses.curr[i] = null; + return true; + } + + return false; + }); + } + } + + /** + * Add a new reference to a list of references. This reference will be at a location that a does + * not have a reference, this triggers {@link MissingReferenceException}. + */ + class ExcessReferencePhase extends RAVPhaseWrapper { + BasicBlock currentBlock; + RAVInstruction.Op prev; + + @Override + protected void modifyVerifierInstructions(LIR lir, BlockMap> instructions, AllocationContext ctx) { + currentBlock = null; + prev = null; + + scanOps(lir, instructions, (block, op) -> { + if (block != currentBlock) { + currentBlock = block; + prev = null; + } + + if (prev != null) { + var curr = prev.dests.curr[0]; + var kind = curr.getLIRKind().makeUnknownReference(); + var refLocation = RAValue.create(curr.asRegister().getRegister().asValue(kind)); + + op.references = new EconomicHashSet<>(); + op.references.add(refLocation); + return true; + } + + if (op.isLabel()) { + return false; + } + + if (op.dests.count == 0) { + return false; + } + + var curr = op.dests.curr[0]; + if (!curr.isRegister() || !curr.getLIRKind().isValue()) { + return false; + } + + // This instruction will define dest location + // next instruction will verify the reference list + // -> no reference there! + prev = op; + return false; + }); + } + } + + /** + * Test if a synthetically inserted variable gets its location chosen correctly by + * {@link jdk.graal.compiler.lir.alloc.verifier.FromUsageResolverGlobal}. + */ + class PhiResolver extends RAVPhaseWrapper { + RAVInstruction.Op label; + RAValue location; + + @Override + protected void modifyVerifierInstructions(LIR lir, BlockMap> instructions, AllocationContext context) { + RAValue target = getUnusedValue(lir, instructions, context); + + var labelOp = (RAVInstruction.Op) instructions.get(0).getFirst(); + + var newDst = new RAVInstruction.ValueArrayPair(labelOp.dests.count + 1); + for (int i = 0; i < labelOp.dests.count; i++) { + newDst.curr[i] = labelOp.dests.curr[i]; + newDst.orig[i] = labelOp.dests.orig[i]; + newDst.operandFlags.add(labelOp.dests.operandFlags.get(i)); + } + + var variable = createNewVariable(lir); + location = target; + + newDst.orig[labelOp.dests.count] = variable; + newDst.curr[labelOp.dests.count] = null; + newDst.operandFlags.add(EnumSet.of(REG)); + + labelOp.dests = newDst; + label = labelOp; + + var usage = createSymbolUsage(variable, location); + instructions.get(0).add(instructions.get(0).size() - 1, usage); + } + } + + /** + * Test if a reference gets deleted, if it's not tracked in the reference list. When + * {@link jdk.graal.compiler.lir.alloc.verifier.RAVInstruction.Op#references} is set (even + * empty), then only these references can survive, so an old one will get deleted. Triggers + * {@link ValueNotInRegisterException}, because it was purged. + */ + class DeleteReferencePhase extends RAVPhaseWrapper { + RAValue location; + RAValue variable; + BasicBlock currentBlock; + RAVInstruction.Op prev; + + @Override + protected void modifyVerifierInstructions(LIR lir, BlockMap> instructions, AllocationContext context) { + currentBlock = null; + prev = null; + + scanOps(lir, instructions, (block, op) -> { + if (block != currentBlock) { + currentBlock = block; + prev = null; + } + + if (prev != null && (setReferences(op.uses) || setReferences(op.alive))) { + prev.references = new EconomicHashSet<>(); + return true; + } + + if (!op.isLabel()) { + // Check fix, the previous instruction cannot define + // the reference being discarded, because then this phase + // won't work + prev = op; + } + + return false; + }); + } + + protected boolean setReferences(RAVInstruction.ValueArrayPair values) { + for (int i = 0; i < values.count; i++) { + var orig = values.orig[i]; + var curr = values.curr[i]; + + if (orig.isIllegal() || curr == null || curr.isIllegal()) { + continue; + } + + if (!orig.getLIRKind().isValue()) { + location = curr; + variable = orig; + return true; + } + } + return false; + } + } + + class ForgottenReloadPhase extends RAVPhaseWrapper { + RAValue spillSlot; + RAValue expectedReloadLocation; + RAValue redirectedReloadLocation; + + @Override + protected void modifyVerifierInstructions(LIR lir, BlockMap> instructions, AllocationContext context) { + scanOverInstructions(lir, instructions, (block, instruction) -> { + if (!(instruction instanceof RAVInstruction.Reload reload)) { + return false; + } + + if (!isLocationUsedLater(instructions, block, instruction, reload.to)) { + return false; + } + + spillSlot = reload.from; + expectedReloadLocation = reload.to; + redirectedReloadLocation = getUnusedValue(lir, instructions, context, reload.to.getLIRKind()); + + reload.to = redirectedReloadLocation; + + // LocationMove is generally used instead of specialized Reload, + // so the destination needs to be overwritten in both cases. + // This should probably be changed. + ((RAVInstruction.LocationMove) reload).to = redirectedReloadLocation; + return true; + }); + } + + /** + * Check if the location is used later. + * + * @param instructions Verifier instructions + * @param currentBlock Block where it is supposed to be used + * @param currentInstruction Instruction from which we are looking for usage + * @param location The location in question + * @return true, if it was used in this block, otherwise false + */ + protected boolean isLocationUsedLater(BlockMap> instructions, BasicBlock currentBlock, RAVInstruction.Base currentInstruction, RAValue location) { + boolean foundCurrentInstruction = false; + var instructionsForBlock = instructions.get(currentBlock); + for (var instruction : instructionsForBlock) { + if (!foundCurrentInstruction) { + if (instruction == currentInstruction) { + foundCurrentInstruction = true; + } + continue; + } + + if (usesLocation(instruction, location)) { + return true; + } + } + + return false; + } + + protected boolean usesLocation(RAVInstruction.Base instruction, RAValue location) { + if (instruction instanceof RAVInstruction.Op op) { + return isLocationInValues(op.uses, location) || isLocationInValues(op.alive, location); + } + return false; + } + + protected boolean isLocationInValues(RAVInstruction.ValueArrayPair values, RAValue location) { + for (int i = 0; i < values.count; i++) { + if (location.equals(values.curr[i])) { + return true; + } + } + + return false; + } + } + + /** + * Complete overwrite the incoming snippet code with early reuse violation. + * + *

+ * Simulate a variable being created, immediately overwritten, and then used. This throws a + * {@link ValueNotInRegisterException}, because the variable is not present, and instead a + * different value is present. + *

+ */ + class EarlyReusePhase extends RAVPhaseWrapper { + RAVariable liveVariable; + RAVariable overwrittenVariable; + RAValue location; + + @Override + protected void modifyVerifierInstructions(LIR lir, BlockMap> instructions, AllocationContext context) { + location = getUnusedValue(lir, instructions, context, LIRKind.Illegal); + + liveVariable = createNewVariable(lir, location.getLIRKind()); + overwrittenVariable = createNewVariable(lir, location.getLIRKind()); + + var changedInstructions = new ArrayList(); + changedInstructions.add(instructions.get(0).getFirst()); + changedInstructions.add(createSymbolSpawnOp(liveVariable, location)); + changedInstructions.add(createSymbolSpawnOp(overwrittenVariable, location)); + changedInstructions.add(createSymbolUsage(overwrittenVariable, location)); + changedInstructions.add(createSymbolUsage(liveVariable, location)); + + for (var blockId : lir.getBlocks()) { + var block = lir.getBlockById(blockId); + instructions.put(block, new ArrayList<>()); + } + + var startBlock = lir.getControlFlowGraph().getStartBlock(); + instructions.put(startBlock, changedInstructions); + } + } + + @Override + protected CompilationResult compile(ResolvedJavaMethod installedCodeOwner, StructuredGraph graph, CompilationResult compilationResult, CompilationIdentifier compilationId, OptionValues options) { + OptionValues newOptions; + if (disableInlining) { + // Disable any inlining to allow for function calls + newOptions = new OptionValues(options, + RegAllocVerifierPhase.Options.RAVFailOnFirst, true, + HighTier.Options.Inline, false, + BytecodeParserOptions.InlineDuringParsing, false); + } else { + newOptions = new OptionValues(options, RegAllocVerifierPhase.Options.RAVFailOnFirst, true); + } + + return super.compile(installedCodeOwner, graph, compilationResult, compilationId, newOptions); + } + + @Override + protected LIRSuites createLIRSuites(OptionValues options) { + if (validSuites) { + return createLIRSuitesWithVerifier(options); + } + + return createModifiedVerifierLIRSuites(options); + } + + /** + * Create LIR suites with verification enabled, but no tainting. This is done preemptively to + * check that the verification is passing. + */ + protected LIRSuites createLIRSuitesWithVerifier(OptionValues options) { + LIRSuites suites = super.createLIRSuites(options); + var stage = suites.getAllocationStage(); + + if (RegAllocVerifierPhase.Options.EnableRAVerifier.getValue(options)) { + var verifier = (RegAllocVerifierPhase) stage.findPhaseInstance(RegisterAllocationPhase.class); + assert verifier != null; + + var stackAllocator = verifier.getStackSlotAllocator(); + if (stackAllocator != null) { + // Do not use stack allocator + verifier.setStackSlotAllocator(null); + stage.appendPhase(stackAllocator); + } + + return suites; + } + + var it = stage.findPhase(RegisterAllocationPhase.class); + assert it != null; + + var allocator = (RegisterAllocationPhase) it.previous(); + it.set(new RegAllocVerifierPhase(allocator, null)); + + return suites; + } + + /** + * Create LIR suites with tainted verifier. + */ + protected LIRSuites createModifiedVerifierLIRSuites(OptionValues options) { + LIRSuites suites = super.createLIRSuites(options); + var stage = suites.getAllocationStage(); + + var it = stage.findPhase(RegisterAllocationPhase.class); + Assert.assertNotNull(it); + + LIRPhase stackAllocator = null; + var allocator = (RegisterAllocationPhase) it.previous(); + if (allocator instanceof RegAllocVerifierPhase rav) { + stackAllocator = rav.getStackSlotAllocator(); + if (stackAllocator != null) { + rav.setStackSlotAllocator(null); + } + + phase.setAllocator(rav.getAllocator()); + } else { + phase.setAllocator(allocator); + } + + it.set(phase); + if (stackAllocator != null) { + stage.appendPhase(stackAllocator); + } + + return suites; + } + + protected void compileModified(String name) { + validSuites = false; + compile(getResolvedJavaMethod(name), null); + validSuites = true; + } + + private T runVerifierExpectingException(String methodName, RAVPhaseWrapper testPhase, Class expectedException) { + phase = testPhase; + + compile(getResolvedJavaMethod(methodName), null); + Assert.assertNull(exception); + + compileModified(methodName); + + return assertException(expectedException); + } + + private T assertException(Class expectedException) { + Assert.assertNotNull("No exception was thrown", exception); + + RAVException actualException = exception; + if (actualException.getCause() != null) { + actualException = actualException.getCause(); + } + + Assert.assertTrue("Unexpected exception: " + actualException, expectedException.isInstance(actualException)); + return expectedException.cast(actualException); + } + + public static int simple(int a) { + return a + 1; + } + + public static int keep(int a) { + int b = 5 * a; // Make sure b is in different reg than a + return b + a; + } + + public static int diamond(int a, int b, int c, int d, int e) { + int x; + if (a > 0) { + x = b * e; + } else { + x = c + d + 2 * b; + } + + return x + a; + } + + public static int sum(int a, int b) { + int sum = 0; + for (int i = 0; i < a; i++) { + sum += i * b; + } + return sum; + } + + // toArray and arrayLengthProviderSnippet taken from ArrayLengthProviderTest + // to force dest and alive in an instruction + public static Object[] toArray(List list) { + return new Object[list.size()]; + } + + public static Object arrayLengthProviderSnippet(ArrayList list, boolean a) { + while (true) { + Object[] array = toArray(list); + if (array.length < 1) { + return null; + } + if (array[0] instanceof String || a) { + /* + * This code is outside of the loop. Accessing the array requires a ValueProxyNode. + * When the simplification of the ArrayLengthNode replaces the length access with + * the ArrayList.size used to create the array, then the value needs to have a + * ValueProxyNode too. In addition, the two parts of the if-condition actually lead + * to two separate loop exits, with two separate proxy nodes. A ValuePhiNode is + * present originally for the array, and the array length simplification needs to + * create a new ValuePhiNode for the two newly introduced ValueProxyNode. + */ + if (array.length < 1) { + return null; + } + return array[0]; + } + } + } + + // Taken from EnumSwitchTest + // to find an instruction with both temp and alive + public static int aliveConstraintSnippet(Ex e) { + switch (e) { + case E0: + return 0; + case E1: + return 1; + case E2: + return 2; + case E3: + return 3; + default: + return -1; + } + } + + public static void loop(int a, int b) { + while (true) { + if (a > 3) { + System.out.println("a = " + a); + } + + a += b; + } + } + + class A { + protected int a; + + A(int a) { + this.a = a; + } + + public void increment() { + this.a++; + } + + public int getA() { + return a; + } + } + + A obj; + + public int referenceSnippet(int a, int b) { + obj = new A(a); + while (obj.getA() < b) { + obj.increment(); + } + return obj.getA(); + } + + public static int consumeLiveValues(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) { + return a + b + c + d + e + f + g + h + i + j; + } + + public static int consumeReloadedValue(int value) { + return 3 * value + 1; + } + + /** + * Increase register pressure so that some variable is spilled and reloaded. + */ + public static int spillAndReload(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m, int n) { + int v0 = a + n; + int v1 = b + m; + int v2 = c + l; + int v3 = d + k; + int v4 = e + j; + int v5 = f + i; + int v6 = g + h; + int v7 = a + c + e; + int v8 = b + d + f; + int v9 = m + n + k; + int v10 = a + g + m; + int v11 = b + h + n; + int kept = v10 ^ v11; + int call = consumeLiveValues(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); + int reloaded = consumeReloadedValue(kept); + return call + reloaded + kept + v0 + v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10 + v11; + } + + @Before + public void prepareTest() { + exception = null; + validSuites = true; + phase = null; + disableInlining = false; + } + + @Test + public void testInvalidRegisterUsed() { + var disallowedRegPhase = new DisallowedRegisterPhase(); + var iruException = runVerifierExpectingException("simple", disallowedRegPhase, InvalidRegisterUsedException.class); + Assert.assertEquals(iruException.register, disallowedRegPhase.ignoredReg); + } + + @Test + public void testWrongVariableInState() { + var changeVariablePhase = new ChangeVariablePhase(); + var vnrException = runVerifierExpectingException("simple", changeVariablePhase, ValueNotInRegisterException.class); + + // Expected original variable + Assert.assertEquals(changeVariablePhase.originalVariable, vnrException.variable); + Assert.assertTrue(vnrException.state instanceof ValueAllocationState); + // But new variable is there instead + Assert.assertEquals(changeVariablePhase.newVariable, ((ValueAllocationState) vnrException.state).getRAValue()); + } + + @Test + public void testUnknownLocation() { + var changeLocationPhase = new ForceUnknownStateInRegister(); + var vnrException = runVerifierExpectingException("keep", changeLocationPhase, ValueNotInRegisterException.class); + Assert.assertTrue(vnrException.state.isUnknown()); + Assert.assertEquals(vnrException.variable, changeLocationPhase.symbol); + Assert.assertEquals(vnrException.location, changeLocationPhase.oldLocation); + } + + @Test + public void testConflictedLoc() { + var diamondConflictPhase = new DiamondConflictPhase(); + var vnrException = runVerifierExpectingException("diamond", diamondConflictPhase, ValueNotInRegisterException.class); + Assert.assertTrue(vnrException.state.isConflicted()); + + var confState = (ConflictedAllocationState) vnrException.state; + var conflictedStates = confState.getConflictedStates(); + Assert.assertEquals(conflictedStates, diamondConflictPhase.conflictVariables); + } + + @Test + public void testConflictInLoop() { + var loopConflictPhase = new LoopConflictPhase(); + var vnrException = runVerifierExpectingException("sum", loopConflictPhase, ValueNotInRegisterException.class); + Assert.assertTrue(vnrException.state.isConflicted()); + + var confState = (ConflictedAllocationState) vnrException.state; + Assert.assertEquals(confState.getConflictedStates(), loopConflictPhase.conflictVariables); + } + + @Test + public void testConflictInInfiniteLoop() { + var loopConflictPhase = new LoopConflictPhase(); + var vnrException = runVerifierExpectingException("loop", loopConflictPhase, ValueNotInRegisterException.class); + Assert.assertTrue(vnrException.state.isConflicted()); + + var confState = (ConflictedAllocationState) vnrException.state; + Assert.assertEquals(confState.getConflictedStates(), loopConflictPhase.conflictVariables); + } + + @Test + public void testAliveConstraintInDest() { + var violateAliveConstraintPhase = new ViolateAliveConstraintInDstPhase(); + runVerifierExpectingException("arrayLengthProviderSnippet", violateAliveConstraintPhase, AliveConstraintViolationException.class); + } + + @Test + public void testAliveConstraintInTemp() { + var violateAliveConstraintPhase = new ViolateAliveConstraintInTempPhase(); + runVerifierExpectingException("aliveConstraintSnippet", violateAliveConstraintPhase, AliveConstraintViolationException.class); + } + + @Test + public void testKindMatchAfterAlloc() { + var changeInputKindPhase = new ChangeInputKindPhase(); + var kmException = runVerifierExpectingException("keep", changeInputKindPhase, KindsMismatchException.class); + Assert.assertEquals(changeInputKindPhase.variable.index, kmException.value1.asVariable().getVariable().index); + Assert.assertEquals(changeInputKindPhase.variable.getValueKind(), kmException.value2.getValue().getValueKind()); + Assert.assertEquals(ValueKind.Illegal, kmException.value1.getValue().getValueKind()); + Assert.assertTrue(kmException.origVsCurr); + } + + @Test + public void testKindMatchInState() { + var changeInputKindPhase = new ChangeOutputKindPhase(); + var kmException = runVerifierExpectingException("keep", changeInputKindPhase, KindsMismatchException.class); + Assert.assertEquals(changeInputKindPhase.variable.index, kmException.value1.asVariable().getVariable().index); + Assert.assertEquals(changeInputKindPhase.variable.index, kmException.value2.asVariable().getVariable().index); + Assert.assertEquals(changeInputKindPhase.variable.getValueKind(), kmException.value1.getValue().getValueKind()); + Assert.assertEquals(ValueKind.Illegal, kmException.value2.getValue().getValueKind()); + Assert.assertFalse(kmException.origVsCurr); + } + + @Test + public void testCalleeSaveRetrieval() { + var calleeSavePhase = new CalleeSavePhase(); + var csException = runVerifierExpectingException("simple", calleeSavePhase, CalleeSavedRegisterNotRetrievedException.class); + Assert.assertEquals(csException.register.getRegister(), calleeSavePhase.register); + } + + @Test + public void testOperandFlags() { + var forceOperandFlagPhase = new ForceOperandFlagPhase(); + runVerifierExpectingException("simple", forceOperandFlagPhase, OperandFlagMismatchException.class); + } + + @Test + public void testMissingLocation() { + var forceMissingLocationException = new ForceMissingLocationExceptionPhase(); + runVerifierExpectingException("simple", forceMissingLocationException, MissingLocationException.class); + } + + @Test + public void testExcessReference() { + var forceExcessReference = new ExcessReferencePhase(); + runVerifierExpectingException("simple", forceExcessReference, MissingReferenceException.class); + } + + @Test + public void testEarlyReuse() { + var earlyReusePhase = new EarlyReusePhase(); + // The phase overwrites the method here + var vnrException = runVerifierExpectingException("simple", earlyReusePhase, ValueNotInRegisterException.class); + + Assert.assertEquals(earlyReusePhase.liveVariable, vnrException.variable); + Assert.assertEquals(earlyReusePhase.location, vnrException.location); + Assert.assertTrue(vnrException.state instanceof ValueAllocationState); + Assert.assertEquals(earlyReusePhase.overwrittenVariable, ((ValueAllocationState) vnrException.state).getRAValue()); + } + + @Test + public void testForgottenReload() { + disableInlining = true; + var forgottenReloadPhase = new ForgottenReloadPhase(); + var vnrException = runVerifierExpectingException("spillAndReload", forgottenReloadPhase, ValueNotInRegisterException.class); + + Assert.assertEquals(vnrException.location, forgottenReloadPhase.expectedReloadLocation); + } + + @Test + public void testPhiResolver() { + var phiResolver = new PhiResolver(); + phase = phiResolver; + + var methodName = "simple"; + compile(getResolvedJavaMethod(methodName), null); + + Assert.assertNull(exception); + + compileModified(methodName); + + Assert.assertNull(exception); + Assert.assertEquals(phiResolver.label.dests.curr[phiResolver.label.dests.count - 1], phiResolver.location); + } + + @Test + public void testConstRematerializer() { + var constRemPhase = new PhantomConstRematerializationPhase(); + var crException = runVerifierExpectingException("simple", constRemPhase, ConstantRematerializedToStackException.class); + Assert.assertEquals(constRemPhase.stackSlotValue, crException.location); + Assert.assertEquals(constRemPhase.constant, crException.state.getValue()); + } + + @Test + public void testDeleteReference() { + var deleteReferencePhase = new DeleteReferencePhase(); + var vnrException = runVerifierExpectingException("referenceSnippet", deleteReferencePhase, ValueNotInRegisterException.class); + Assert.assertEquals(vnrException.variable, deleteReferencePhase.variable); + Assert.assertEquals(vnrException.location, deleteReferencePhase.location); + } +} + +// Taken from EnumSwitchTest, shortened +enum Ex { + E0, + E1, + E2, + E3, +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/AllocationState.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/AllocationState.java new file mode 100644 index 000000000000..b75499274f0a --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/AllocationState.java @@ -0,0 +1,79 @@ +/* + * 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; + +/** + * Interface for state concrete location is in, stored in {@link AllocationStateMap}. + */ +public abstract class AllocationState implements Cloneable { + /** + * Get the default allocation state for every location, instead of null, we have + * {@link UnknownAllocationState unknown} state. + * + * @return Default state for every location + */ + public static AllocationState getDefault() { + return UnknownAllocationState.INSTANCE; + } + + /** + * Shortcut to check if state is {@link UnknownAllocationState unknown}. + * + * @return Is {@link UnknownAllocationState unknown state} + */ + public boolean isUnknown() { + return false; + } + + /** + * Shortcut to check if state is {@link ConflictedAllocationState conflicted}. + * + * @return Is {@link ConflictedAllocationState conflicted} + */ + public boolean isConflicted() { + return false; + } + + /** + * Create a copy of this state, necessary for state copies made over program graph edges. + * + * @return Newly copied state + */ + @Override + public abstract AllocationState clone(); + + /** + * Meet a state from a different block coming from an edge in the program graph, decide what the + * result of said two states should be. + * + * @param other The other state coming from a predecessor edge + * @param otherBlock Which block is other state from + * @param block Which state is this state from? + * @return New state if the current one needs to be changed, null otherwise. + */ + public abstract AllocationState meet(AllocationState other, BasicBlock otherBlock, BasicBlock block); +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/AllocationStateMap.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/AllocationStateMap.java new file mode 100644 index 000000000000..06cef281bf2b --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/AllocationStateMap.java @@ -0,0 +1,217 @@ +/* + * 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.lir.alloc.verifier.exceptions.InvalidRegisterUsedException; +import jdk.graal.compiler.util.EconomicHashMap; +import jdk.graal.compiler.util.EconomicHashSet; + +import java.util.Map; +import java.util.Set; + +/** + * Mapping between a location and allocation state that stores one of these: + *
    + *
  • {@link UnknownAllocationState unknown} - our null state, nothing was stored yet
  • + *
  • {@link ValueAllocationState value} - symbol that is stored at said location
  • + *
  • {@link ConflictedAllocationState conflicted} - set of Values that are supposed to be at same + * location
  • + *
+ * + *

+ * Conflicts are resolved by assigning new {@link ValueAllocationState value} to same location. + * Otherwise, they cannot be used. {@link ValueAllocationState Value} can store register, stack + * slot, constant, but most importantly variables used before allocation. These are what we are + * checking with the verification process. + *

+ */ +public class AllocationStateMap { + protected final BasicBlock block; + + /** + * Internal map maintaining the mapping. + */ + protected final Map internalMap; + + /** + * Register allocation config describing which registers can be used. + */ + protected final RegisterAllocationConfig registerAllocationConfig; + + public AllocationStateMap(BasicBlock block, RegisterAllocationConfig registerAllocationConfig) { + internalMap = new EconomicHashMap<>(); + this.block = block; + this.registerAllocationConfig = registerAllocationConfig; + } + + public AllocationStateMap(BasicBlock block, AllocationStateMap other) { + internalMap = new EconomicHashMap<>(other.internalMap); + registerAllocationConfig = other.registerAllocationConfig; + this.block = block; + } + + public boolean has(RAValue key) { + return internalMap.containsKey(key); + } + + public AllocationState get(RAValue key) { + return this.internalMap.getOrDefault(key, AllocationState.getDefault()); + } + + /** + * Put a new state for location to the map, while checking if register can be allocated to. + * + * @param key Location used + * @param state State to store + */ + public void put(RAValue key, AllocationState state, RAVInstruction.Base instruction) { + this.checkRegisterDestinationValidity(key, instruction); + putWithoutRegCheck(key, state); + } + + /** + * Put a new state for location to the map, without checking if the register can actually be + * used. + * + *

+ * This is useful for registers that are used by the ABI in the first label but can actually + * never be changed, like rbp. + *

+ * + * @param key Location used + * @param state State to store + */ + public void putWithoutRegCheck(RAValue key, AllocationState state) { + if (state.isUnknown()) { + internalMap.remove(key); // Do not propagate unknown further + } + + internalMap.put(key, state); + } + + /** + * Put a copied state to a location, used when merging. + * + * @param key Location used + * @param state State to store + */ + public void putClone(RAValue key, AllocationState state, RAVInstruction.Base instruction) { + if (state.isUnknown()) { + this.put(key, state, instruction); + return; + } + + this.put(key, state.clone(), instruction); + } + + /** + * Get the set of locations holding this particular variable/constant. + * + * @param value Symbol we are looking for + * @return Set of locations holding the symbol + */ + public Set getValueLocations(RAValue value) { + Set locations = new EconomicHashSet<>(); + for (var entry : this.internalMap.entrySet()) { + if (entry.getValue() instanceof ValueAllocationState valState) { + if (valState.getRAValue().equals(value)) { + locations.add(entry.getKey()); + } + } + } + return locations; + } + + /** + * Merge two maps, a source is generally the predecessor to the current block (this state map). + * + * @param source Predecessor merging to here + * @return Was this map changed? + */ + public boolean mergeWith(AllocationStateMap source) { + boolean changed = false; + for (var entry : source.internalMap.entrySet()) { + var location = entry.getKey(); + var incomingState = entry.getValue(); + if (!this.internalMap.containsKey(location)) { + if (incomingState.isUnknown()) { + continue; // Unknown and Unknown can be skipped + } + + changed = true; + + this.putWithoutRegCheck(location, incomingState.clone()); + continue; + } + + var currentState = this.internalMap.get(location); + var newState = currentState.meet(incomingState, source.block, this.block); + if (newState != null) { + changed = true; + + this.putWithoutRegCheck(location, newState); + } + } + + // Process remaining locations from our map that have not yet been processed. + for (var entry : this.internalMap.entrySet()) { + var location = entry.getKey(); + if (source.internalMap.containsKey(location) || entry.getValue().isUnknown()) { + // Only care about unprocessed locations + continue; + } + + var currentState = entry.getValue(); + var resultState = currentState.meet(UnknownAllocationState.INSTANCE, source.block, this.block); + if (resultState != null) { + changed = true; + + entry.setValue(resultState); + } + } + + return changed; + } + + /** + * Check if register can be used by the register allocator. If not allowed, an exception is + * thrown. + * + * @param location Value that could be a register. + */ + protected void checkRegisterDestinationValidity(RAValue location, RAVInstruction.Base instruction) { + if (!location.isRegister()) { + return; + } + + // Equality check so we know that this change was made by the register allocator. + var register = location.asRegister().getRegister(); + if (!this.registerAllocationConfig.getAllocatableRegisters().contains(register)) { + throw new InvalidRegisterUsedException(register, instruction, block); + } + } +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/BlockVerifierState.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/BlockVerifierState.java new file mode 100644 index 000000000000..7b2bf069167d --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/BlockVerifierState.java @@ -0,0 +1,781 @@ +/* + * 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.core.common.alloc.RegisterAllocationConfig; +import jdk.graal.compiler.core.common.cfg.BasicBlock; +import jdk.graal.compiler.debug.GraalError; +import jdk.graal.compiler.lir.CastValue; +import jdk.graal.compiler.lir.LIRInstruction; +import jdk.graal.compiler.lir.LIRValueUtil; +import jdk.graal.compiler.lir.alloc.verifier.exceptions.AliveConstraintViolationException; +import jdk.graal.compiler.lir.alloc.verifier.exceptions.CalleeSavedRegisterNotRetrievedException; +import jdk.graal.compiler.lir.alloc.verifier.exceptions.ConstantRematerializedToStackException; +import jdk.graal.compiler.lir.alloc.verifier.exceptions.JavaKindReferenceMismatchException; +import jdk.graal.compiler.lir.alloc.verifier.exceptions.KindsMismatchException; +import jdk.graal.compiler.lir.alloc.verifier.exceptions.MissingLocationException; +import jdk.graal.compiler.lir.alloc.verifier.exceptions.MissingReferenceException; +import jdk.graal.compiler.lir.alloc.verifier.exceptions.OperandFlagMismatchException; +import jdk.graal.compiler.lir.alloc.verifier.exceptions.RAVException; +import jdk.graal.compiler.lir.alloc.verifier.exceptions.ValueNotInRegisterException; +import jdk.graal.compiler.lir.dfa.LocationMarker; +import jdk.vm.ci.code.BytecodeFrame; +import jdk.vm.ci.code.ValueUtil; +import jdk.vm.ci.meta.AllocatableValue; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.Value; +import jdk.vm.ci.meta.ValueKind; + +/** + * Verification state a block is in, holds a mapping between locations and their allocation states, + * which can be unknown, value (with symbol content) or conflicted; multiple values conflict with + * each other. + */ +public class BlockVerifierState { + /** + * Map maintaining mapping between locations and their state. + */ + public final AllocationStateMap values; + + /** + * Register allocation config we use to check if only allocatable registers are the ones used. + */ + protected final RegisterAllocationConfig registerAllocationConfig; + + /** + * Block this state pertains to. + */ + protected final BasicBlock block; + + protected final CalleeSaveMap calleeSaveMap; + + public BlockVerifierState(BasicBlock block, RegisterAllocationConfig registerAllocationConfig, + CalleeSaveMap calleeSaveMap) { + this.values = new AllocationStateMap(block, registerAllocationConfig); + this.registerAllocationConfig = registerAllocationConfig; + this.calleeSaveMap = calleeSaveMap; + this.block = block; + } + + public BlockVerifierState(BasicBlock block, BlockVerifierState other) { + this.registerAllocationConfig = other.registerAllocationConfig; + this.values = new AllocationStateMap(block, other.values); + this.calleeSaveMap = other.calleeSaveMap; + this.block = block; + } + + public AllocationStateMap getValues() { + return values; + } + + /** + * Merge states of block and it's predecessor. This process modifies the current state based on + * the contents of the predecessor, creating conflicts where current locations do not match. + * + * @param other Predecessor of this block + * @return Was this state changed? + */ + public boolean meetWith(BlockVerifierState other) { + return this.values.mergeWith(other.getValues()); + } + + /** + * Check state values stored in LIRFrameState same way other operands, except we skip those + * where the state values are incorrectly stored, after stack allocation, because some values + * are no longer present. + * + * @param op Operation for which we are checking state values + */ + protected void checkStateValues(RAVInstruction.Op op) { + if (!op.hasCompleteState()) { + /* + * Some values are null after allocation because of stack slot allocator because it is + * skipped when iteration (StackLockValue). + */ + return; + } + + checkInputs(op.stateValues, op); + } + + /** + * Verify the correspondence of original variables used in instructions are stored in the state + * of current locations. + * + * @param valuePairs Array of pairs of current location and original variable. + * @param op Operation this input array of values belongs to + */ + protected void checkInputs(RAVInstruction.ValueArrayPair valuePairs, RAVInstruction.Op op) { + // Check that incoming values are not unknown or conflicted - these only matter if used + for (int idx = 0; idx < valuePairs.count; idx++) { + checkOperand(valuePairs.orig[idx], valuePairs.curr[idx], op); + } + } + + /** + * Check that original variable matches symbol stored at the current location in the + * {@link AllocationStateMap}. + * + *

+ * We also check that kinds match and possibly rematerialize variables at this point in the + * state map. + *

+ * + * @param orig Original variable + * @param curr Current location + * @param op Operation where these are used + */ + protected void checkOperand(RAValue orig, RAValue curr, RAVInstruction.Op op) { + assert orig != null; + + if (curr == null) { + if (op.isJump()) { + /* + * This can happen if a variable without a usage is passed in even when this + * variable acts as an alias to the next label, there's no usage, so no location. + */ + return; + } + + throw new MissingLocationException(op, block, orig); + } + + ValueKind currKind = curr.getValue().getValueKind(); + if (!kindsEqualBetweenPreAndPostAlloc(orig.getValue().getValueKind(), currKind)) { + throw new KindsMismatchException(op, block, orig, curr, true); + } + + AllocationState state = this.values.get(curr); + if (orig.equals(curr)) { + /* + * Whenever both the original symbol and current location are equal, we do not do any + * further checking, as there is no symbol to check. This happens for instructions like + * function calls, which have their inputs set to concrete locations before allocation. + * + * The state here can be unknown, some value or in conflict. + */ + return; + } + + if (ValueUtil.isStackSlot(curr.getValue()) && LIRValueUtil.isVirtualStackSlot(orig.getValue())) { + /* + * For the same reason as a previous statement, if vstack slot was present before + * allocation and stack allocator have run, then we need to check for a vstack and stack + * combination. Both are considered locations, and so we do not have a symbol to check + * for. + * + * Test case IntegerDivRemCanonicalizationTest, has instruction: r10|QWORD = STACKLEA + * slot: stack:80|ILLEGAL[*] in B0, where vstack:0 was present before stack allocation. + */ + return; + } + + if (state.isUnknown()) { + throw new ValueNotInRegisterException(op, block, orig, curr, state, this); + } + + if (state.isConflicted()) { + throw new ValueNotInRegisterException(op, block, orig, curr, state, this); + } + + if (state instanceof ValueAllocationState valAllocState) { + if (!valAllocState.value.equals(orig)) { + throw new ValueNotInRegisterException(op, block, orig, curr, state, this); + } + + if (!kindsEqualFromState(orig, valAllocState)) { + throw new KindsMismatchException(op, block, orig, valAllocState.value, false); + } + + if (orig.isConstant()) { + var constant = orig.asConstant(); + checkMaterializationLocation(constant, valAllocState); + } + + return; + } + + throw GraalError.shouldNotReachHere("Invalid state " + state); + } + + protected void checkMaterializationLocation(RAVConstant constant, ValueAllocationState state) { + if (constant.canRematerializeToStack) { + return; + } + + var source = state.getSource(); + if (source instanceof RAVInstruction.ValueMove move) { + var location = move.getLocation(); + if (LIRValueUtil.isStackSlotValue(location.getValue())) { + throw new ConstantRematerializedToStackException(constant, move.getLocation(), state); + } + } + } + + protected boolean kindsEqualBetweenPreAndPostAlloc(RAValue orig, RAValue curr) { + var origKind = orig.getValue().getValueKind(); + var currKind = curr.getValue().getValueKind(); + return kindsEqualBetweenPreAndPostAlloc(origKind, currKind); + } + + /** + * Are kinds equal even when {@link LIRKindWithCast casting} is present? + * + * @param origInputKind Original variable kind + * @param currInputKind Current location kind + * @return Are they equal? + */ + protected boolean kindsEqualBetweenPreAndPostAlloc(ValueKind origInputKind, ValueKind currInputKind) { + ValueKind origKind; + if (origInputKind instanceof LIRKindWithCast castKind) { + origKind = castKind.getActualKind(); + } else { + origKind = origInputKind; + } + + ValueKind currKind; + if (currInputKind instanceof LIRKindWithCast castKind) { + // Original symbol was defined with the actual kind + // that is being cast from with current location, + // so we check kinds against that. + currKind = castKind.getActualKind(); + } else { + currKind = currInputKind; + } + + return currKind.equals(origKind); + } + + /** + * Are kinds equal even when {@link CastValue cast value} is present? + * + *

+ * We need to ignore the cast value because the currently stored value will not be cast. + *

+ * + * @param orig Original variable + * @param valAllocState State we are checking against + * @return Are they equal? + */ + protected boolean kindsEqualFromState(RAValue orig, ValueAllocationState valAllocState) { + LIRKind stateKind = valAllocState.getKind(); + ValueKind origKind = orig.getLIRKind(); + if (LIRValueUtil.isCast(orig.getValue())) { + origKind = LIRValueUtil.uncast(orig.getValue()).getValueKind(); + } + + return origKind.equals(stateKind); + } + + /** + * Check that all instruction arrays of pairs of original variable and current location check + * out to the state stored in for this block. + * + * @param instruction Instruction we are checking + */ + public void check(RAVInstruction.Base instruction) { + if (instruction instanceof RAVInstruction.Op op) { + checkInputs(op.uses, op); + checkInputs(op.alive, op); + checkStateValues(op); + + checkTempKind(op); + checkAliveConstraint(op); + + checkOperandFlags(op.dests, op); + checkOperandFlags(op.uses, op); + checkOperandFlags(op.alive, op); + checkOperandFlags(op.temp, op); + + checkBytecodeFrames(op); + + if (op.references != null) { + checkReferences(op); + } + } else if (instruction instanceof RAVInstruction.LocationMove move) { + checkLocationMoveKinds(move); + } + } + + /** + * Iterate over references collected by ReferenceBuilder and check if said locations actually + * contain a reference in the state map. + * + * @param op Operation containing references for GC + */ + protected void checkReferences(RAVInstruction.Op op) { + // ReferenceSet makes sure that contents are references only + for (RAValue reference : op.references) { + var state = values.get(reference); + if (state.isConflicted()) { + var confState = (ConflictedAllocationState) state; + for (var valAllocState : confState.getConflictedStates()) { + if (valAllocState.isUndefinedFromBlock()) { + continue; // Undefined in branch + } + + if (valAllocState.isReference()) { + continue; // State holds a reference + } + + throw new MissingReferenceException(op, block, reference, state, this); + } + + continue; + } + + if (state.isUnknown()) { + throw new MissingReferenceException(op, block, reference, state, this); + } + + var valAllocState = (ValueAllocationState) state; + if (valAllocState.isReference()) { + continue; // State holds a reference + } + + throw new MissingReferenceException(op, block, reference, state, this); + } + } + + /** + * Check that a move destination has the correct kind to store the {@link ValueAllocationState + * value}. + * + * @param move Move between locations, inserted by register allocator + */ + protected void checkLocationMoveKinds(RAVInstruction.LocationMove move) { + AllocationState state = this.values.get(move.from); + if (state instanceof ValueAllocationState valueAllocationState) { + RAValue movedValue = valueAllocationState.getRAValue(); + if (!movedValue.getLIRKind().getPlatformKind().equals(move.to.getLIRKind().getPlatformKind())) { + throw new KindsMismatchException(move, block, move.to, movedValue, false); + } + } + } + + /** + * Check {@link BytecodeFrame frames}, before and after allocation, mainly checking that + * {@link LIRKind} is a reference when {@link JavaKind} is an Object and wise-versa, checking + * that {@link LIRKind} is not a reference when {@link JavaKind} is not an object. + * + * @param op Operation holding said frames + * @throws RAVException when a violation occurs + */ + public void checkBytecodeFrames(RAVInstruction.Op op) { + for (var frame : op.bcFrames) { + for (int i = 0; i < frame.kinds.length; i++) { + var origJV = frame.orig[i]; + if (!(origJV instanceof AllocatableValue orig) || Value.ILLEGAL.equals(orig)) { + continue; + } + + var currJV = frame.curr[i]; + if (!(currJV instanceof AllocatableValue curr) || Value.ILLEGAL.equals(curr)) { + continue; + } + + var kind = frame.kinds[i]; + if (JavaKind.Long.equals(kind)) { + /* + * Skipping long(s) because it can be a numeric value or a derived reference / + * native pointer + */ + continue; + } + + var origLIRKind = orig.getValueKind(LIRKind.class); + var currLIRKind = curr.getValueKind(LIRKind.class); + if (JavaKind.Object.equals(kind)) { + if (!origLIRKind.isValue() && !currLIRKind.isValue()) { + continue; + } + + throw new JavaKindReferenceMismatchException(orig, curr, kind, op, block); + } else { + if (origLIRKind.isValue() && currLIRKind.isValue()) { + // Either not a reference or a derived one - which might not be marked as + // Object + continue; + } + + throw new JavaKindReferenceMismatchException(orig, curr, kind, op, block); + } + } + } + } + + /** + * Check if kinds in the {@link RAVInstruction.Op#temp temporary array} match before allocation + * original variables with after allocation concrete locations. + * + * @param op Instruction we update state from + * @throws KindsMismatchException if a pair does not match + */ + protected void checkTempKind(RAVInstruction.Op op) { + for (int i = 0; i < op.temp.count; i++) { + var curr = op.temp.curr[i]; + var orig = op.temp.orig[i]; + + if (!kindsEqualBetweenPreAndPostAlloc(orig, curr)) { + // Make sure the assigned register has the correct kind for temp. + throw new KindsMismatchException(op, block, orig, curr, true); + } + } + } + + /** + * Check if alive constraint is not being violated, when one location is supposed to be alive + * after instruction is complete, but is used either as an output or a generic input. + * + * @param instruction Instruction with alive inputs + * @throws AliveConstraintViolationException throw when violation occurs + */ + protected void checkAliveConstraint(RAVInstruction.Op instruction) { + for (int i = 0; i < instruction.alive.count; i++) { + RAValue value = instruction.alive.curr[i]; + if (value == null) { + continue; + } + + if (value.isIllegal()) { + continue; + } + + for (int j = 0; j < instruction.temp.count; j++) { + if (value.equals(instruction.temp.curr[j])) { + throw new AliveConstraintViolationException(instruction, block, value, false); + } + } + + for (int j = 0; j < instruction.dests.count; j++) { + if (value.equals(instruction.dests.curr[j])) { + throw new AliveConstraintViolationException(instruction, block, value, true); + } + } + } + } + + /** + * Make sure concrete current locations changed by the allocator are not violating a set of + * {@link jdk.graal.compiler.lir.LIRInstruction.OperandFlag flags}, which specify what type can + * they be. This is done on every array of pairs (dest, uses, alive, temp). + * + * @param valuePairs Value array pair we are verifying + * @param op Instruction that holds this array, for tracing in exceptions + * @throws OperandFlagMismatchException Operand is a wrong type based on OperandFlag set. + */ + protected void checkOperandFlags(RAVInstruction.ValueArrayPair valuePairs, RAVInstruction.Op op) { + for (int i = 0; i < valuePairs.count; i++) { + var curr = valuePairs.curr[i]; + if (curr == null) { + continue; + } + + var flags = valuePairs.operandFlags.get(i); + var currValue = curr.getValue(); + if (LIRValueUtil.isStackSlotValue(currValue)) { + if (flags.contains(LIRInstruction.OperandFlag.STACK)) { + continue; + } + } else if (ValueUtil.isRegister(currValue)) { + if (flags.contains(LIRInstruction.OperandFlag.REG)) { + continue; + } + } else if (LIRValueUtil.isConstantValue(currValue)) { + if (flags.contains(LIRInstruction.OperandFlag.CONST)) { + continue; + } + } else if (Value.ILLEGAL.equals(currValue)) { + if (flags.contains(LIRInstruction.OperandFlag.ILLEGAL)) { + continue; + } + } + + throw new OperandFlagMismatchException(op, block, currValue, flags); + } + } + + /** + * Update the current state based on outputs of this instruction. Setting contents of current + * location in {@link AllocationStateMap} to the symbol that was present before allocation was + * completed. + * + * @param instruction Instruction we update state from + */ + public void update(RAVInstruction.Base instruction) { + switch (instruction) { + case RAVInstruction.Op op -> this.updateWithOp(op); + case RAVInstruction.ValueMove virtMove -> this.updateWithValueMove(virtMove); + case RAVInstruction.LocationMove move -> this.updateWithLocationMove(move); + default -> throw GraalError.shouldNotReachHere("Invalid RAV instruction " + instruction); + } + } + + public void updateWithLocationMove(RAVInstruction.LocationMove move) { + if (move instanceof RAVInstruction.StackMove stackMove) { + // Maybe the backup slot should hold what the scratch register holds? + this.values.put(stackMove.backupSlot, UnknownAllocationState.INSTANCE, move); + } + + var state = this.values.get(move.from); + if (state instanceof ValueAllocationState valueAllocationState) { + var movedValue = valueAllocationState.getRAValue(); + if (!movedValue.getLIRKind().equals(move.to.getLIRKind())) { + state = new ValueAllocationState(valueAllocationState, move.to.getLIRKind()); + } + } + + if (move.validateRegisters) { + this.values.putClone(move.to, state, move); + } else { + this.values.putWithoutRegCheck(move.to, state.clone()); + } + } + + /** + * Update the state using a generic operation, based on contents of its + * {@link RAVInstruction.Op#dests output array}, storing symbols pre-allocation to current + * locations after allocation. + * + * @param op Operation we update state from + */ + protected void updateWithOp(RAVInstruction.Op op) { + if (op.references != null) { + /* + * First we remove unknown references, then we define new values by the return value + */ + updateWithSafePoint(op); + } + + if (canCastOpToMove(op)) { + /* + * Moves present before the allocation can also be treated same way the one inserted by + * the allocator + */ + RAVInstruction.LocationMove locMove = castMove(op); + updateWithLocationMove(locMove); + return; + } + + /* + * For calls, temp lists all registers that are supposed to be caller saved, because + * sometimes there's a difference between RegisterConfig.getCallerSaveRegisters + * + * These registers need to be set to unknown before output registers, in case some of them + * are also set as an output. + */ + for (int i = 0; i < op.temp.count; i++) { + var value = op.temp.curr[i]; + if (value.isIllegal()) { + continue; + } + + // We cannot believe the contents of registers used as temp, thus we need to reset. + RAValue location = op.temp.curr[i]; + + if (op.temp.orig[i].equals(location)) { + this.values.putWithoutRegCheck(location, UnknownAllocationState.INSTANCE); + } else { + this.values.put(location, UnknownAllocationState.INSTANCE, op); + } + } + + for (int i = 0; i < op.dests.count; i++) { + if (op.dests.orig[i].isIllegal()) { + continue; // Safe to ignore when destination is illegal value, not when used. + } + + assert op.dests.orig[i] != null; + + if (op.dests.curr[i] == null && op.isLabel()) { + continue; // Unused label variable + } + + assert op.dests.curr[i] != null; + + RAValue location = op.dests.curr[i]; + RAValue variable = op.dests.orig[i]; + + if (location.equals(variable)) { + /* + * Only check register validity if it was changed by the register allocator, for + * example, rbp is used as input to start block and forbidden to be used by the + * allocator + */ + this.values.putWithoutRegCheck(location, new ValueAllocationState(variable, op, block)); + } else { + this.values.put(location, new ValueAllocationState(variable, op, block), op); + } + } + + if (op.isLabel() && block.getId() == 0) { + updateCalleeSavedRegisters(); + } + } + + /** + * Moves not inserted by the register allocator were previously treated as generic Operations, + * but some of them can be cast back to a move to increase the accuracy of the verification + * process. + * + *

+ * All moves where the destination is the same location before and after the allocation, or + * vstack/stack combination can be cast to a move. + *

+ * + * @param op the operation being cast + * @return true, if op can be cast to a move + */ + protected boolean canCastOpToMove(RAVInstruction.Op op) { + if (!op.lirInstruction.isMoveOp() || op.dests.count != 1 || op.uses.count != 1) { + return false; + } + + return op.dests.curr[0].equals(op.dests.orig[0]) || (ValueUtil.isStackSlot(op.dests.curr[0].getValue()) && LIRValueUtil.isVirtualStackSlot(op.dests.orig[0].getValue())); + } + + protected RAVInstruction.LocationMove castMove(RAVInstruction.Op op) { + return new RAVInstruction.LocationMove(op.lirInstruction, op.uses.curr[0].getValue(), op.dests.curr[0].getValue(), false); + } + + /** + * Update the block state with a safe point list of live references deemed by the GC; any other + * references not included in the said list are to be set as unknown, so there's no freed + * pointer use. + * + *

+ * References need to be retrieved using {@link LocationMarker} classes. + *

+ * + * @param op SafePoint we are using to remove old references + */ + protected void updateWithSafePoint(RAVInstruction.Op op) { + for (var entry : this.values.internalMap.entrySet()) { + var state = entry.getValue(); + if (state.isUnknown() || state.isConflicted()) { + continue; // Retain information + } + + var valueAllocState = (ValueAllocationState) state; + if (Value.ILLEGAL.equals(valueAllocState.getValue()) || !valueAllocState.isReference()) { + continue; // Not a reference, continue + } + + if (op.references.contains(entry.getKey())) { + continue; + } + + /* + * Remove all references that are not present in the reference list; maybe it makes + * sense to keep registers that have live references, that are same as the one in the + * reference list? Because the list is expected to have stack slots and registers can + * retain the same references. + */ + entry.setValue(new ValueAllocationState(new RAValue(Value.ILLEGAL), op, block)); + } + } + + /** + * Take the list of callee saved registers and add them to the start block state with their own + * values as symbols in order to check that they were correctly retrieved at an exit point. + */ + protected void updateCalleeSavedRegisters() { + var registers = this.registerAllocationConfig.getRegisterConfig().getCalleeSaveRegisters(); + if (registers == null) { + return; + } + + for (var reg : registers) { + var regValue = calleeSaveMap.createCalleeSavedRegister(reg.asValue()); + + var presentState = values.get(regValue); + if (presentState instanceof ValueAllocationState valueAllocationState) { + // Keep the old value from the label but save it for check at exit point. + if (!valueAllocationState.getRAValue().equals(regValue)) { + // If the symbol is the same register, then override it with CalleeSaveRegister + calleeSaveMap.addValue(regValue, valueAllocationState.getRAValue()); + continue; + } + + // Keep the kind here! + var lirRegValue = ValueUtil.asRegisterValue(valueAllocationState.getValue()); + regValue = calleeSaveMap.createCalleeSavedRegister(lirRegValue); + } + + // Save same registers as symbol, and later check if it was retrieved + var state = new ValueAllocationState(regValue, null, block); + this.values.putWithoutRegCheck(regValue, state); + } + } + + /** + * At exit point, check that all callee saved registers were indeed correctly saved, by checking + * that the symbol stored in said registers is equal to the registers themselves. + * + * @throws RAVException when callee saved register was not recovered + */ + protected void checkCalleeSavedRegisters() { + var registers = this.calleeSaveMap.getCalleeSaveRegisters(); + if (registers == null) { + return; + } + + for (var reg : registers) { + var regValue = (RAVRegister) RAVRegister.create(reg.asValue()); + var state = this.values.get(regValue); + if (state instanceof ValueAllocationState valueAllocationState) { + var stateValue = valueAllocationState.getRAValue(); + var calleeSavedValue = calleeSaveMap.getCalleeSavedValue(regValue); + if (stateValue.equals(calleeSavedValue) && stateValue.getLIRKind().equals(calleeSavedValue.getLIRKind())) { + /* + * The same symbol as register means the value was retrieved safely. Kinds also + * need to match + */ + continue; + } + } + + throw new CalleeSavedRegisterNotRetrievedException(regValue, block, this); + } + } + + /** + * Update state with a {@link RAVInstruction.ValueMove}, if location is concrete, we set it to a + * variable/constant, if it's a variable to variable move, then all locations containing old + * variable need to be changed to the new variable. + * + * @param valueMove move we update state from + */ + protected void updateWithValueMove(RAVInstruction.ValueMove valueMove) { + var state = new ValueAllocationState(valueMove.constant, valueMove, block); + if (valueMove.validateRegisters) { + this.values.put(valueMove.getLocation(), state, valueMove); + } else { + this.values.putWithoutRegCheck(valueMove.getLocation(), state); + } + } +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/CalleeSaveMap.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/CalleeSaveMap.java new file mode 100644 index 000000000000..225ceeef2c75 --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/CalleeSaveMap.java @@ -0,0 +1,137 @@ +/* + * 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 java.util.List; +import java.util.Map; + +import jdk.graal.compiler.util.EconomicHashMap; +import jdk.vm.ci.code.Register; +import jdk.vm.ci.code.RegisterConfig; +import jdk.vm.ci.code.RegisterValue; + +/** + * This class helps with checking that callee-saved register got retrieved at an exit block, in + * {@link BlockVerifierState#checkCalleeSavedRegisters()}. + * + *

+ * We save values that are supposed to be retrieved in a map for each callee-saved register. + *

+ * + *

+ * It can be an instance of {@link CalleeSavedRAVRegister} which is just a special class denoting + * that this value is the callee-saved value saved in start block, before first instruction using + * {@link BlockVerifierState#updateCalleeSavedRegisters}. + *

+ * + *

+ * Or it can be a {@link RAValue} that was set by the first label, meaning we are preserving + * parameter that was received. + *

+ */ +public class CalleeSaveMap { + protected final RegisterConfig registerConfig; + protected final Map virtualValues; + + public CalleeSaveMap(RegisterConfig registerConfig) { + this.registerConfig = registerConfig; + virtualValues = new EconomicHashMap<>(); + } + + public class CalleeSavedRAVRegister extends RAVRegister { + protected CalleeSavedRAVRegister(RegisterValue registerValue) { + super(registerValue); + } + + @Override + public String toString() { + return "CalleeSaved " + super.toString(); + } + } + + /** + * Create a callee saved register this is signaling that this value needs to be retrieved on + * exit point, not just the same register value. + * + * @param registerValue Callee saved register + * @return Instance of callee saved register + */ + public CalleeSavedRAVRegister createCalleeSavedRegister(RegisterValue registerValue) { + var calleeSavedRegister = new CalleeSavedRAVRegister(registerValue); + virtualValues.put(calleeSavedRegister, calleeSavedRegister); + return calleeSavedRegister; + } + + /** + * Add a variable from a virtual move that is assigned to a callee saved register and can also + * be retrieved on exit. + * + * @param register Callee saved register + * @param value New callee saved value + */ + public void addValue(RAVRegister register, RAValue value) { + if (!isCalleeSaveRegister(register)) { + return; + } + + if (virtualValues.get(register) instanceof CalleeSavedRAVRegister) { + virtualValues.put(register, value); + } + } + + /** + * Is callee saved value? + * + * @param register Callee saved register + * @return Get callee saved value for this register + */ + public RAValue getCalleeSavedValue(RAVRegister register) { + return virtualValues.get(register); + } + + /** + * Is register callee-saved? + * + * @param register Register + * @return true, if register is callee saved + */ + public boolean isCalleeSaveRegister(RAVRegister register) { + var calleeSaveRegisters = registerConfig.getCalleeSaveRegisters(); + if (calleeSaveRegisters == null) { + return false; + } + + return calleeSaveRegisters.contains(register.getRegister()); + } + + /** + * Get the list of callee saved registers. + * + * @return callee saved registers + */ + public List getCalleeSaveRegisters() { + return registerConfig.getCalleeSaveRegisters(); + } +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/ConflictedAllocationState.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/ConflictedAllocationState.java new file mode 100644 index 000000000000..579ba5f5aed3 --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/ConflictedAllocationState.java @@ -0,0 +1,147 @@ +/* + * 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.util.EconomicHashSet; + +import java.util.Set; + +/** + * Conflicted allocation state - two or more instances have collided, and either of them can be + * stored at said location, needs to be resolved by either overwriting the location with a new + * {@link ValueAllocationState instance}. + */ +public class ConflictedAllocationState extends AllocationState { + protected final Set conflictedStates; + + public ConflictedAllocationState() { + this.conflictedStates = new EconomicHashSet<>(); + } + + @SuppressWarnings("this-escape") + public ConflictedAllocationState(ValueAllocationState state1, ValueAllocationState state2) { + this(); + addConflictedValue(state1); + addConflictedValue(state2); + } + + protected ConflictedAllocationState(Set conflictedStates) { + this.conflictedStates = new EconomicHashSet<>(conflictedStates); + } + + public void addConflictedValue(ValueAllocationState state) { + if (hasConflictedValue(state)) { + return; + } + + this.conflictedStates.add(state); + } + + public boolean hasConflictedValue(ValueAllocationState valueAllocationState) { + return this.conflictedStates.contains(valueAllocationState); + } + + /** + * Get the set of all {@link ValueAllocationState} instances conflicting in this state. + * + * @return Set of {@link ValueAllocationState} instances + */ + public Set getConflictedStates() { + return this.conflictedStates; + } + + @Override + public boolean isConflicted() { + return true; + } + + /** + * Adds incoming state to conflicted states, does not allocate a new state. + * + * @param other The other state coming from a predecessor edge + * @return always null (see {@link AllocationStateMap#mergeWith}), to not trigger propagation + * through the CFG + */ + @Override + public AllocationState meet(AllocationState other, BasicBlock otherBlock, BasicBlock block) { + if (other instanceof ValueAllocationState valueState) { + this.addConflictedValue(valueState); + } else if (other instanceof ConflictedAllocationState conflictedState) { + for (var otherConfState : conflictedState.getConflictedStates()) { + this.addConflictedValue(otherConfState); + } + } else if (other instanceof UnknownAllocationState) { + /* + * Unknown state creates an Illegal ValueAllocationState inside it, because the unknown + * state is coming from a different predecessor to the same block, and it means that + * this location was not defined there, but it was defined in a different predecessor + * block, meaning it's now in a conflicted state, where it either is defined or it - + * should not be used in further blocks. + */ + this.addConflictedValue(ValueAllocationState.createUndefined(otherBlock)); + } + + // When conflicted, we do not want to process everything again + return null; + } + + @Override + public AllocationState clone() { + return new ConflictedAllocationState(this.conflictedStates); + } + + /** + * We do not compare a conflicted state on its contents, whenever a new one would be created as + * a result, the set of contents would remain the same if values are equal based on RAValue + * rules. + * + * @param other Another state we are comparing it to + * @return Are both states conflicted? + */ + @Override + public boolean equals(Object other) { + return other instanceof ConflictedAllocationState c && c.isConflicted(); + } + + @Override + public int hashCode() { + return conflictedStates.hashCode(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Conflicted {"); + for (var state : this.conflictedStates) { + sb.append(state.toString()).append(", "); + } + + if (!conflictedStates.isEmpty()) { + sb.setLength(sb.length() - 2); + } + + return sb.append("}").toString(); + } +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/FromUsageResolverGlobal.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/FromUsageResolverGlobal.java new file mode 100644 index 000000000000..e0b25104da62 --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/verifier/FromUsageResolverGlobal.java @@ -0,0 +1,470 @@ +/* + * 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.core.common.cfg.BlockMap; +import jdk.graal.compiler.lir.LIR; +import jdk.graal.compiler.lir.StandardOp; +import jdk.graal.compiler.lir.dfa.UniqueWorkList; +import jdk.graal.compiler.util.EconomicHashMap; +import jdk.graal.compiler.util.EconomicHashSet; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; + +/** + * Resolve label phi variables back to labels and jumps by find their first usage and handling any + * reg allocator inserted moves back to the defining label. + * + *

+ * Register allocator strips us of this information that is necessary for the verification. In order + * to avoid modifying the existing allocators, we rather try to resolve this information from other + * instructions. + *

+ * + *

+ * Variables with only usage in jump instructions are marked as aliases and are resolved after their + * successors. + *

+ * + *

+ * If a variable has no usage, then no location is resolved and verification continues without + * issues. + *

+ * + *

+ * In the case the first usage of a label-defined variable is wrong, then JUMP instructions from + * predecessors fail the verification - wrong register will be chosen. + *

+ */ +public class FromUsageResolverGlobal { + /** + * LIR of the compilation unit we are resolving for. + */ + protected final LIR lir; + + /** + * Verifier IR. + */ + protected final BlockMap> blockInstructions; + + /** + * Mapping of variables to the labels that defined them. + */ + public final Map labelMap; + + /** + * Mapping of operation to a set of variable for which this operation is a first usage. + */ + public final Map> firstUsages; + + /** + * Initial locations of label-defined variables to set them to when their first usage is found. + */ + public final Map initialLocations; + + /** + * Variable and a block where it's coming from (last jump instruction), this variable is aliased + * by the succeeding label, which needs to be resolved first, before this one can be. + */ + class AliasPair { + final RAVariable variable; + final BasicBlock block; + + AliasPair(RAVariable variable, BasicBlock block) { + this.variable = variable; + this.block = block; + } + } + + /** + * Map of variables are aliases for a list of variables used in predecessor jump instructions. + * First, the successor label variable needs to be resolved and after the predecessor labels. + */ + private final Map> aliasMap; + + /** + * Block map of their usage objects. + */ + protected final BlockMap blockUsageMap; + + /** + * Set of blocks that have no successors. + */ + public final Set> endBlocks; + + /** + * Information about locations of variables found when traversing LIR, from the first usage, + * handling all related moves up until the label instruction that defined the variable. + */ + protected final class BlockUsage { + protected final Map locations; + protected boolean processed; + + protected BlockUsage() { + this.locations = new EconomicHashMap<>(); + this.processed = false; + } + + protected BlockUsage(BlockUsage blockDefs, boolean processed) { + this.locations = new EconomicHashMap<>(); + for (var variable : blockDefs.locations.keySet()) { + var location = blockDefs.locations.get(variable); + if (location == null) { + continue; + } + + locations.put(variable, location); + } + + this.processed = processed; + } + } + + protected FromUsageResolverGlobal(LIR lir, BlockMap> blockInstructions) { + this.lir = lir; + this.blockInstructions = blockInstructions; + + this.labelMap = new EconomicHashMap<>(); + this.firstUsages = new EconomicHashMap<>(); + this.initialLocations = new EconomicHashMap<>(); + this.aliasMap = new EconomicHashMap<>(); + this.endBlocks = new EconomicHashSet<>(); + + this.blockUsageMap = new BlockMap<>(lir.getControlFlowGraph()); + } + + /** + * Resolves label variable locations by finding where they are first used. Walk back from their + * usage to their defining label (bottom-up), handling any spills, reloads, and moves along the + * way to set the location in the label back after the register allocator strips this + * information. + */ + public void resolvePhiFromUsage() { + Queue> worklist = new UniqueWorkList(lir.getBlocks().length); + + this.initializeUsages(); + + for (var block : endBlocks) { + blockUsageMap.put(block, new BlockUsage()); + worklist.add(block); + } + + while (!worklist.isEmpty()) { + if (labelMap.isEmpty()) { + break; // No need to process further + } + + processBlock(worklist); + } + } + + protected void processBlock(Queue> 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: + *

    + *
  1. Compute the initial state of every block until a fixed point is reached, only generates + * symbols
  2. + *
  3. Verify that read symbols are present at their locations
  4. + *
+ *

+ * + *

+ * {@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> 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)) {