-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Register allocator correctness verifier #13229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 110 commits
9a765e2
09ac5b8
d3dc97d
51efb03
8692753
ffe73e6
3a7a664
d80f0b8
cefa342
25128e7
69224db
0775f2a
15eb8a4
872b321
b197244
b40a31d
f48e6d5
6e8dda9
d84dd18
c767c15
d260760
a3bd670
92c489a
37b76d4
3f7060e
2ee75cf
e3f33b2
569612d
00aa04a
2ae6469
4c7cc15
fd9d692
499c44d
13fbbf6
a99f31d
cfa4627
040c121
5ed9c42
1bfa592
7cf409d
1659f70
4bec15f
76c0091
ba197a5
88e7d1e
ac00e5e
786849a
6f2564f
05a32e6
a598465
89478c1
37dc5e3
b061de7
24afdde
c7e2528
984a91f
13faefd
aec2780
0431f9c
5fd14e0
4b211b5
b07fce0
919995d
d11b100
e0a266e
9d641da
225c9a0
362858d
49baaa7
664f145
6b80ecf
8b49f49
c7b1080
25779b4
12c14aa
8f88fa2
b9febcf
0b6de17
6cfce85
a47d947
ac7a94e
6a91b75
3dbe57f
2bfa987
1859d38
fbd27b1
c28a22b
47b29c3
1ff83e7
67b2751
6e159b8
eb80d21
7d9daf5
1eb15c7
20a21e0
570de05
3012298
68acbac
ea465ec
25596b8
6180508
689ffd0
f81cca6
615f0c9
715d93b
f95f9cf
54e1cba
5544c9d
bbb3941
cc4dbe1
219c6c1
dd401b4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| /* | ||
| * 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; | ||
|
|
||
| /** | ||
| * 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 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"; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| /* | ||
| * 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 { | ||
| /** | ||
| * 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 What is the new state the location is in. | ||
| */ | ||
| public abstract AllocationState meet(AllocationState other, BasicBlock<?> otherBlock, BasicBlock<?> block); | ||
|
|
||
| public abstract boolean equals(AllocationState other); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,206 @@ | ||
| /* | ||
| * 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.util.EconomicHashMap; | ||
| import jdk.graal.compiler.util.EconomicHashSet; | ||
| import jdk.vm.ci.meta.ValueKind; | ||
|
|
||
| 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Automated formatting destroyed your list, please use proper HTML list syntax in Javadoc comments. |
||
| * | ||
| * <p> | ||
| * 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. | ||
| * </p> | ||
| */ | ||
| public class AllocationStateMap { | ||
| protected BasicBlock<?> block; | ||
|
|
||
| /** | ||
| * Internal map maintaining the mapping. | ||
| */ | ||
| protected Map<RAValue, AllocationState> internalMap; | ||
|
|
||
| /** | ||
| * Map of casts for locations that was forced by allocator-inserted move, see | ||
| * {@link BlockVerifierState#isMoveKindChange}. | ||
| */ | ||
| protected Map<RAValue, ValueKind<LIRKind>> castMap; | ||
|
|
||
| /** | ||
| * Register allocation config describing which registers can be used. | ||
| */ | ||
| protected RegisterAllocationConfig registerAllocationConfig; | ||
|
|
||
| public AllocationStateMap(BasicBlock<?> block, RegisterAllocationConfig registerAllocationConfig) { | ||
| internalMap = new EconomicHashMap<>(); | ||
| castMap = new EconomicHashMap<>(); | ||
| this.block = block; | ||
| this.registerAllocationConfig = registerAllocationConfig; | ||
| } | ||
|
|
||
| public AllocationStateMap(BasicBlock<?> block, AllocationStateMap other) { | ||
| internalMap = new EconomicHashMap<>(other.internalMap); | ||
| castMap = new EconomicHashMap<>(other.castMap); | ||
| registerAllocationConfig = other.registerAllocationConfig; | ||
| this.block = block; | ||
| } | ||
|
|
||
| public boolean has(RAValue key) { | ||
| return internalMap.containsKey(key); | ||
| } | ||
|
|
||
| public AllocationState get(RAValue key) { | ||
| return this.get(key, AllocationState.getDefault()); | ||
| } | ||
|
|
||
| public AllocationState get(RAValue key, AllocationState defaultValue) { | ||
| var state = internalMap.get(key); | ||
| if (state == null) { | ||
| return defaultValue; | ||
| } | ||
| return state; | ||
| } | ||
|
|
||
| /** | ||
| * 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. | ||
| * | ||
| * <p> | ||
| * This is useful for registers that are used by the ABI in the first label but can actually | ||
| * never be changed, like rbp. | ||
| * </p> | ||
| * | ||
| * @param key Location used | ||
| * @param state State to store | ||
| */ | ||
| public void putWithoutRegCheck(RAValue key, AllocationState state) { | ||
| internalMap.put(key, state); | ||
| castMap.remove(key); // Always remove the cast when new value is inserted. | ||
| } | ||
|
|
||
| /** | ||
| * 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<RAValue> getValueLocations(RAValue value) { | ||
| Set<RAValue> 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()) { | ||
| if (!this.internalMap.containsKey(entry.getKey())) { | ||
| changed = true; | ||
|
|
||
| this.putWithoutRegCheck(entry.getKey(), UnknownAllocationState.INSTANCE); | ||
| } | ||
|
|
||
| var currentValue = this.internalMap.get(entry.getKey()); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method would be a bit cleaner if you did this |
||
| var result = this.internalMap.get(entry.getKey()).meet(entry.getValue(), source.block, this.block); | ||
| if (!currentValue.equals(result)) { | ||
| changed = true; | ||
| } | ||
|
|
||
| this.putWithoutRegCheck(entry.getKey(), result); | ||
| } | ||
|
|
||
| castMap.putAll(source.castMap); // This should not affect the merge logic | ||
| 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); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding is that you should implement the
Cloneableinterface if you overrideObject.clone().