diff --git a/vadl/main/vadl/ast/AnnotationTable.java b/vadl/main/vadl/ast/AnnotationTable.java index a97928f4d..b22ef2d9d 100644 --- a/vadl/main/vadl/ast/AnnotationTable.java +++ b/vadl/main/vadl/ast/AnnotationTable.java @@ -52,6 +52,7 @@ import vadl.viam.ArtificialResource; import vadl.viam.AssemblyDescription; import vadl.viam.Constant; +import vadl.viam.Counter; import vadl.viam.Encoding; import vadl.viam.Endianness; import vadl.viam.Format; @@ -68,6 +69,7 @@ import vadl.viam.annotations.DefineOperandAnnotation; import vadl.viam.annotations.EnableHtifAnno; import vadl.viam.annotations.InstructionUndefinedAnno; +import vadl.viam.annotations.PcOffsetAnnotation; import vadl.viam.annotations.TbStateRegisterAnnotation; /** @@ -107,7 +109,11 @@ public class AnnotationTable { .add("next", EnableAnnotation::new) .add("next next", EnableAnnotation::new) .check(GroupedAnnotationBuilder.GroupCheckContext::verifyOnlyOneOfGroup) - // FIXME: Apply to AST, see Issue #938 + .applyViam(ctx -> { + var reg = ((Counter) ctx.targetDefinition).registerTensor(); + ctx.get("next").ifPresent(ann -> reg.addAnnotation(new PcOffsetAnnotation(1))); + ctx.get("next next").ifPresent(ann -> reg.addAnnotation(new PcOffsetAnnotation(2))); + }) .build(); groupOn(AliasDefinition.class) @@ -123,7 +129,11 @@ public class AnnotationTable { + "and program counter aliases")); ctx.verifyOnlyOneOfGroup(); }) - // FIXME: Apply to AST, see Issue #938 + .applyViam(ctx -> { + var reg = ((Counter) ctx.targetDefinition).registerTensor(); + ctx.get("next").ifPresent(ann -> reg.addAnnotation(new PcOffsetAnnotation(1))); + ctx.get("next next").ifPresent(ann -> reg.addAnnotation(new PcOffsetAnnotation(2))); + }) .build(); annotationOn(RegisterDefinition.class, "zero", ZeroConstraintAnnotation::new) diff --git a/vadl/main/vadl/ast/BehaviorLowering.java b/vadl/main/vadl/ast/BehaviorLowering.java index ef56de104..51680c7df 100644 --- a/vadl/main/vadl/ast/BehaviorLowering.java +++ b/vadl/main/vadl/ast/BehaviorLowering.java @@ -1324,34 +1324,18 @@ private ExpressionNode visitSubCall(CallIndexExpr expr, ExpressionNode exprBefor case null, default -> false; }; if (targetIsCounter) { - // FIXME: @ffreitag this is currently hardcoded as was wrong before. - // It must add the instruction width in bytes. - // This width is obtained by the format type of the current instruction - var instrWidth = 32; - // The byte is defined by the "word" that is returned by the main memory definition. - // So essentially the return type in the relation type of the memory definition. - var byteWidth = 8; - var instrWidthInByte = instrWidth / byteWidth; - - // FIXME: Handle slicing and format subcall propperly + // FIXME: Handle slicing and format subcall properly int offset = 0; for (var subcall : expr.subCalls) { var subcallName = subcall.identifier().name; - if (subcallName.equals("current")) { - offset = 0; - } else if (subcallName.equals("next")) { - offset += instrWidthInByte; - } else if (subcallName.equals("nextnext")) { - offset += instrWidthInByte * 2; - } else { - throw new IllegalStateException("unknown subcall: " + subcallName); + switch (subcallName) { + case "current" -> offset = 0; + case "next" -> offset += 1; + case "nextnext" -> offset += 2; + default -> throw new IllegalStateException("unknown subcall: " + subcallName); } } - - resultExpr = BuiltInCall.of(BuiltInTable.ADD, - resRead, - intU(offset, resRead.type().bitWidth()).toNode() - ); + resRead.setPcOffset(offset); } } else { throw new IllegalStateException(); diff --git a/vadl/main/vadl/ast/SymbolTable.java b/vadl/main/vadl/ast/SymbolTable.java index c2acca70a..d41a68423 100644 --- a/vadl/main/vadl/ast/SymbolTable.java +++ b/vadl/main/vadl/ast/SymbolTable.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Predicate; import java.util.function.Supplier; import javax.annotation.Nullable; import vadl.error.Diagnostic; @@ -388,8 +389,8 @@ Set allSymbolNames() { @SafeVarargs final Set allSymbolNamesOf(Class... classes) { var matchingNames = symbols.entrySet().stream() - .filter(entry -> entry.getValue() instanceof AstSymbol astSymbol - && Arrays.stream(classes).anyMatch(klass -> klass.isInstance(astSymbol.origin))) + .filter(entry -> entry.getValue() instanceof AstSymbol(Node origin) + && Arrays.stream(classes).anyMatch(klass -> klass.isInstance(origin))) .map(Map.Entry::getKey) .toList(); @@ -400,6 +401,26 @@ final Set allSymbolNamesOf(Class... classes) { return names; } + /** + * Returns all symbol names in scope that point to nodes satisfying the given predicate. + * + * @param predicate that must be satisfied. + * @return the set of all available names. + */ + final Set allSymbolNamesWhere(Predicate predicate) { + var matchingNames = symbols.entrySet().stream() + .filter(entry -> entry.getValue() instanceof AstSymbol(Node origin) + && predicate.test(origin)) + .map(Map.Entry::getKey) + .toList(); + + var names = new HashSet<>(matchingNames); + if (parent != null) { + names.addAll(parent.allSymbolNamesWhere(predicate)); + } + return names; + } + /** * Returns all symbol names in scope that point to the defined node classes. * diff --git a/vadl/main/vadl/ast/TypeChecker.java b/vadl/main/vadl/ast/TypeChecker.java index 7dd0015b7..b5e6cec4c 100644 --- a/vadl/main/vadl/ast/TypeChecker.java +++ b/vadl/main/vadl/ast/TypeChecker.java @@ -1603,13 +1603,26 @@ public Void visit(AliasDefinition definition) { // if this does not directly reference a register, // it might reference another alias definition var alias = definition.symbolTable().findAs(targetIdent, AliasDefinition.class); - if (alias == null || alias.kind != AliasDefinition.AliasKind.REGISTER) { + if (alias == null) { + var candidates = new ArrayList<>(definition.symbolTable().allSymbolNamesWhere( + node -> node instanceof RegisterDefinition + || (node instanceof AliasDefinition aliasDef + && aliasDef.kind == AliasDefinition.AliasKind.REGISTER) + )); + var suggestions = Levenshtein.suggestions(targetIdent.pathToString(), candidates); throw addErrorAndStopChecking( error("Unknown alias source register", targetIdent.location()) .locationDescription(targetIdent.location(), "Unknown register `%s`.", targetIdent) + .suggestions(suggestions) .build()); } + if (alias.kind == AliasDefinition.AliasKind.PROGRAM_COUNTER) { + throw addErrorAndStopChecking( + error("Register alias cannot refer to program counter alias", targetIdent.location()) + .build() + ); + } check(alias); reg = (RegisterDefinition) requireNonNull(alias.computedTarget); } @@ -1622,7 +1635,7 @@ public Void visit(AliasDefinition definition) { addErrorAndStopChecking( error("Unsupported Alias Type", definition) .locationDescription(definition, - "The typechecker doesn't know how such aliases yet.") + "The typechecker doesn't know such aliases yet.") .locationHelp(definition, "If you desire this feature, please let us know at: " + "https://github.com/OpenVADL/openvadl/issues/new") diff --git a/vadl/main/vadl/pass/order/ViamPassOrder.java b/vadl/main/vadl/pass/order/ViamPassOrder.java index eb50c34fd..5098eecb0 100644 --- a/vadl/main/vadl/pass/order/ViamPassOrder.java +++ b/vadl/main/vadl/pass/order/ViamPassOrder.java @@ -35,6 +35,7 @@ import vadl.viam.passes.functionInliner.ArtificialResInlinerPass; import vadl.viam.passes.functionInliner.FieldAccessInlinerPass; import vadl.viam.passes.functionInliner.FunctionInlinerPass; +import vadl.viam.passes.pcOffset.PcOffsetPass; import vadl.viam.passes.sideeffect_condition.SideEffectConditionResolvingPass; import vadl.viam.passes.staticCounterAccess.CounterAccessResolvingPass; import vadl.viam.passes.statusBuiltInInlinePass.RemoveUnusedStatusFlagsFromBuiltinsPass; @@ -61,6 +62,7 @@ public static PassOrder create(GeneralConfiguration configuration) throws IOExce order.add(new NormalizeFieldsToFieldAccessFunctionsPass(configuration)); order.add(new RenamingConflictingRegistersPass(configuration)); order.add(new SnapshotInstructionBehaviorPass(configuration)); + order.add(new PcOffsetPass(configuration)); order.add(new RemoveUnusedStatusFlagsFromBuiltinsPass(configuration)); order.add(new StatusBuiltInInlinePass(configuration)); diff --git a/vadl/main/vadl/viam/annotations/PcOffsetAnnotation.java b/vadl/main/vadl/viam/annotations/PcOffsetAnnotation.java new file mode 100644 index 000000000..ec5e29db2 --- /dev/null +++ b/vadl/main/vadl/viam/annotations/PcOffsetAnnotation.java @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText : © 2026 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program 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 for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.viam.annotations; + +import vadl.viam.Annotation; +import vadl.viam.RegisterTensor; + +/** + * Annotation for offsetting read pc values by one or two instruction + * lengths. Can be overwritten by {@link vadl.viam.passes.pcOffset.nodes.PcOffsetNode}. + * + *

The offset is applied by {@link vadl.viam.passes.pcOffset.PcOffsetPass}. + */ +public class PcOffsetAnnotation extends Annotation { + + public PcOffsetAnnotation(int offset) { + this.offset = offset; + } + + private final int offset; + + @Override + public Class parentDefinitionClass() { + return RegisterTensor.class; + } + + /** + * The offset in instructions lengths that is added. + */ + public int offset() { + return offset; + } +} diff --git a/vadl/main/vadl/viam/graph/dependency/ReadResourceNode.java b/vadl/main/vadl/viam/graph/dependency/ReadResourceNode.java index 47472c43e..a46d82490 100644 --- a/vadl/main/vadl/viam/graph/dependency/ReadResourceNode.java +++ b/vadl/main/vadl/viam/graph/dependency/ReadResourceNode.java @@ -37,6 +37,11 @@ public abstract class ReadResourceNode extends ExpressionNode { @Input protected NodeList indices; + // FIXME: not sure what is the best way to implement this... + // maybe add a special node for adding this offset? + @Nullable + private Integer pcOffset; + public ReadResourceNode(@Nullable ExpressionNode address, DataType type) { super(type); this.indices = address == null ? new NodeList<>() : new NodeList<>(address); @@ -104,6 +109,12 @@ protected void collectInputs(List collection) { collection.addAll(indices); } + @Override + protected void collectData(List collection) { + super.collectData(collection); + collection.add(pcOffset); + } + @Override public void applyOnInputsUnsafe( vadl.viam.graph.GraphVisitor.Applier visitor) { @@ -122,4 +133,21 @@ public boolean hasConstantAddress() { return false; } + + /** + * Sets the read offset produced by calling e.g. {@code .next} on a program + * counter. + */ + public void setPcOffset(int pcOffset) { + this.pcOffset = pcOffset; + } + + /** + * Returns the read offset produced by calling e.g. {@code .next} on a program + * counter. Is used by {@link vadl.viam.passes.pcOffset.PcOffsetPass}. + */ + @Nullable + public Integer pcOffset() { + return pcOffset; + } } diff --git a/vadl/main/vadl/viam/passes/pcOffset/PcOffsetPass.java b/vadl/main/vadl/viam/passes/pcOffset/PcOffsetPass.java new file mode 100644 index 000000000..f13c56a81 --- /dev/null +++ b/vadl/main/vadl/viam/passes/pcOffset/PcOffsetPass.java @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText : © 2026 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program 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 for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.viam.passes.pcOffset; + +import java.io.IOException; +import javax.annotation.Nullable; +import vadl.configuration.GeneralConfiguration; +import vadl.pass.Pass; +import vadl.pass.PassName; +import vadl.pass.PassResults; +import vadl.types.BuiltInTable; +import vadl.utils.GraphUtils; +import vadl.utils.ViamUtils; +import vadl.viam.Instruction; +import vadl.viam.Specification; +import vadl.viam.annotations.PcOffsetAnnotation; +import vadl.viam.graph.Graph; +import vadl.viam.graph.dependency.BuiltInCall; +import vadl.viam.graph.dependency.ReadResourceNode; + +/** + * Applies program counter offsets to program counter reads. + * + *

When reading a program counter that has been annotated with + * {@code [current]}, {@code [next]} or {@code [next next]} or when using + * one of the subcalls {@code .current}, {@code .next} or {@code .nextnext}, + * the read value is offset by multiples of the instruction length. The subcalls + * overwrite the annotations. + * + *

This pass looks for {@link PcOffsetAnnotation}s and checks + * {@link ReadResourceNode#pcOffset()} and applies them by inserting addition nodes. + */ +public class PcOffsetPass extends Pass { + + public PcOffsetPass(GeneralConfiguration configuration) { + super(configuration); + } + + @Override + public PassName getName() { + return new PassName("PC Offset Pass"); + } + + @Override + public @Nullable Object execute(PassResults passResults, Specification viam) + throws IOException { + ViamUtils.findAllBehaviors(viam).forEach(this::handleBehaviour); + return null; + } + + private void handleBehaviour(Graph behaviour) { + // FIXME: are instruction lengths always multiples of 8? + // What if the pc does not counter per byte? + // FIXME: What instruction length should be used in behaviours outside + // instructions (eg in functions)? + int instrBytes = behaviour.parentDefinition() instanceof Instruction instruction + ? instruction.format().type().bitWidth() / 8 + : 32; + + behaviour.getNodes(ReadResourceNode.class) + .forEach(n -> handleRead(n, instrBytes)); + } + + private void handleRead(ReadResourceNode read, int instrBytes) { + var offsetAnn = read.resourceDefinition().annotation(PcOffsetAnnotation.class); + var regOffset = offsetAnn == null ? 0 : offsetAnn.offset(); + var readOffset = read.pcOffset(); + int offset = readOffset != null ? readOffset : regOffset; + + if (offset != 0) { + read.replace(BuiltInCall.of( + BuiltInTable.ADD, read, + GraphUtils.intUNode((long) offset * instrBytes, read.type().bitWidth()) + )); + } + } +} diff --git a/vadl/test/resources/testSource/passes/pcOffset/valid_pc_alias_ann_current.vadl b/vadl/test/resources/testSource/passes/pcOffset/valid_pc_alias_ann_current.vadl new file mode 100644 index 000000000..5d13cc4e5 --- /dev/null +++ b/vadl/test/resources/testSource/passes/pcOffset/valid_pc_alias_ann_current.vadl @@ -0,0 +1,19 @@ + +instruction set architecture PcTest = { + using Regs = Bits<32> + + register A: Regs + [ current ] + alias program counter PC: Regs = A + + instruction READ_PC: F = A := PC + + encoding READ_PC = { I = 2 } + assembly READ_PC = "Test" + + format F: Regs = { + I: Regs + } +} + +processor TEST implements PcTest = {} \ No newline at end of file diff --git a/vadl/test/resources/testSource/passes/pcOffset/valid_pc_alias_ann_next.vadl b/vadl/test/resources/testSource/passes/pcOffset/valid_pc_alias_ann_next.vadl new file mode 100644 index 000000000..418616b02 --- /dev/null +++ b/vadl/test/resources/testSource/passes/pcOffset/valid_pc_alias_ann_next.vadl @@ -0,0 +1,19 @@ + +instruction set architecture PcTest = { + using Regs = Bits<32> + + register A: Regs + [ next ] + alias program counter PC: Regs = A + + instruction READ_PC: F = A := PC + + encoding READ_PC = { I = 2 } + assembly READ_PC = "Test" + + format F: Regs = { + I: Regs + } +} + +processor TEST implements PcTest = {} \ No newline at end of file diff --git a/vadl/test/resources/testSource/passes/pcOffset/valid_pc_alias_ann_nextnext.vadl b/vadl/test/resources/testSource/passes/pcOffset/valid_pc_alias_ann_nextnext.vadl new file mode 100644 index 000000000..d2bf5980b --- /dev/null +++ b/vadl/test/resources/testSource/passes/pcOffset/valid_pc_alias_ann_nextnext.vadl @@ -0,0 +1,19 @@ + +instruction set architecture PcTest = { + using Regs = Bits<32> + + register A: Regs + [ next next ] + alias program counter PC: Regs = A + + instruction READ_PC: F = A := PC + + encoding READ_PC = { I = 2 } + assembly READ_PC = "Test" + + format F: Regs = { + I: Regs + } +} + +processor TEST implements PcTest = {} \ No newline at end of file diff --git a/vadl/test/resources/testSource/unit/register/valid_pc_alias_reg_subcalls.vadl b/vadl/test/resources/testSource/passes/pcOffset/valid_pc_alias_subcalls.vadl similarity index 100% rename from vadl/test/resources/testSource/unit/register/valid_pc_alias_reg_subcalls.vadl rename to vadl/test/resources/testSource/passes/pcOffset/valid_pc_alias_subcalls.vadl diff --git a/vadl/test/resources/testSource/passes/pcOffset/valid_pc_alias_subcalls_with_current.vadl b/vadl/test/resources/testSource/passes/pcOffset/valid_pc_alias_subcalls_with_current.vadl new file mode 100644 index 000000000..0f5f7bbec --- /dev/null +++ b/vadl/test/resources/testSource/passes/pcOffset/valid_pc_alias_subcalls_with_current.vadl @@ -0,0 +1,24 @@ + +instruction set architecture PcTest = { + using Regs = Bits<32> + + register A: Regs + [ current ] + alias program counter PC: Regs = A + + // subcalls should overwrite the [ current ] annotation + instruction READ_PC_CURRENT: F = A := PC.current + instruction READ_PC_NEXT: F = A := PC.next + instruction READ_PC_NEXTNEXT: F = A := PC.nextnext + + encoding READ_PC_CURRENT = { I = 2 } + encoding READ_PC_NEXT = { I = 2 } + encoding READ_PC_NEXTNEXT = { I = 2 } + assembly READ_PC_CURRENT, READ_PC_NEXT, READ_PC_NEXTNEXT = "Test" + + format F: Regs = { + I: Regs + } +} + +processor TEST implements PcTest = {} \ No newline at end of file diff --git a/vadl/test/resources/testSource/unit/register/valid_pc_alias_reg_subcalls_with_annotation.vadl b/vadl/test/resources/testSource/passes/pcOffset/valid_pc_alias_subcalls_with_next.vadl similarity index 100% rename from vadl/test/resources/testSource/unit/register/valid_pc_alias_reg_subcalls_with_annotation.vadl rename to vadl/test/resources/testSource/passes/pcOffset/valid_pc_alias_subcalls_with_next.vadl diff --git a/vadl/test/resources/testSource/passes/pcOffset/valid_pc_ann_current.vadl b/vadl/test/resources/testSource/passes/pcOffset/valid_pc_ann_current.vadl new file mode 100644 index 000000000..0857eaffc --- /dev/null +++ b/vadl/test/resources/testSource/passes/pcOffset/valid_pc_ann_current.vadl @@ -0,0 +1,19 @@ + +instruction set architecture PcTest = { + using Regs = Bits<32> + + register A: Regs + [ current ] + program counter PC: Regs + + instruction READ_PC: F = A := PC + + encoding READ_PC = { I = 2 } + assembly READ_PC = "Test" + + format F: Regs = { + I: Regs + } +} + +processor TEST implements PcTest = {} \ No newline at end of file diff --git a/vadl/test/resources/testSource/passes/pcOffset/valid_pc_ann_next.vadl b/vadl/test/resources/testSource/passes/pcOffset/valid_pc_ann_next.vadl new file mode 100644 index 000000000..035c5b13a --- /dev/null +++ b/vadl/test/resources/testSource/passes/pcOffset/valid_pc_ann_next.vadl @@ -0,0 +1,19 @@ + +instruction set architecture PcTest = { + using Regs = Bits<32> + + register A: Regs + [ next ] + program counter PC: Regs + + instruction READ_PC: F = A := PC + + encoding READ_PC = { I = 2 } + assembly READ_PC = "Test" + + format F: Regs = { + I: Regs + } +} + +processor TEST implements PcTest = {} \ No newline at end of file diff --git a/vadl/test/resources/testSource/passes/pcOffset/valid_pc_ann_nextnext.vadl b/vadl/test/resources/testSource/passes/pcOffset/valid_pc_ann_nextnext.vadl new file mode 100644 index 000000000..cd6cb7740 --- /dev/null +++ b/vadl/test/resources/testSource/passes/pcOffset/valid_pc_ann_nextnext.vadl @@ -0,0 +1,19 @@ + +instruction set architecture PcTest = { + using Regs = Bits<32> + + register A: Regs + [ next next ] + program counter PC: Regs + + instruction READ_PC: F = A := PC + + encoding READ_PC = { I = 2 } + assembly READ_PC = "Test" + + format F: Regs = { + I: Regs + } +} + +processor TEST implements PcTest = {} \ No newline at end of file diff --git a/vadl/test/resources/testSource/passes/pcOffset/valid_pc_subcalls.vadl b/vadl/test/resources/testSource/passes/pcOffset/valid_pc_subcalls.vadl new file mode 100644 index 000000000..d5c2d3258 --- /dev/null +++ b/vadl/test/resources/testSource/passes/pcOffset/valid_pc_subcalls.vadl @@ -0,0 +1,22 @@ + +instruction set architecture PcTest = { + using Regs = Bits<32> + + register A: Regs + program counter PC: Regs + + instruction READ_PC_CURRENT: F = A := PC.current + instruction READ_PC_NEXT: F = A := PC.next + instruction READ_PC_NEXTNEXT: F = A := PC.nextnext + + encoding READ_PC_CURRENT = { I = 2 } + encoding READ_PC_NEXT = { I = 2 } + encoding READ_PC_NEXTNEXT = { I = 2 } + assembly READ_PC_CURRENT, READ_PC_NEXT, READ_PC_NEXTNEXT = "Test" + + format F: Regs = { + I: Regs + } +} + +processor TEST implements PcTest = {} \ No newline at end of file diff --git a/vadl/test/resources/testSource/passes/pcOffset/valid_pc_subcalls_with_current.vadl b/vadl/test/resources/testSource/passes/pcOffset/valid_pc_subcalls_with_current.vadl new file mode 100644 index 000000000..cd0cb4963 --- /dev/null +++ b/vadl/test/resources/testSource/passes/pcOffset/valid_pc_subcalls_with_current.vadl @@ -0,0 +1,24 @@ + +instruction set architecture PcTest = { + using Regs = Bits<32> + + register A: Regs + [ current ] + program counter PC: Regs + + // subcalls should overwrite the [ current ] annotation + instruction READ_PC_CURRENT: F = A := PC.current + instruction READ_PC_NEXT: F = A := PC.next + instruction READ_PC_NEXTNEXT: F = A := PC.nextnext + + encoding READ_PC_CURRENT = { I = 2 } + encoding READ_PC_NEXT = { I = 2 } + encoding READ_PC_NEXTNEXT = { I = 2 } + assembly READ_PC_CURRENT, READ_PC_NEXT, READ_PC_NEXTNEXT = "Test" + + format F: Regs = { + I: Regs + } +} + +processor TEST implements PcTest = {} \ No newline at end of file diff --git a/vadl/test/resources/testSource/passes/pcOffset/valid_pc_subcalls_with_next.vadl b/vadl/test/resources/testSource/passes/pcOffset/valid_pc_subcalls_with_next.vadl new file mode 100644 index 000000000..e17172440 --- /dev/null +++ b/vadl/test/resources/testSource/passes/pcOffset/valid_pc_subcalls_with_next.vadl @@ -0,0 +1,24 @@ + +instruction set architecture PcTest = { + using Regs = Bits<32> + + register A: Regs + [ next ] + program counter PC: Regs + + // subcalls should overwrite the [ next ] annotation + instruction READ_PC_CURRENT: F = A := PC.current + instruction READ_PC_NEXT: F = A := PC.next + instruction READ_PC_NEXTNEXT: F = A := PC.nextnext + + encoding READ_PC_CURRENT = { I = 2 } + encoding READ_PC_NEXT = { I = 2 } + encoding READ_PC_NEXTNEXT = { I = 2 } + assembly READ_PC_CURRENT, READ_PC_NEXT, READ_PC_NEXTNEXT = "Test" + + format F: Regs = { + I: Regs + } +} + +processor TEST implements PcTest = {} \ No newline at end of file diff --git a/vadl/test/resources/testSource/unit/register/invalid_alias_reg_referToPC.vadl b/vadl/test/resources/testSource/unit/register/invalid_alias_reg_referToPC.vadl new file mode 100644 index 000000000..b0f969b0a --- /dev/null +++ b/vadl/test/resources/testSource/unit/register/invalid_alias_reg_referToPC.vadl @@ -0,0 +1,13 @@ + +instruction set architecture PcTest = { + using Regs = Bits<32> + + program counter PC: Regs + alias register AR: Regs = PC + + format F: Regs = { + I: Regs + } +} + +processor TEST implements PcTest = {} diff --git a/vadl/test/resources/testSource/unit/register/invalid_alias_reg_referToPCAlias.vadl b/vadl/test/resources/testSource/unit/register/invalid_alias_reg_referToPCAlias.vadl new file mode 100644 index 000000000..f1f8c3515 --- /dev/null +++ b/vadl/test/resources/testSource/unit/register/invalid_alias_reg_referToPCAlias.vadl @@ -0,0 +1,14 @@ + +instruction set architecture PcTest = { + using Regs = Bits<32> + + register R: Regs + alias program counter PC: Regs = R + alias register AR: Regs = PC + + format F: Regs = { + I: Regs + } +} + +processor TEST implements PcTest = {} diff --git a/vadl/test/vadl/viam/RegisterTest.java b/vadl/test/vadl/viam/RegisterTest.java index b80564ee5..cf43d285f 100644 --- a/vadl/test/vadl/viam/RegisterTest.java +++ b/vadl/test/vadl/viam/RegisterTest.java @@ -39,7 +39,6 @@ import vadl.types.Type; import vadl.viam.graph.control.BranchEndNode; import vadl.viam.graph.control.IfNode; -import vadl.viam.graph.dependency.BuiltInCall; import vadl.viam.graph.dependency.ConstantNode; import vadl.viam.graph.dependency.DependencyNode; import vadl.viam.graph.dependency.FuncCallNode; @@ -56,6 +55,9 @@ private static Stream invalidRegisterTestSources() { return AbstractTest.getTestSourceArgsForParameterizedTest("unit/register/invalid_", arguments("reg_invalidFormat", "Invalid Format"), + arguments("alias_reg_referToPC", "Unknown register"), + arguments("alias_reg_referToPCAlias", "Register alias cannot refer"), + arguments("pc_alias_reg_subcall_currentOnReg", "No subcall"), arguments("pc_alias_reg_subcall_nextOnReg", "No subcall"), arguments("pc_alias_reg_subcall_nextnextOnReg", "No subcall"), @@ -269,35 +271,8 @@ public Stream testPcReg() { testPc("valid_pc_alias_regfile.vadl", "PcTest::PC", "PcTest::X", 31), testPc("valid_pc_current.vadl", "PcTest::PC", "PcTest::PC", null), testPc("valid_pc_next.vadl", "PcTest::PC", "PcTest::PC", null), - testPc("valid_pc_next_next.vadl", "PcTest::PC", "PcTest::PC", null), - - testPcSubcalls("valid_pc_alias_reg_subcalls.vadl"), - testPcSubcalls("valid_pc_alias_reg_subcalls_with_annotation.vadl") + testPc("valid_pc_next_next.vadl", "PcTest::PC", "PcTest::PC", null) ); - - } - - private DynamicTest testPcSubcalls(String fileName) { - return dynamicTest(fileName, () -> { - var spec = runAndGetViamSpecification("unit/register/" + fileName); - // FIXME: fix hardcoded instr lengths here - testPcSubcall("PcTest::READ_PC_CURRENT", spec, 0); - testPcSubcall("PcTest::READ_PC_NEXT", spec, 4); - testPcSubcall("PcTest::READ_PC_NEXTNEXT", spec, 8); - }); - } - - private void testPcSubcall(String instrName, Specification spec, int offset) { - var instr = TestUtils.findDefinitionByNameIn(instrName, spec, Instruction.class); - var readReg = getSingleNode(instr.behavior(), ReadRegTensorNode.class); - Assertions.assertTrue(readReg.hasUsages()); - var usage = readReg.usages().findFirst().get(); - Assertions.assertInstanceOf(BuiltInCall.class, usage); - var add = (BuiltInCall) usage; - Assertions.assertEquals(BuiltInTable.ADD, add.builtIn()); - var offsetNode = add.arg(1); - Assertions.assertInstanceOf(ConstantNode.class, offsetNode); - Assertions.assertEquals(offset, ((ConstantNode) offsetNode).constant().asVal().intValue()); } private DynamicTest testPc(String fileName, String counterName, String resourceName, diff --git a/vadl/test/vadl/viam/passes/PcOffsetTest.java b/vadl/test/vadl/viam/passes/PcOffsetTest.java new file mode 100644 index 000000000..61216f420 --- /dev/null +++ b/vadl/test/vadl/viam/passes/PcOffsetTest.java @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText : © 2026 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program 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 for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.viam.passes; + +import static org.junit.jupiter.api.DynamicTest.dynamicTest; +import static vadl.utils.GraphUtils.getSingleNode; + +import java.util.stream.Stream; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; +import vadl.AbstractTest; +import vadl.TestUtils; +import vadl.pass.PassOrders; +import vadl.types.BuiltInTable; +import vadl.viam.Instruction; +import vadl.viam.Specification; +import vadl.viam.graph.dependency.BuiltInCall; +import vadl.viam.graph.dependency.ConstantNode; +import vadl.viam.graph.dependency.ReadRegTensorNode; +import vadl.viam.graph.dependency.WriteResourceNode; +import vadl.viam.passes.pcOffset.PcOffsetPass; + +public class PcOffsetTest extends AbstractTest { + + @TestFactory + public Stream testOffsets() { + return Stream.of( + testPcSubCalls("valid_pc_alias_subcalls.vadl"), + testPcSubCalls("valid_pc_alias_subcalls_with_current.vadl"), + testPcSubCalls("valid_pc_alias_subcalls_with_next.vadl"), + + testPcSubCalls("valid_pc_subcalls.vadl"), + testPcSubCalls("valid_pc_subcalls_with_current.vadl"), + testPcSubCalls("valid_pc_subcalls_with_next.vadl"), + + testPcOffsetAnnotation("valid_pc_ann_current.vadl", 0), + testPcOffsetAnnotation("valid_pc_ann_next.vadl", 1), + testPcOffsetAnnotation("valid_pc_ann_nextnext.vadl", 2), + + testPcOffsetAnnotation("valid_pc_alias_ann_current.vadl", 0), + testPcOffsetAnnotation("valid_pc_alias_ann_next.vadl", 1), + testPcOffsetAnnotation("valid_pc_alias_ann_nextnext.vadl", 2) + ); + } + + private DynamicTest testPcOffsetAnnotation(String fileName, int offset) { + return dynamicTest(fileName, () -> { + var spec = setupPassManagerAndRunSpec( + "passes/pcOffset/" + fileName, + PassOrders.viam(getConfiguration(false)) + .untilFirst(PcOffsetPass.class) + ).specification(); + testPcOffset("PcTest::READ_PC", spec, offset); + }); + } + + private DynamicTest testPcSubCalls(String fileName) { + return dynamicTest(fileName, () -> { + var spec = setupPassManagerAndRunSpec( + "passes/pcOffset/" + fileName, + PassOrders.viam(getConfiguration(false)) + .untilFirst(PcOffsetPass.class) + ).specification(); + testPcOffset("PcTest::READ_PC_CURRENT", spec, 0); + testPcOffset("PcTest::READ_PC_NEXT", spec, 1); + testPcOffset("PcTest::READ_PC_NEXTNEXT", spec, 2); + }); + } + + private void testPcOffset(String instrName, Specification spec, int offset) { + var instr = TestUtils.findDefinitionByNameIn(instrName, spec, Instruction.class); + var readReg = getSingleNode(instr.behavior(), ReadRegTensorNode.class); + Assertions.assertTrue(readReg.hasUsages()); + var usage = readReg.usages().findFirst().get(); + if (offset == 0) { + Assertions.assertInstanceOf(WriteResourceNode.class, usage); + } else { + Assertions.assertInstanceOf(BuiltInCall.class, usage); + var add = (BuiltInCall) usage; + Assertions.assertEquals(BuiltInTable.ADD, add.builtIn()); + var offsetNode = add.arg(1); + Assertions.assertInstanceOf(ConstantNode.class, offsetNode); + Assertions.assertEquals(offset * 4, + ((ConstantNode) offsetNode).constant().asVal().intValue()); + } + } +}