diff --git a/vadl/main/vadl/dump/infoEnrichers/ViamEnricherCollection.java b/vadl/main/vadl/dump/infoEnrichers/ViamEnricherCollection.java index 8c4062049..c2e3f2201 100644 --- a/vadl/main/vadl/dump/infoEnrichers/ViamEnricherCollection.java +++ b/vadl/main/vadl/dump/infoEnrichers/ViamEnricherCollection.java @@ -29,6 +29,8 @@ import vadl.dump.InfoEnricher; import vadl.dump.InfoUtils; import vadl.dump.entities.DefinitionEntity; +import vadl.iss.passes.extensions.InstrExecPlan; +import vadl.iss.passes.extensions.InstrInfo; import vadl.utils.SourceLocation; import vadl.viam.DefProp; import vadl.viam.Encoding; @@ -295,6 +297,36 @@ private static String sourceHeader(SourceLocation location) { entity.addInfo(info); }); + public static InfoEnricher VECTOR_TCG_PLAN_SUPPLIER_TAGS = + forType(DefinitionEntity.class, (entity, passResult) -> { + if (!(entity.origin() instanceof Instruction instruction)) { + return; + } + + var info = instruction.extension(InstrInfo.class); + if (info == null || info.executionPlan() == null) { + return; + } + + var executionPlan = info.executionPlan(); + entity.addInfo(Info.Tag.of("SelectedExecutionStrategy", + executionPlan.selectedStrategy().name())); + + var directGvec = executionPlan.evaluation( + InstrExecPlan.StrategyKind.DIRECT_GVEC); + if (directGvec == null) { + return; + } + + entity.addInfo(Info.Tag.of("DirectGvecStatus", directGvec.status().name())); + entity.addInfo(Info.Tag.of("DirectGvecIssues", + directGvec.issues().isEmpty() + ? "-" + : directGvec.issues().stream() + .map(issue -> issue.code()) + .collect(Collectors.joining(", ")))); + }); + public static InfoEnricher BEHAVIOR_NO_LOCATION_EXPANDABLE = forType(DefinitionEntity.class, (entity, passResult) -> { @@ -405,6 +437,7 @@ private static String sourceHeader(SourceLocation location) { VERIFY_SUPPLIER_EXPANDABLE, SOURCE_CODE_SUPPLIER_EXPANDABLE, RESOURCE_ACCESS_SUPPLIER_EXPANDABLE, + VECTOR_TCG_PLAN_SUPPLIER_TAGS, BEHAVIOR_NO_LOCATION_EXPANDABLE, STAGE_ORDER_SUPPLIER, OPERATION_INSTRUCTIONS_SUPPLIER_EXPANDABLE, diff --git a/vadl/main/vadl/iss/passes/common/IssExecStrategyPass.java b/vadl/main/vadl/iss/passes/common/IssExecStrategyPass.java deleted file mode 100644 index 9eb3a1a88..000000000 --- a/vadl/main/vadl/iss/passes/common/IssExecStrategyPass.java +++ /dev/null @@ -1,80 +0,0 @@ -// 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.iss.passes.common; - -import static vadl.iss.passes.TcgPassUtils.instrInfo; -import static vadl.iss.passes.TcgPassUtils.regInfo; - -import java.io.IOException; -import javax.annotation.Nullable; -import vadl.configuration.IssConfiguration; -import vadl.iss.passes.AbstractIssPass; -import vadl.iss.passes.extensions.InstrInfo; -import vadl.iss.passes.extensions.RegInfo; -import vadl.iss.passes.nodes.IssReadRegNode; -import vadl.iss.passes.nodes.IssWriteRegNode; -import vadl.pass.PassName; -import vadl.pass.PassResults; -import vadl.viam.Specification; -import vadl.viam.graph.control.ForallNode; -import vadl.viam.graph.dependency.FoldNode; -import vadl.viam.graph.dependency.TensorNode; - -/** - * Computes and stores ISS backend execution strategies for instructions. - * - *

This pass centralizes strategy classification so code generation only consumes the strategy - * and does not infer backend choices ad-hoc.

- */ -public class IssExecStrategyPass extends AbstractIssPass { - - public IssExecStrategyPass(IssConfiguration configuration) { - super(configuration); - } - - @Override - public PassName getName() { - return PassName.of("ISS Exec Strategy Classification"); - } - - @Override - public @Nullable Object execute(PassResults passResults, Specification viam) throws IOException { - if (viam.isa().isEmpty()) { - return null; - } - - var isa = viam.isa().get(); - isa.ownInstructions().forEach(instr -> { - var hasCpuVectorReads = instr.behavior().getNodes(IssReadRegNode.class) - .anyMatch(n -> regInfo(n.regTensor()).execClass() == RegInfo.ExecClass.CPU_VECTOR); - var hasCpuVectorWrites = instr.behavior().getNodes(IssWriteRegNode.class) - .anyMatch(n -> regInfo(n.regTensor()).execClass() == RegInfo.ExecClass.CPU_VECTOR); - - // TODO: Eventually this must be supported by TCG as well - var anyTensorExpr = instr.behavior().getNodes(ForallNode.class).findAny().isPresent() - || instr.behavior().getNodes(TensorNode.class).findAny().isPresent() - || instr.behavior().getNodes(FoldNode.class).findAny().isPresent(); - - var strategy = hasCpuVectorReads || hasCpuVectorWrites || anyTensorExpr - ? InstrInfo.ExecStrategy.HELPER_CALL - : InstrInfo.ExecStrategy.DIRECT_TCG; - - instrInfo(instr).setExecStrategy(strategy); - }); - return null; - } -} diff --git a/vadl/main/vadl/iss/passes/common/planning/InstructionExecutionPlanner.java b/vadl/main/vadl/iss/passes/common/planning/InstructionExecutionPlanner.java new file mode 100644 index 000000000..3e101c5fb --- /dev/null +++ b/vadl/main/vadl/iss/passes/common/planning/InstructionExecutionPlanner.java @@ -0,0 +1,61 @@ +// 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.iss.passes.common.planning; + +import java.util.Comparator; +import java.util.List; +import vadl.iss.passes.extensions.InstrExecPlan; +import vadl.iss.passes.extensions.InstrExecPlan.StrategyEvaluation; +import vadl.iss.passes.extensions.InstrExecPlan.StrategyKind; +import vadl.viam.Instruction; + +/** + * Evaluates all configured execution strategies and selects the best viable one. + */ +final class InstructionExecutionPlanner { + + private final List evaluators; + + InstructionExecutionPlanner(List evaluators) { + this.evaluators = List.copyOf(evaluators); + } + + /** + * Plans instruction execution by evaluating all strategies and selecting the best viable one. + */ + InstrExecPlan plan(Instruction instruction) { + var evaluations = evaluators.stream() + .map(evaluator -> evaluator.evaluate(instruction)) + .toList(); + var selected = evaluations.stream() + .filter(StrategyEvaluation::isViable) + .min(Comparator + .comparingInt(StrategyEvaluation::estimatedCost) + .thenComparingInt(evaluation -> strategyPriority(evaluation.strategy()))) + .orElseThrow(() -> new IllegalStateException( + "Expected at least one viable execution strategy for " + instruction.simpleName())); + return new InstrExecPlan(evaluations, selected); + } + + private int strategyPriority(StrategyKind strategy) { + return switch (strategy) { + case DIRECT_GVEC -> 0; + case TCG_SCALAR -> 1; + case HELPER_CALL -> 2; + }; + } +} diff --git a/vadl/main/vadl/iss/passes/common/planning/IssExecStrategyPass.java b/vadl/main/vadl/iss/passes/common/planning/IssExecStrategyPass.java new file mode 100644 index 000000000..70b68be51 --- /dev/null +++ b/vadl/main/vadl/iss/passes/common/planning/IssExecStrategyPass.java @@ -0,0 +1,74 @@ +// 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.iss.passes.common.planning; + +import static vadl.iss.passes.TcgPassUtils.instrInfo; + +import java.io.IOException; +import java.util.List; +import javax.annotation.Nullable; +import vadl.configuration.IssConfiguration; +import vadl.iss.passes.AbstractIssPass; +import vadl.iss.passes.common.planning.evaluators.DirectGvecStrategyEvaluator; +import vadl.iss.passes.common.planning.evaluators.HelperCallStrategyEvaluator; +import vadl.iss.passes.common.planning.evaluators.TcgScalarStrategyEvaluator; +import vadl.pass.PassName; +import vadl.pass.PassResults; +import vadl.viam.Specification; + +/** + * Computes and stores instruction execution plans plus the currently supported backend strategy. + * + *

The selected execution plan is the backend-independent source of truth. The legacy + * {@link vadl.iss.passes.extensions.InstrInfo.ExecStrategy} is derived from the selected plan so + * existing code generation can keep treating unsupported non-scalar strategies as helper calls.

+ */ +public class IssExecStrategyPass extends AbstractIssPass { + + private final InstructionExecutionPlanner executionPlanner; + + public IssExecStrategyPass(IssConfiguration configuration) { + super(configuration); + this.executionPlanner = new InstructionExecutionPlanner(defaultEvaluators()); + } + + @Override + public PassName getName() { + return PassName.of("ISS Exec Strategy Classification"); + } + + @Override + public @Nullable Object execute(PassResults passResults, Specification viam) throws IOException { + if (viam.isa().isEmpty()) { + return null; + } + + var isa = viam.isa().get(); + isa.ownInstructions().forEach( + instr -> instrInfo(instr).setExecutionPlan(executionPlanner.plan(instr)) + ); + return null; + } + + private List defaultEvaluators() { + return List.of( + new DirectGvecStrategyEvaluator(), + new TcgScalarStrategyEvaluator(), + new HelperCallStrategyEvaluator() + ); + } +} diff --git a/vadl/main/vadl/iss/passes/common/planning/StrategyEvaluator.java b/vadl/main/vadl/iss/passes/common/planning/StrategyEvaluator.java new file mode 100644 index 000000000..dd2667024 --- /dev/null +++ b/vadl/main/vadl/iss/passes/common/planning/StrategyEvaluator.java @@ -0,0 +1,31 @@ +// 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.iss.passes.common.planning; + +import vadl.iss.passes.extensions.InstrExecPlan.StrategyEvaluation; +import vadl.viam.Instruction; + +/** + * Evaluates whether one execution strategy can implement a given instruction. + */ +public interface StrategyEvaluator { + + /** + * Evaluates the strategy for the given instruction. + */ + StrategyEvaluation evaluate(Instruction instruction); +} diff --git a/vadl/main/vadl/iss/passes/common/planning/analysis/VectorAnalysisSupport.java b/vadl/main/vadl/iss/passes/common/planning/analysis/VectorAnalysisSupport.java new file mode 100644 index 000000000..3a45e077d --- /dev/null +++ b/vadl/main/vadl/iss/passes/common/planning/analysis/VectorAnalysisSupport.java @@ -0,0 +1,233 @@ +// 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.iss.passes.common.planning.analysis; + +import static vadl.iss.passes.TcgPassUtils.regInfo; + +import java.util.List; +import javax.annotation.Nullable; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.AccessBaseKind; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.AccessWindowKind; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.BindingFacts; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.LayoutFacts; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.OperationKind; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.SizeFacts; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.StorageFacts; +import vadl.iss.passes.nodes.IssReadRegNode; +import vadl.iss.passes.nodes.IssWriteRegNode; +import vadl.types.BuiltInTable; +import vadl.viam.graph.Node; +import vadl.viam.graph.dependency.BuiltInCall; +import vadl.viam.graph.dependency.ConstantNode; +import vadl.viam.graph.dependency.ExpressionNode; +import vadl.viam.graph.dependency.ForIdxNode; + +/** + * Shared helper logic for vector-analysis steps. + */ +public final class VectorAnalysisSupport { + + private VectorAnalysisSupport() { + } + + /** + * Returns the read node when the expression is a lowered vector register read. + */ + public static @Nullable IssReadRegNode vectorRead(ExpressionNode node) { + return node instanceof IssReadRegNode read ? read : null; + } + + /** + * Creates a destination binding and strips the loop index from element-access indices. + */ + public static BindingFacts bindingFacts(IssWriteRegNode write, ForIdxNode idx) { + return new BindingFacts( + write.regTensor(), + vectorRegisterAccessorIndices(write.accessorIndices(), idx) + ); + } + + /** + * Creates a source binding and strips the loop index from element-access indices. + */ + public static BindingFacts bindingFacts(IssReadRegNode read, ForIdxNode idx) { + return new BindingFacts( + read.regTensor(), + vectorRegisterAccessorIndices(read.accessorIndices(), idx) + ); + } + + /** + * Removes the per-lane loop index from a lowered vector register access when present. + */ + public static List vectorRegisterAccessorIndices(List indices, + ForIdxNode idx) { + if (!indices.isEmpty() && indices.getLast() == idx) { + return List.copyOf(indices.subList(0, indices.size() - 1)); + } + return List.copyOf(indices); + } + + /** + * Derives vector shape facts from the destination binding and element/lane counts. + */ + public static SizeFacts sizeFacts(BindingFacts destination, int elementBits, int laneCount) { + var oprszBytes = elementBits * laneCount / 8; + var maxszBits = destination.registerTensor() + .resultType(destination.accessorIndices().size()) + .bitWidth(); + var maxszBytes = maxszBits / 8; + return new SizeFacts( + elementBits, + laneCount, + oprszBytes, + maxszBytes + ); + } + + /** + * Checks that the selected vector view is laid out as contiguous element lanes. + */ + public static LayoutFacts layoutFacts(BindingFacts binding, SizeFacts size) { + var dimensions = binding.registerTensor().dimensions(); + var baseIndexCount = binding.accessorIndices().size(); + var contiguousElements = dimensions.size() == baseIndexCount + 2 + && dimensions.get(baseIndexCount).size() == size.laneCount() + && dimensions.getLast().size() == size.elementBits(); + var fullRegisterRange = size.oprszBytes() == size.maxszBytes(); + return new LayoutFacts( + contiguousElements, + fullRegisterRange, + fullRegisterRange + ); + } + + /** + * Derives neutral storage facts for a register access. + */ + public static StorageFacts storageFacts(vadl.viam.RegisterTensor registerTensor) { + var info = regInfo(registerTensor); + return new StorageFacts( + info.execClass(), + info.isGvecCapable(), + registerTensor.totalWidth() % 8 == 0, + info.isCpuVector() ? info.cpuVectorAlignmentBytes() : 0 + ); + } + + /** + * Normalizes access-base classifications across read and write nodes. + */ + public static AccessBaseKind accessBaseKind(IssWriteRegNode.AccessKind accessKind) { + return switch (accessKind) { + case BASE -> AccessBaseKind.BASE; + case ALIAS -> AccessBaseKind.ALIAS; + }; + } + + /** + * Normalizes access-base classifications across read and write nodes. + */ + public static AccessBaseKind accessBaseKind(IssReadRegNode.AccessKind accessKind) { + return switch (accessKind) { + case BASE -> AccessBaseKind.BASE; + case ALIAS -> AccessBaseKind.ALIAS; + }; + } + + /** + * Normalizes access-window classifications across read and write nodes. + */ + public static AccessWindowKind accessWindowKind(IssWriteRegNode.WindowKind windowKind) { + return switch (windowKind) { + case CHUNK -> AccessWindowKind.CHUNK; + case FULL -> AccessWindowKind.FULL; + default -> AccessWindowKind.OTHER; + }; + } + + /** + * Normalizes access-window classifications across read and write nodes. + */ + public static AccessWindowKind accessWindowKind(IssReadRegNode.WindowKind windowKind) { + return switch (windowKind) { + case CHUNK -> AccessWindowKind.CHUNK; + case FULL -> AccessWindowKind.FULL; + default -> AccessWindowKind.OTHER; + }; + } + + /** + * Matches the normalized chunk offset shape {@code idx * elementBits}. + */ + public static boolean isLoopElementOffset(ExpressionNode expr, + ForIdxNode idx, + int elementBits) { + if (!(expr instanceof BuiltInCall call) || call.builtIn() != BuiltInTable.MUL + || call.arguments().size() != 2) { + return false; + } + return matchesLoopMul(call.arg(0), call.arg(1), idx, elementBits) + || matchesLoopMul(call.arg(1), call.arg(0), idx, elementBits); + } + + /** + * Checks whether the final accessor index selects the current loop lane. + */ + public static boolean isFullyIndexedElementAccess(List indices, ForIdxNode idx) { + return !indices.isEmpty() && indices.getLast() == idx; + } + + /** + * Checks whether an expression is an integer constant with the expected value. + */ + public static boolean isConstantInt(ExpressionNode expr, int expected) { + return expr instanceof ConstantNode c && c.constant().asVal().intValue() == expected; + } + + private static boolean matchesLoopMul(Node maybeIdx, + ExpressionNode maybeElementBits, + ForIdxNode idx, + int elementBits) { + return maybeIdx == idx && isConstantInt(maybeElementBits, elementBits); + } + + /** + * Maps supported lowered builtins to the current vector operation enum. + */ + public static OperationKind operationKindOf(BuiltInTable.BuiltIn builtIn) { + if (builtIn == BuiltInTable.ADD) { + return OperationKind.ADD; + } + if (builtIn == BuiltInTable.SUB) { + return OperationKind.SUB; + } + if (builtIn == BuiltInTable.AND) { + return OperationKind.AND; + } + if (builtIn == BuiltInTable.OR) { + return OperationKind.OR; + } + if (builtIn == BuiltInTable.XOR) { + return OperationKind.XOR; + } + if (builtIn == BuiltInTable.MUL) { + return OperationKind.MUL; + } + return OperationKind.OTHER; + } +} diff --git a/vadl/main/vadl/iss/passes/common/planning/analysis/VectorCandidate.java b/vadl/main/vadl/iss/passes/common/planning/analysis/VectorCandidate.java new file mode 100644 index 000000000..56121d1ab --- /dev/null +++ b/vadl/main/vadl/iss/passes/common/planning/analysis/VectorCandidate.java @@ -0,0 +1,31 @@ +// 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.iss.passes.common.planning.analysis; + +import vadl.iss.passes.nodes.IssWriteRegNode; +import vadl.viam.graph.dependency.ExpressionNode; +import vadl.viam.graph.dependency.ForIdxNode; + +/** + * The current vector instruction shape under analysis. + */ +public record VectorCandidate( + IssWriteRegNode write, + ExpressionNode valueExpression, + ForIdxNode idx +) { +} diff --git a/vadl/main/vadl/iss/passes/common/planning/analysis/VectorFactExtractor.java b/vadl/main/vadl/iss/passes/common/planning/analysis/VectorFactExtractor.java new file mode 100644 index 000000000..0d709de34 --- /dev/null +++ b/vadl/main/vadl/iss/passes/common/planning/analysis/VectorFactExtractor.java @@ -0,0 +1,65 @@ +// 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.iss.passes.common.planning.analysis; + +import java.util.List; +import vadl.iss.passes.common.planning.analysis.steps.VectorAccessPatternStep; +import vadl.iss.passes.common.planning.analysis.steps.VectorOperandStep; +import vadl.iss.passes.common.planning.analysis.steps.VectorOperationStep; +import vadl.iss.passes.common.planning.analysis.steps.VectorWriteShapeStep; +import vadl.viam.Instruction; + +/** + * Extracts reusable vector facts for one instruction. + * + *

The extracted facts are strategy-neutral. Strategy evaluators decide which of these facts are + * required and which issue codes should reject a specific execution strategy.

+ */ +public final class VectorFactExtractor { + + private final List steps; + + /** + * Creates an extractor with the given ordered fact-extraction steps. + */ + public VectorFactExtractor(List steps) { + this.steps = List.copyOf(steps); + } + + /** + * Creates the default vector fact extractor used by current strategy evaluators. + */ + public static VectorFactExtractor defaultExtractor() { + return new VectorFactExtractor(List.of( + new VectorAccessPatternStep(), + new VectorWriteShapeStep(), + new VectorOperationStep(), + new VectorOperandStep() + )); + } + + /** + * Runs all configured fact-extraction steps for one instruction. + */ + public VectorInstructionFacts extract(Instruction instruction) { + var state = new VectorFactsBuilder(instruction); + for (var step : steps) { + step.extract(state); + } + return state.toFacts(); + } +} diff --git a/vadl/main/vadl/iss/passes/common/planning/analysis/VectorFactStep.java b/vadl/main/vadl/iss/passes/common/planning/analysis/VectorFactStep.java new file mode 100644 index 000000000..c30dca8b4 --- /dev/null +++ b/vadl/main/vadl/iss/passes/common/planning/analysis/VectorFactStep.java @@ -0,0 +1,25 @@ +// 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.iss.passes.common.planning.analysis; + +/** + * A single vector fact-extraction step. + */ +public interface VectorFactStep { + + void extract(VectorFactsBuilder builder); +} diff --git a/vadl/main/vadl/iss/passes/common/planning/analysis/VectorFactsBuilder.java b/vadl/main/vadl/iss/passes/common/planning/analysis/VectorFactsBuilder.java new file mode 100644 index 000000000..aac22a1b4 --- /dev/null +++ b/vadl/main/vadl/iss/passes/common/planning/analysis/VectorFactsBuilder.java @@ -0,0 +1,105 @@ +// 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.iss.passes.common.planning.analysis; + +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.EffectFacts; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.LoopFacts; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.OperandAccessFacts; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.OperationFacts; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.WriteAccessFacts; +import vadl.viam.Instruction; + +/** + * Mutable assembly object while extracting vector facts for one instruction. + */ +public final class VectorFactsBuilder { + + private final Instruction instruction; + private int forallCount; + private boolean hasSingleForallRegisterWriteBody; + private int sideEffectCount; + private @Nullable VectorCandidate candidate; + private @Nullable WriteAccessFacts writeFacts; + private @Nullable OperationFacts operationFacts; + private final List operandFacts = new ArrayList<>(); + + public VectorFactsBuilder(Instruction instruction) { + this.instruction = instruction; + } + + public Instruction instruction() { + return instruction; + } + + public void setForallCount(int forallCount) { + this.forallCount = forallCount; + } + + public void setHasSingleForallRegisterWriteBody(boolean hasSingleForallRegisterWriteBody) { + this.hasSingleForallRegisterWriteBody = hasSingleForallRegisterWriteBody; + } + + public void setSideEffectCount(int sideEffectCount) { + this.sideEffectCount = sideEffectCount; + } + + public void setCandidate(VectorCandidate candidate) { + this.candidate = candidate; + } + + public @Nullable VectorCandidate candidate() { + return candidate; + } + + public void setWriteFacts(WriteAccessFacts writeFacts) { + this.writeFacts = writeFacts; + } + + public @Nullable WriteAccessFacts writeFacts() { + return writeFacts; + } + + public void setOperationFacts(OperationFacts operationFacts) { + this.operationFacts = operationFacts; + } + + public @Nullable OperationFacts operationFacts() { + return operationFacts; + } + + public void addOperandFact(OperandAccessFacts operandFact) { + operandFacts.add(operandFact); + } + + /** + * Materializes the immutable vector fact set extracted for this instruction. + */ + public VectorInstructionFacts toFacts() { + return new VectorInstructionFacts( + instruction, + new LoopFacts(forallCount, hasSingleForallRegisterWriteBody), + new EffectFacts(sideEffectCount), + candidate, + writeFacts, + operationFacts, + List.copyOf(operandFacts) + ); + } +} diff --git a/vadl/main/vadl/iss/passes/common/planning/analysis/VectorInstructionFacts.java b/vadl/main/vadl/iss/passes/common/planning/analysis/VectorInstructionFacts.java new file mode 100644 index 000000000..4cadf5d18 --- /dev/null +++ b/vadl/main/vadl/iss/passes/common/planning/analysis/VectorInstructionFacts.java @@ -0,0 +1,196 @@ +// 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.iss.passes.common.planning.analysis; + +import java.util.List; +import javax.annotation.Nullable; +import vadl.iss.passes.extensions.RegInfo; +import vadl.iss.passes.nodes.IssReadRegNode; +import vadl.iss.passes.nodes.IssWriteRegNode; +import vadl.viam.Instruction; +import vadl.viam.RegisterTensor; +import vadl.viam.graph.dependency.BuiltInCall; +import vadl.viam.graph.dependency.ExpressionNode; + +/** + * Reusable vector facts extracted from one instruction. + */ +public record VectorInstructionFacts( + Instruction instruction, + LoopFacts loop, + EffectFacts effects, + @Nullable VectorCandidate candidate, + @Nullable WriteAccessFacts write, + @Nullable OperationFacts operation, + List operands +) { + + public VectorInstructionFacts { + operands = List.copyOf(operands); + } + + /** + * Loop-shape facts extracted from the instruction body. + */ + public record LoopFacts( + int forallCount, + boolean hasSingleForallRegisterWriteBody + ) { + } + + /** + * Side-effect facts extracted from the instruction body. + */ + public record EffectFacts( + int sideEffectCount + ) { + } + + /** + * Write-side facts derived from the lowered destination access. + */ + public record WriteAccessFacts( + IssWriteRegNode write, + AccessBaseKind baseKind, + AccessWindowKind windowKind, + boolean elementShapeMatches, + boolean conditional, + StorageFacts storage, + BindingFacts binding, + LayoutFacts layout, + SizeFacts size, + OverlapFacts overlap + ) { + + public boolean usesSupportedWindowKind() { + return windowKind == AccessWindowKind.CHUNK || windowKind == AccessWindowKind.FULL; + } + } + + /** + * Operation-shape facts derived from the vector body value expression. + */ + public record OperationFacts( + boolean valueIsBuiltInCall, + @Nullable BuiltInCall binaryOperation, + @Nullable OperationKind operationKind + ) { + } + + /** + * Operand-side facts derived from one vector operation argument. + */ + public record OperandAccessFacts( + ExpressionNode expression, + @Nullable IssReadRegNode read, + AccessBaseKind baseKind, + AccessWindowKind windowKind, + boolean elementShapeMatches, + @Nullable StorageFacts storage, + boolean widthMatches, + @Nullable BindingFacts binding + ) { + + public boolean usesSupportedWindowKind() { + return windowKind == AccessWindowKind.CHUNK || windowKind == AccessWindowKind.FULL; + } + } + + /** + * Neutralized base-access classification for lowered register accesses. + */ + public enum AccessBaseKind { + BASE, + ALIAS, + OTHER + } + + /** + * Neutralized window classification for lowered register accesses. + */ + public enum AccessWindowKind { + CHUNK, + FULL, + OTHER + } + + /** + * Neutralized classification of storage properties relevant to later evaluators. + */ + public record StorageFacts( + RegInfo.ExecClass execClass, + boolean envOffsetAddressable, + boolean byteAddressable, + int alignmentBytes + ) { + } + + /** + * Neutralized binding facts for vector register selections. + */ + public record BindingFacts( + RegisterTensor registerTensor, + List accessorIndices + ) { + public BindingFacts { + accessorIndices = List.copyOf(accessorIndices); + } + } + + /** + * Neutralized layout facts derived from the selected vector view. + */ + public record LayoutFacts( + boolean contiguousElements, + boolean fullRegisterRange, + boolean paddingPreserved + ) { + } + + /** + * Neutralized size facts derived from the selected vector view. + */ + public record SizeFacts( + int elementBits, + int laneCount, + int oprszBytes, + int maxszBytes + ) { + } + + /** + * Neutralized overlap proof available from the extracted vector shape. + */ + public enum OverlapFacts { + NOT_ANALYZED, + EXACT_OR_DISJOINT_ONLY, + PARTIAL_POSSIBLE + } + + /** + * Neutralized vector-operation classification. + */ + public enum OperationKind { + ADD, + SUB, + AND, + OR, + XOR, + MUL, + OTHER + } +} diff --git a/vadl/main/vadl/iss/passes/common/planning/analysis/VectorStrategyIssueCode.java b/vadl/main/vadl/iss/passes/common/planning/analysis/VectorStrategyIssueCode.java new file mode 100644 index 000000000..b5c31d823 --- /dev/null +++ b/vadl/main/vadl/iss/passes/common/planning/analysis/VectorStrategyIssueCode.java @@ -0,0 +1,41 @@ +// 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.iss.passes.common.planning.analysis; + +/** + * Rejection reasons for the direct-gvec strategy evaluator. + */ +public enum VectorStrategyIssueCode { + NO_FORALL, + MULTIPLE_FORALLS, + EXTRA_SIDE_EFFECTS, + FORALL_WITHOUT_SINGLE_SIDE_EFFECT, + WRITE_NOT_BASE_CHUNK, + WRITE_HAS_CONDITION, + DESTINATION_NOT_GVEC_CAPABLE, + UNSUPPORTED_ELEMENT_WIDTH, + NON_BYTE_OPERATION_SIZE, + UNSUPPORTED_VALUE_SHAPE, + UNSUPPORTED_OPERATION, + OP_SIZE_NOT_FULL_RANGE, + LAYOUT_NOT_CONTIGUOUS, + OPERAND_NOT_VECTOR_READ, + READ_NOT_BASE_ELEMENT, + READ_NOT_GVEC_CAPABLE, + READ_WIDTH_MISMATCH, + READ_OFFSET_MISMATCH +} diff --git a/vadl/main/vadl/iss/passes/common/planning/analysis/steps/VectorAccessPatternStep.java b/vadl/main/vadl/iss/passes/common/planning/analysis/steps/VectorAccessPatternStep.java new file mode 100644 index 000000000..905f93f84 --- /dev/null +++ b/vadl/main/vadl/iss/passes/common/planning/analysis/steps/VectorAccessPatternStep.java @@ -0,0 +1,76 @@ +// 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.iss.passes.common.planning.analysis.steps; + +import java.util.List; +import vadl.iss.passes.common.planning.analysis.VectorCandidate; +import vadl.iss.passes.common.planning.analysis.VectorFactStep; +import vadl.iss.passes.common.planning.analysis.VectorFactsBuilder; +import vadl.iss.passes.nodes.IssWriteRegNode; +import vadl.viam.graph.control.ForallEndNode; +import vadl.viam.graph.control.ForallNode; +import vadl.viam.graph.dependency.SideEffectNode; + +/** + * Detects the current vector instruction shape. + */ +public final class VectorAccessPatternStep implements VectorFactStep { + + @Override + public void extract(VectorFactsBuilder builder) { + var graph = builder.instruction().behavior(); + // Later evaluators want loop and side-effect counts even for rejected shapes so they can + // explain a rejection without re-walking the graph. + var foralls = graph.getNodes(ForallNode.class).toList(); + builder.setForallCount(foralls.size()); + builder.setSideEffectCount(graph.getNodes(SideEffectNode.class).toList().size()); + analyzeForallPattern(builder, foralls); + } + + private void analyzeForallPattern(VectorFactsBuilder builder, List foralls) { + // No forall means there is no lane-structured vector body for later strategies to reason about. + if (foralls.isEmpty()) { + builder.setHasSingleForallRegisterWriteBody(false); + return; + } + + // The first vector strategies only know how to reason about one elementwise vector write. + var forallEnds = builder.instruction().behavior().getNodes(ForallEndNode.class).toList(); + if (forallEnds.size() != 1 || forallEnds.getFirst().sideEffects().size() != 1) { + builder.setHasSingleForallRegisterWriteBody(false); + return; + } + + var sideEffect = forallEnds.getFirst().sideEffects().getFirst(); + // A non-register side effect does not expose the destination binding later strategies need for + // offset, layout, and overlap reasoning. + if (!(sideEffect instanceof IssWriteRegNode write)) { + builder.setHasSingleForallRegisterWriteBody(false); + return; + } + + // Candidate extraction stays intentionally minimal: later steps and evaluators decide which + // write shape, storage kind, and operation forms a strategy can actually accept. + builder.setHasSingleForallRegisterWriteBody(true); + var idx = foralls.getFirst().idx(); + builder.setCandidate(new VectorCandidate( + write, + write.value(), + idx + )); + } +} diff --git a/vadl/main/vadl/iss/passes/common/planning/analysis/steps/VectorOperandStep.java b/vadl/main/vadl/iss/passes/common/planning/analysis/steps/VectorOperandStep.java new file mode 100644 index 000000000..248f721c0 --- /dev/null +++ b/vadl/main/vadl/iss/passes/common/planning/analysis/steps/VectorOperandStep.java @@ -0,0 +1,107 @@ +// 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.iss.passes.common.planning.analysis.steps; + +import static vadl.iss.passes.common.planning.analysis.VectorAnalysisSupport.accessBaseKind; +import static vadl.iss.passes.common.planning.analysis.VectorAnalysisSupport.accessWindowKind; +import static vadl.iss.passes.common.planning.analysis.VectorAnalysisSupport.bindingFacts; +import static vadl.iss.passes.common.planning.analysis.VectorAnalysisSupport.isConstantInt; +import static vadl.iss.passes.common.planning.analysis.VectorAnalysisSupport.isFullyIndexedElementAccess; +import static vadl.iss.passes.common.planning.analysis.VectorAnalysisSupport.isLoopElementOffset; +import static vadl.iss.passes.common.planning.analysis.VectorAnalysisSupport.storageFacts; +import static vadl.iss.passes.common.planning.analysis.VectorAnalysisSupport.vectorRead; + +import javax.annotation.Nullable; +import vadl.iss.passes.common.planning.analysis.VectorFactStep; +import vadl.iss.passes.common.planning.analysis.VectorFactsBuilder; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.OperandAccessFacts; +import vadl.iss.passes.nodes.IssReadRegNode; + +/** + * Extracts operand-side access and binding facts from the matched vector operation. + */ +public final class VectorOperandStep implements VectorFactStep { + + @Override + public void extract(VectorFactsBuilder builder) { + var candidate = builder.candidate(); + var writeFacts = builder.writeFacts(); + var operationFacts = builder.operationFacts(); + var operationCall = operationFacts == null ? null : operationFacts.binaryOperation(); + if (candidate == null || writeFacts == null || operationCall == null) { + return; + } + + for (var arg : operationCall.arguments()) { + // Each operand is recorded independently so different strategies can make different decisions + // about vector, scalar, immediate, alias, or broadcast forms from the same fact set. + var read = vectorRead(arg); + builder.addOperandFact(operandFact( + arg, + read, + candidate.idx(), + writeFacts.size().elementBits() + )); + } + } + + private OperandAccessFacts operandFact(vadl.viam.graph.dependency.ExpressionNode expression, + @Nullable IssReadRegNode read, + vadl.viam.graph.dependency.ForIdxNode idx, + int elementBits) { + // Non-register operands are kept as explicit facts rather than dropped so evaluators can + // distinguish scalar/immediate rejection from "no analysis result". + if (read == null) { + return new OperandAccessFacts( + expression, + null, + vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.AccessBaseKind.OTHER, + vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.AccessWindowKind.OTHER, + false, + null, + false, + null + ); + } + + return new OperandAccessFacts( + expression, + read, + accessBaseKind(read.accessKind()), + accessWindowKind(read.windowKind()), + matchesElementShape(read, idx, elementBits), + storageFacts(read.regTensor()), + read.readBitWidth() == elementBits, + bindingFacts(read, idx) + ); + } + + private boolean matchesElementShape(IssReadRegNode read, + vadl.viam.graph.dependency.ForIdxNode idx, + int elementBits) { + // Read-side shape matching mirrors the write side so later evaluators can reason about exact + // lane correspondence without duplicating offset decoding. + if (read.windowKind() == IssReadRegNode.WindowKind.CHUNK) { + return isLoopElementOffset(read.bitOffset(), idx, elementBits) + && isConstantInt(read.bitWidth(), elementBits); + } + if (read.windowKind() == IssReadRegNode.WindowKind.FULL) { + return isFullyIndexedElementAccess(read.accessorIndices(), idx); + } + return false; + } +} diff --git a/vadl/main/vadl/iss/passes/common/planning/analysis/steps/VectorOperationStep.java b/vadl/main/vadl/iss/passes/common/planning/analysis/steps/VectorOperationStep.java new file mode 100644 index 000000000..5086cb420 --- /dev/null +++ b/vadl/main/vadl/iss/passes/common/planning/analysis/steps/VectorOperationStep.java @@ -0,0 +1,60 @@ +// 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.iss.passes.common.planning.analysis.steps; + +import javax.annotation.Nullable; +import vadl.iss.passes.common.planning.analysis.VectorAnalysisSupport; +import vadl.iss.passes.common.planning.analysis.VectorFactStep; +import vadl.iss.passes.common.planning.analysis.VectorFactsBuilder; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.OperationFacts; +import vadl.viam.graph.dependency.BuiltInCall; +import vadl.viam.graph.dependency.ExpressionNode; + +/** + * Extracts operation-shape facts from the vector body value expression. + */ +public final class VectorOperationStep implements VectorFactStep { + + @Override + public void extract(VectorFactsBuilder builder) { + var candidate = builder.candidate(); + if (candidate == null) { + return; + } + + // The fact layer records the operation shape without deciding yet whether any concrete + // strategy can lower it directly. + var valueIsBuiltInCall = candidate.valueExpression() instanceof BuiltInCall; + var operationCall = binaryOperation(candidate.valueExpression()); + builder.setOperationFacts(new OperationFacts( + valueIsBuiltInCall, + operationCall, + operationCall == null + ? null + : VectorAnalysisSupport.operationKindOf(operationCall.builtIn()) + )); + } + + private @Nullable BuiltInCall binaryOperation(ExpressionNode node) { + // The first vector strategies only reason about binary lane-local expressions. Broader shapes + // still flow through the fact model as rejected operation forms. + if (!(node instanceof BuiltInCall call) || call.arguments().size() != 2) { + return null; + } + return call; + } +} diff --git a/vadl/main/vadl/iss/passes/common/planning/analysis/steps/VectorWriteShapeStep.java b/vadl/main/vadl/iss/passes/common/planning/analysis/steps/VectorWriteShapeStep.java new file mode 100644 index 000000000..87533c5a4 --- /dev/null +++ b/vadl/main/vadl/iss/passes/common/planning/analysis/steps/VectorWriteShapeStep.java @@ -0,0 +1,88 @@ +// 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.iss.passes.common.planning.analysis.steps; + +import static vadl.iss.passes.common.planning.analysis.VectorAnalysisSupport.accessBaseKind; +import static vadl.iss.passes.common.planning.analysis.VectorAnalysisSupport.accessWindowKind; +import static vadl.iss.passes.common.planning.analysis.VectorAnalysisSupport.bindingFacts; +import static vadl.iss.passes.common.planning.analysis.VectorAnalysisSupport.isConstantInt; +import static vadl.iss.passes.common.planning.analysis.VectorAnalysisSupport.isFullyIndexedElementAccess; +import static vadl.iss.passes.common.planning.analysis.VectorAnalysisSupport.isLoopElementOffset; +import static vadl.iss.passes.common.planning.analysis.VectorAnalysisSupport.layoutFacts; +import static vadl.iss.passes.common.planning.analysis.VectorAnalysisSupport.sizeFacts; +import static vadl.iss.passes.common.planning.analysis.VectorAnalysisSupport.storageFacts; + +import vadl.iss.passes.common.planning.analysis.VectorFactStep; +import vadl.iss.passes.common.planning.analysis.VectorFactsBuilder; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.OverlapFacts; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.WriteAccessFacts; +import vadl.iss.passes.nodes.IssWriteRegNode; + +/** + * Extracts write-side properties plus destination binding and shape facts. + */ +public final class VectorWriteShapeStep implements VectorFactStep { + + @Override + public void extract(VectorFactsBuilder builder) { + var candidate = builder.candidate(); + if (candidate == null) { + return; + } + + var elementBits = candidate.write().writeBitWidth(); + var laneCount = candidate.idx().toIdx() - candidate.idx().fromIdx() + 1; + // Binding extraction strips the lane index so later evaluators see the selected vector + // register rather than one per-lane access inside the lowered loop body. + var destination = bindingFacts(candidate.write(), candidate.idx()); + // Size and layout remain strategy-neutral here so later strategies can reuse them with + // different acceptance rules. + var size = sizeFacts(destination, elementBits, laneCount); + var layout = layoutFacts(destination, size); + var write = candidate.write(); + builder.setWriteFacts(new WriteAccessFacts( + write, + accessBaseKind(write.accessKind()), + accessWindowKind(write.windowKind()), + matchesElementShape(write, candidate.idx(), elementBits), + write.nullableCondition() != null, + storageFacts(write.regTensor()), + destination, + layout, + size, + // The extracted shape is a full-lane overwrite where equal register selectors overlap + // exactly and distinct selectors are disjoint. + OverlapFacts.EXACT_OR_DISJOINT_ONLY + )); + } + + private boolean matchesElementShape(IssWriteRegNode write, + vadl.viam.graph.dependency.ForIdxNode idx, + int elementBits) { + // Chunk windows are the normalized scalarized form of one lane inside a vector register. + if (write.windowKind() == IssWriteRegNode.WindowKind.CHUNK) { + return isLoopElementOffset(write.bitOffset(), idx, elementBits) + && isConstantInt(write.bitWidth(), elementBits); + } + // Full windows are still usable as long as the final accessor index selects the active lane. + if (write.windowKind() == IssWriteRegNode.WindowKind.FULL) { + return isFullyIndexedElementAccess(write.accessorIndices(), idx) + && write.writeBitWidth() == elementBits; + } + return false; + } +} diff --git a/vadl/main/vadl/iss/passes/common/planning/evaluators/DirectGvecStrategyEvaluator.java b/vadl/main/vadl/iss/passes/common/planning/evaluators/DirectGvecStrategyEvaluator.java new file mode 100644 index 000000000..011b249ef --- /dev/null +++ b/vadl/main/vadl/iss/passes/common/planning/evaluators/DirectGvecStrategyEvaluator.java @@ -0,0 +1,254 @@ +// 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.iss.passes.common.planning.evaluators; + +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; +import vadl.iss.passes.common.planning.StrategyEvaluator; +import vadl.iss.passes.common.planning.analysis.VectorFactExtractor; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.AccessBaseKind; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.BindingFacts; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.LayoutFacts; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.OperationKind; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.OverlapFacts; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.SizeFacts; +import vadl.iss.passes.common.planning.analysis.VectorInstructionFacts.WriteAccessFacts; +import vadl.iss.passes.common.planning.analysis.VectorStrategyIssueCode; +import vadl.iss.passes.extensions.InstrExecPlan; +import vadl.iss.passes.extensions.InstrExecPlan.StrategyEvaluation; +import vadl.iss.passes.extensions.InstrExecPlan.StrategyIssue; +import vadl.iss.passes.extensions.InstrExecPlan.StrategyKind; +import vadl.iss.passes.extensions.VectorTensorPlan; +import vadl.iss.passes.extensions.VectorTensorPlan.OverlapPolicy; +import vadl.iss.passes.extensions.VectorTensorPlan.VectorOperand; +import vadl.iss.passes.extensions.VectorTensorPlan.VectorRegisterBinding; +import vadl.iss.passes.extensions.VectorTensorPlan.VectorShape; +import vadl.viam.Instruction; + +/** + * Evaluates the direct-gvec strategy for vector instructions. + */ +public final class DirectGvecStrategyEvaluator implements StrategyEvaluator { + + private static final int DIRECT_GVEC_ESTIMATED_COST = 10; + + private final VectorFactExtractor factExtractor = VectorFactExtractor.defaultExtractor(); + + @Override + public StrategyEvaluation evaluate(Instruction instruction) { + var facts = factExtractor.extract(instruction); + var issues = new ArrayList(); + + // Strategy evaluation is additive: each check only rejects DIRECT_GVEC, not the whole + // instruction. Other strategies still get the same neutral fact set. + recordGeneralShapeIssues(facts, issues); + + var candidate = facts.candidate(); + if (candidate == null) { + return rejected(issues); + } + + recordWriteIssues(facts.write(), issues); + + var operationFacts = facts.operation(); + var vectorOp = evaluateOperation(operationFacts, issues); + + var operands = recordOperandIssues(facts, issues); + var writeFacts = facts.write(); + if (!issues.isEmpty() || writeFacts == null || vectorOp == null) { + return rejected(issues); + } + + // The direct-gvec plan is assembled only after all direct-gvec-specific preconditions hold. + return InstrExecPlan.StrategyEvaluation.viable( + StrategyKind.DIRECT_GVEC, + DIRECT_GVEC_ESTIMATED_COST, + VectorTensorPlan.directGvecCandidate( + vectorOp, + vectorShape(writeFacts.size(), writeFacts.layout()), + vectorRegisterBinding(writeFacts.binding()), + overlapPolicy(writeFacts.overlap()), + operands + ) + ); + } + + private void recordGeneralShapeIssues(VectorInstructionFacts facts, + List issues) { + // Direct gvec starts from one normalized vector loop. More complex loop structures may still + // be usable by other vector strategies later. + if (facts.loop().forallCount() == 0) { + issues.add(StrategyIssue.of(VectorStrategyIssueCode.NO_FORALL)); + return; + } + if (facts.loop().forallCount() != 1) { + issues.add(StrategyIssue.of(VectorStrategyIssueCode.MULTIPLE_FORALLS)); + } + if (facts.effects().sideEffectCount() != 1) { + issues.add(StrategyIssue.of(VectorStrategyIssueCode.EXTRA_SIDE_EFFECTS)); + } + if (!facts.loop().hasSingleForallRegisterWriteBody()) { + issues.add(StrategyIssue.of(VectorStrategyIssueCode.FORALL_WITHOUT_SINGLE_SIDE_EFFECT)); + } + } + + private @Nullable VectorTensorPlan.VectorOp evaluateOperation( + @Nullable VectorInstructionFacts.OperationFacts operationFacts, + List issues) { + // Distinguish "not a binary builtin shape" from "binary builtin but not one of the currently + // mapped direct-gvec ops" so diagnostics stay useful as more strategies are added. + if (operationFacts == null || operationFacts.binaryOperation() == null) { + if (operationFacts != null && operationFacts.valueIsBuiltInCall()) { + issues.add(StrategyIssue.of(VectorStrategyIssueCode.UNSUPPORTED_OPERATION)); + } else { + issues.add(StrategyIssue.of(VectorStrategyIssueCode.UNSUPPORTED_VALUE_SHAPE)); + } + return null; + } + + var vectorOp = vectorOpOf(operationFacts.operationKind()); + if (vectorOp == null) { + issues.add(StrategyIssue.of(VectorStrategyIssueCode.UNSUPPORTED_OPERATION)); + } + return vectorOp; + } + + private void recordWriteIssues(@Nullable WriteAccessFacts writeFacts, + List issues) { + if (writeFacts == null) { + return; + } + + var sizeFacts = writeFacts.size(); + // Direct gvec currently assumes byte-addressable lane sizes with one full vector operation + // region. Smaller or irregular shapes remain available to other strategies. + if (sizeFacts.elementBits() <= 0 + || sizeFacts.laneCount() <= 0 + || sizeFacts.elementBits() > 64) { + issues.add(StrategyIssue.of(VectorStrategyIssueCode.UNSUPPORTED_ELEMENT_WIDTH)); + } + if ((sizeFacts.elementBits() * sizeFacts.laneCount()) % 8 != 0) { + issues.add(StrategyIssue.of(VectorStrategyIssueCode.NON_BYTE_OPERATION_SIZE)); + } + + // Direct gvec needs a destination access that can be translated into a stable env offset plus + // a plain contiguous vector region. + if (writeFacts.baseKind() != AccessBaseKind.BASE || !writeFacts.usesSupportedWindowKind()) { + issues.add(StrategyIssue.of(VectorStrategyIssueCode.WRITE_NOT_BASE_CHUNK)); + } else if (!writeFacts.elementShapeMatches()) { + issues.add(StrategyIssue.of(VectorStrategyIssueCode.WRITE_NOT_BASE_CHUNK)); + } + if (writeFacts.conditional()) { + issues.add(StrategyIssue.of(VectorStrategyIssueCode.WRITE_HAS_CONDITION)); + } + if (!writeFacts.storage().envOffsetAddressable()) { + issues.add(StrategyIssue.of(VectorStrategyIssueCode.DESTINATION_NOT_GVEC_CAPABLE)); + } + if (!writeFacts.layout().fullRegisterRange()) { + issues.add(StrategyIssue.of(VectorStrategyIssueCode.OP_SIZE_NOT_FULL_RANGE)); + } + if (!writeFacts.layout().contiguousElements()) { + issues.add(StrategyIssue.of(VectorStrategyIssueCode.LAYOUT_NOT_CONTIGUOUS)); + } + } + + private List recordOperandIssues(VectorInstructionFacts facts, + List issues) { + var operands = new ArrayList(); + for (var operandFacts : facts.operands()) { + // Direct gvec currently only accepts vector-register operands. Keeping the extracted operand + // facts explicit lets later strategies reuse the same analysis for scalar or immediate forms. + var read = operandFacts.read(); + if (read == null) { + issues.add(StrategyIssue.of(VectorStrategyIssueCode.OPERAND_NOT_VECTOR_READ)); + continue; + } + if (operandFacts.baseKind() != AccessBaseKind.BASE + || !operandFacts.usesSupportedWindowKind()) { + issues.add(StrategyIssue.of(VectorStrategyIssueCode.READ_NOT_BASE_ELEMENT)); + } else if (!operandFacts.elementShapeMatches()) { + issues.add(StrategyIssue.of(VectorStrategyIssueCode.READ_OFFSET_MISMATCH)); + } + var storageFacts = operandFacts.storage(); + if (storageFacts == null || !storageFacts.envOffsetAddressable()) { + issues.add(StrategyIssue.of(VectorStrategyIssueCode.READ_NOT_GVEC_CAPABLE)); + } + if (!operandFacts.widthMatches()) { + issues.add(StrategyIssue.of(VectorStrategyIssueCode.READ_WIDTH_MISMATCH)); + } + if (operandFacts.binding() != null) { + operands.add(VectorOperand.vectorRegister(vectorRegisterBinding(operandFacts.binding()))); + } + } + return List.copyOf(operands); + } + + private @Nullable VectorTensorPlan.VectorOp vectorOpOf(@Nullable OperationKind operationKind) { + if (operationKind == null) { + return null; + } + // The neutral operation kind is translated to a direct-gvec-specific opcode only here. + return switch (operationKind) { + case ADD -> VectorTensorPlan.VectorOp.ADD; + case SUB -> VectorTensorPlan.VectorOp.SUB; + case AND -> VectorTensorPlan.VectorOp.AND; + case OR -> VectorTensorPlan.VectorOp.OR; + case XOR -> VectorTensorPlan.VectorOp.XOR; + case MUL -> VectorTensorPlan.VectorOp.MUL; + case OTHER -> null; + }; + } + + private VectorRegisterBinding vectorRegisterBinding(BindingFacts bindingFacts) { + return new VectorRegisterBinding( + bindingFacts.registerTensor(), + bindingFacts.accessorIndices() + ); + } + + private VectorShape vectorShape(SizeFacts sizeFacts, LayoutFacts layoutFacts) { + return new VectorShape( + sizeFacts.elementBits(), + sizeFacts.laneCount(), + sizeFacts.oprszBytes(), + sizeFacts.maxszBytes(), + layoutFacts.fullRegisterRange(), + layoutFacts.contiguousElements(), + layoutFacts.paddingPreserved() + ); + } + + private OverlapPolicy overlapPolicy(OverlapFacts overlapFacts) { + // Strategy-neutral overlap facts are mapped to the narrower direct-gvec overlap policy only + // when this evaluator commits to a direct-gvec plan. + return switch (overlapFacts) { + case NOT_ANALYZED -> OverlapPolicy.NOT_ANALYZED; + case EXACT_OR_DISJOINT_ONLY -> OverlapPolicy.NO_PARTIAL_OVERLAP; + case PARTIAL_POSSIBLE -> OverlapPolicy.NOT_ANALYZED; + }; + } + + private StrategyEvaluation rejected(List issues) { + return InstrExecPlan.StrategyEvaluation.rejected( + StrategyKind.DIRECT_GVEC, + DIRECT_GVEC_ESTIMATED_COST, + List.copyOf(issues) + ); + } +} diff --git a/vadl/main/vadl/iss/passes/common/planning/evaluators/HelperCallStrategyEvaluator.java b/vadl/main/vadl/iss/passes/common/planning/evaluators/HelperCallStrategyEvaluator.java new file mode 100644 index 000000000..7dc6e8263 --- /dev/null +++ b/vadl/main/vadl/iss/passes/common/planning/evaluators/HelperCallStrategyEvaluator.java @@ -0,0 +1,43 @@ +// 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.iss.passes.common.planning.evaluators; + +import vadl.iss.passes.common.planning.StrategyEvaluator; +import vadl.iss.passes.extensions.InstrExecPlan; +import vadl.iss.passes.extensions.InstrExecPlan.HelperCallPlan; +import vadl.iss.passes.extensions.InstrExecPlan.StrategyEvaluation; +import vadl.iss.passes.extensions.InstrExecPlan.StrategyKind; +import vadl.viam.Instruction; + +/** + * Fallback evaluator that always keeps whole-instruction helper execution available. + */ +public final class HelperCallStrategyEvaluator implements StrategyEvaluator { + + private static final int HELPER_CALL_ESTIMATED_COST = 1_000; + + @Override + public StrategyEvaluation evaluate(Instruction instruction) { + // Whole-instruction helper execution stays always viable so the planner can reject optimized + // strategies locally without losing a correct fallback. + return InstrExecPlan.StrategyEvaluation.viable( + StrategyKind.HELPER_CALL, + HELPER_CALL_ESTIMATED_COST, + new HelperCallPlan() + ); + } +} diff --git a/vadl/main/vadl/iss/passes/common/planning/evaluators/TcgScalarStrategyEvaluator.java b/vadl/main/vadl/iss/passes/common/planning/evaluators/TcgScalarStrategyEvaluator.java new file mode 100644 index 000000000..9b8e161f0 --- /dev/null +++ b/vadl/main/vadl/iss/passes/common/planning/evaluators/TcgScalarStrategyEvaluator.java @@ -0,0 +1,96 @@ +// 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.iss.passes.common.planning.evaluators; + +import static vadl.iss.passes.TcgPassUtils.regInfo; + +import java.util.ArrayList; +import java.util.List; +import vadl.iss.passes.common.planning.StrategyEvaluator; +import vadl.iss.passes.extensions.InstrExecPlan; +import vadl.iss.passes.extensions.InstrExecPlan.StrategyEvaluation; +import vadl.iss.passes.extensions.InstrExecPlan.StrategyIssue; +import vadl.iss.passes.extensions.InstrExecPlan.StrategyKind; +import vadl.iss.passes.extensions.InstrExecPlan.TcgScalarPlan; +import vadl.iss.passes.extensions.RegInfo; +import vadl.iss.passes.nodes.IssReadRegNode; +import vadl.iss.passes.nodes.IssWriteRegNode; +import vadl.viam.Instruction; +import vadl.viam.graph.control.ForallNode; +import vadl.viam.graph.dependency.FoldNode; +import vadl.viam.graph.dependency.TensorNode; + +/** + * Evaluates whether the existing scalar TCG pipeline can lower the instruction directly. + */ +public final class TcgScalarStrategyEvaluator implements StrategyEvaluator { + + private static final int TCG_SCALAR_ESTIMATED_COST = 100; + + @Override + public StrategyEvaluation evaluate(Instruction instruction) { + var issues = new ArrayList(); + var behavior = instruction.behavior(); + + // Scalar TCG is treated as the existing baseline path. It rejects instructions only when the + // current scalar lowering pipeline would have to rediscover vector/tensor semantics it does + // not model directly. + var hasCpuVectorReads = behavior.getNodes(IssReadRegNode.class) + .anyMatch(node -> regInfo(node.regTensor()).execClass() == RegInfo.ExecClass.CPU_VECTOR); + var hasCpuVectorWrites = behavior.getNodes(IssWriteRegNode.class) + .anyMatch(node -> regInfo(node.regTensor()).execClass() == RegInfo.ExecClass.CPU_VECTOR); + var hasForall = behavior.getNodes(ForallNode.class).findAny().isPresent(); + var hasTensor = behavior.getNodes(TensorNode.class).findAny().isPresent(); + if (hasCpuVectorReads || hasCpuVectorWrites) { + issues.add(StrategyIssue.of(IssueCode.USES_CPU_VECTOR_STORAGE)); + } + if (hasForall) { + issues.add(StrategyIssue.of(IssueCode.HAS_FORALL)); + } + if (hasTensor) { + issues.add(StrategyIssue.of(IssueCode.HAS_TENSOR_EXPR)); + } + var hasFold = behavior.getNodes(FoldNode.class).findAny().isPresent(); + if (hasFold) { + issues.add(StrategyIssue.of(IssueCode.HAS_FOLD)); + } + + if (!issues.isEmpty()) { + return InstrExecPlan.StrategyEvaluation.rejected( + StrategyKind.TCG_SCALAR, + TCG_SCALAR_ESTIMATED_COST, + List.copyOf(issues) + ); + } + + return InstrExecPlan.StrategyEvaluation.viable( + StrategyKind.TCG_SCALAR, + TCG_SCALAR_ESTIMATED_COST, + new TcgScalarPlan() + ); + } + + /** + * Rejection reasons for the current scalar TCG pipeline. + */ + public enum IssueCode { + USES_CPU_VECTOR_STORAGE, + HAS_FORALL, + HAS_TENSOR_EXPR, + HAS_FOLD + } +} diff --git a/vadl/main/vadl/iss/passes/extensions/InstrExecPlan.java b/vadl/main/vadl/iss/passes/extensions/InstrExecPlan.java new file mode 100644 index 000000000..193b45478 --- /dev/null +++ b/vadl/main/vadl/iss/passes/extensions/InstrExecPlan.java @@ -0,0 +1,168 @@ +// 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.iss.passes.extensions; + +import java.util.List; +import javax.annotation.Nullable; + +/** + * General execution-planning result for one instruction. + * + *

It keeps all evaluated execution strategies, their rejection reasons, and the selected + * best-performing surviving strategy.

+ */ +public record InstrExecPlan( + List evaluations, + StrategyEvaluation selectedEvaluation +) { + + public InstrExecPlan { + evaluations = List.copyOf(evaluations); + } + + /** + * Returns the selected execution strategy. + */ + public StrategyKind selectedStrategy() { + return selectedEvaluation.strategy(); + } + + /** + * Returns the evaluation for the requested strategy, if present. + */ + public @Nullable StrategyEvaluation evaluation(StrategyKind strategy) { + return evaluations.stream() + .filter(evaluation -> evaluation.strategy() == strategy) + .findFirst() + .orElse(null); + } + + /** + * Backend-independent execution strategy kinds. + */ + public enum StrategyKind { + TCG_SCALAR, + DIRECT_GVEC, + HELPER_CALL + } + + /** + * Outcome of evaluating one candidate execution strategy. + */ + public enum EvaluationStatus { + VIABLE, + REJECTED + } + + /** + * A diagnostic issue recorded while evaluating one strategy. + */ + public record StrategyIssue(String code) { + + /** + * Creates a strategy issue from an enum constant. + */ + public static StrategyIssue of(Enum issueCode) { + return new StrategyIssue(issueCode.name()); + } + } + + /** + * One strategy evaluation including viability, issues, and the concrete strategy plan. + */ + public record StrategyEvaluation( + StrategyKind strategy, + EvaluationStatus status, + int estimatedCost, + List issues, + @Nullable StrategyPlan plan + ) { + + public StrategyEvaluation { + issues = List.copyOf(issues); + } + + /** + * Creates a viable strategy evaluation. + */ + public static StrategyEvaluation viable(StrategyKind strategy, + int estimatedCost, + StrategyPlan plan) { + return new StrategyEvaluation( + strategy, + EvaluationStatus.VIABLE, + estimatedCost, + List.of(), + plan + ); + } + + /** + * Creates a rejected strategy evaluation. + */ + public static StrategyEvaluation rejected(StrategyKind strategy, + int estimatedCost, + List issues) { + return new StrategyEvaluation( + strategy, + EvaluationStatus.REJECTED, + estimatedCost, + issues, + null + ); + } + + /** + * Returns whether this strategy survived evaluation. + */ + public boolean isViable() { + return status == EvaluationStatus.VIABLE; + } + + /** + * Returns whether this evaluation recorded the given issue code. + */ + public boolean hasIssue(String code) { + return issues.stream().anyMatch(issue -> issue.code().equals(code)); + } + + /** + * Returns the plan cast to the requested type, if it matches. + */ + public @Nullable T planAs(Class planType) { + return planType.isInstance(plan) ? planType.cast(plan) : null; + } + } + + /** + * Marker interface for concrete execution-strategy plans. + */ + public sealed interface StrategyPlan permits HelperCallPlan, TcgScalarPlan, VectorTensorPlan { + } + + /** + * Selected when the existing scalar TCG pipeline can lower the instruction directly. + */ + public record TcgScalarPlan() implements StrategyPlan { + } + + /** + * Selected when the instruction must still execute as a whole-instruction helper call. + */ + public record HelperCallPlan() implements StrategyPlan { + } +} diff --git a/vadl/main/vadl/iss/passes/extensions/InstrInfo.java b/vadl/main/vadl/iss/passes/extensions/InstrInfo.java index 11dcad78f..e57ce2198 100644 --- a/vadl/main/vadl/iss/passes/extensions/InstrInfo.java +++ b/vadl/main/vadl/iss/passes/extensions/InstrInfo.java @@ -57,6 +57,9 @@ public enum ExecStrategy { @Nullable private ExecStrategy execStrategy = null; + @Nullable + private InstrExecPlan executionPlan = null; + List extractedFunctions = new ArrayList<>(); /** @@ -84,7 +87,36 @@ public void setExecStrategy(ExecStrategy execStrategy) { this.execStrategy = execStrategy; } + /** + * Returns the execution plan computed for this instruction, if any. + */ + public @Nullable InstrExecPlan executionPlan() { + return executionPlan; + } + + /** + * Stores the execution plan computed for this instruction. + */ + public void setExecutionPlan(InstrExecPlan executionPlan) { + this.executionPlan = executionPlan; + this.execStrategy = mapExecutionPlanToExecStrategy(executionPlan); + } + + /** + * Returns the selected direct-gvec plan for this instruction, if any. + */ + public @Nullable VectorTensorPlan directGvecPlan() { + if (executionPlan == null) { + return null; + } + var evaluation = executionPlan.evaluation(InstrExecPlan.StrategyKind.DIRECT_GVEC); + return evaluation == null ? null : evaluation.planAs(VectorTensorPlan.class); + } + private ExecStrategy computeFallbackExecStrategy() { + if (executionPlan != null) { + return mapExecutionPlanToExecStrategy(executionPlan); + } var hasCpuVectorReads = instr().behavior().getNodes(IssReadRegNode.class) .anyMatch(n -> regInfo(n.regTensor()).execClass() == RegInfo.ExecClass.CPU_VECTOR); var hasCpuVectorWrites = instr().behavior().getNodes(IssWriteRegNode.class) @@ -94,6 +126,12 @@ private ExecStrategy computeFallbackExecStrategy() { : ExecStrategy.DIRECT_TCG; } + private ExecStrategy mapExecutionPlanToExecStrategy(InstrExecPlan executionPlan) { + return executionPlan.selectedStrategy() == InstrExecPlan.StrategyKind.TCG_SCALAR + ? ExecStrategy.DIRECT_TCG + : ExecStrategy.HELPER_CALL; + } + /** * Determines if the instruction's loops should be unrolled. */ diff --git a/vadl/main/vadl/iss/passes/extensions/VectorTensorPlan.java b/vadl/main/vadl/iss/passes/extensions/VectorTensorPlan.java new file mode 100644 index 000000000..4d5521eb9 --- /dev/null +++ b/vadl/main/vadl/iss/passes/extensions/VectorTensorPlan.java @@ -0,0 +1,135 @@ +// 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.iss.passes.extensions; + +import java.util.List; +import javax.annotation.Nullable; +import vadl.viam.RegisterTensor; +import vadl.viam.graph.dependency.ExpressionNode; + +/** + * Diagnostic ISS vector translation plan. + * + *

It stores instruction properties that are important to generate optimized QEMU vector + * instruction execution. + */ +public record VectorTensorPlan( + VectorOp op, + VectorShape shape, + VectorRegisterBinding destination, + OverlapPolicy overlapPolicy, + List operands +) implements InstrExecPlan.StrategyPlan { + + /** + * Creates a direct-gvec candidate plan. + */ + public static VectorTensorPlan directGvecCandidate(VectorOp op, + VectorShape shape, + VectorRegisterBinding destination, + OverlapPolicy overlapPolicy, + List operands) { + return new VectorTensorPlan( + op, + shape, + destination, + overlapPolicy, + List.copyOf(operands) + ); + } + + public int elementBits() { + return shape.elementBits(); + } + + public int laneCount() { + return shape.laneCount(); + } + + public int opBytes() { + return shape.oprszBytes(); + } + + /** + * Vector operation recognized by the first matcher. + */ + public enum VectorOp { + NONE, + ADD, + SUB, + AND, + OR, + XOR, + MUL + } + + /** + * Proven vector operation shape and size/layout facts for direct-gvec candidates. + */ + public record VectorShape( + int elementBits, + int laneCount, + int oprszBytes, + int maxszBytes, + boolean fullRange, + boolean contiguousLayout, + boolean paddingPreserved + ) { + } + + /** + * Register binding that preserves the accessor indices needed for future offset emission. + */ + public record VectorRegisterBinding( + RegisterTensor registerTensor, + List accessorIndices + ) { + public VectorRegisterBinding { + accessorIndices = List.copyOf(accessorIndices); + } + } + + /** + * Operand binding for recognized vector plans. + */ + public record VectorOperand(OperandKind kind, + @Nullable VectorRegisterBinding registerBinding) { + + public static VectorOperand vectorRegister(VectorRegisterBinding binding) { + return new VectorOperand(OperandKind.VECTOR_REGISTER, binding); + } + } + + /** + * Operand categories expected by later vector planning. + */ + public enum OperandKind { + VECTOR_REGISTER, + SCALAR_REGISTER, + IMMEDIATE, + BROADCAST, + PREDICATE + } + + /** + * Register-overlap proof available to a future direct-gvec emitter. + */ + public enum OverlapPolicy { + NOT_ANALYZED, + NO_PARTIAL_OVERLAP + } +} diff --git a/vadl/main/vadl/pass/order/IssPassOrder.java b/vadl/main/vadl/pass/order/IssPassOrder.java index a0c6ff110..5e643db4f 100644 --- a/vadl/main/vadl/pass/order/IssPassOrder.java +++ b/vadl/main/vadl/pass/order/IssPassOrder.java @@ -29,7 +29,6 @@ import vadl.iss.passes.common.IssBuiltInArgTruncOptPass; import vadl.iss.passes.common.IssCommonExprSavePass; import vadl.iss.passes.common.IssConfigurationPass; -import vadl.iss.passes.common.IssExecStrategyPass; import vadl.iss.passes.common.IssExtractOptimizationPass; import vadl.iss.passes.common.IssGdbInfoExtractionPass; import vadl.iss.passes.common.IssInfoRetrievalPass; @@ -42,6 +41,7 @@ import vadl.iss.passes.common.IssScheduleIndirectJumpsPass; import vadl.iss.passes.common.IssTensorAssignmentToForallPass; import vadl.iss.passes.common.opDecomposition.IssOpDecompositionPass; +import vadl.iss.passes.common.planning.IssExecStrategyPass; import vadl.iss.passes.common.safeResourceRead.IssSafeResourceReadPass; import vadl.iss.passes.helper.IssCFunctionExtractionPass; import vadl.iss.passes.scalar.IssHardcodedTcgAddOnPass; @@ -118,10 +118,10 @@ private static void addCommonPasses(PassOrder order, IssConfiguration config) { .add(new IssApplyMemoryEndiannessPass(config)) .add(new IssMemoryDetectionPass(config)) .add(new IssRegisterAccessLoweringPass(config)) - .add(new IssExecStrategyPass(config)) .add(new IssBitfieldWriteLoweringPass(config)) .add(new IssTensorAssignmentToForallPass(config)) .add(new CanonicalizationPass(config)) + .add(new IssExecStrategyPass(config)) .add(new IssOpDecompositionPass(config)) .add(new IssNormalizationPass(config)) .add(new IssExtractOptimizationPass(config)) diff --git a/vadl/test/vadl/iss/passes/IssVectorTcgAnalysisPassTest.java b/vadl/test/vadl/iss/passes/IssVectorTcgAnalysisPassTest.java new file mode 100644 index 000000000..717fea8a0 --- /dev/null +++ b/vadl/test/vadl/iss/passes/IssVectorTcgAnalysisPassTest.java @@ -0,0 +1,172 @@ +// 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.iss.passes; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static vadl.TestUtils.findDefinitionByNameIn; +import static vadl.iss.passes.TcgPassUtils.instrInfo; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import org.junit.jupiter.api.Test; +import vadl.AbstractTest; +import vadl.configuration.DumpMode; +import vadl.configuration.GeneralConfiguration; +import vadl.configuration.IssConfiguration; +import vadl.iss.passes.common.planning.IssExecStrategyPass; +import vadl.iss.passes.extensions.InstrExecPlan; +import vadl.iss.passes.extensions.InstrExecPlan.StrategyEvaluation; +import vadl.iss.passes.extensions.InstrExecPlan.StrategyKind; +import vadl.iss.passes.extensions.InstrInfo; +import vadl.iss.passes.extensions.VectorTensorPlan; +import vadl.iss.passes.extensions.VectorTensorPlan.OperandKind; +import vadl.iss.passes.extensions.VectorTensorPlan.OverlapPolicy; +import vadl.iss.passes.extensions.VectorTensorPlan.VectorOp; +import vadl.pass.PassOrders; +import vadl.pass.exception.DuplicatedPassKeyException; +import vadl.viam.Instruction; +import vadl.viam.Specification; +import vadl.viam.graph.dependency.ExpressionNode; +import vadl.viam.graph.dependency.ParamNode; + +public class IssVectorTcgAnalysisPassTest extends AbstractTest { + + @Test + void recognizesRv64vVaddVv() + throws IOException, DuplicatedPassKeyException { + var viam = analyze("sys/risc-v/rv64v.vadl"); + var instr = findInstruction(viam, "RV64IMV::VADD_VV"); + var executionPlan = executionPlan(instr); + var evaluation = strategyEvaluation(executionPlan, StrategyKind.DIRECT_GVEC); + var plan = vectorPlan(evaluation); + + assertTrue(evaluation.isViable(), evaluation::toString); + assertEquals(StrategyKind.DIRECT_GVEC, executionPlan.selectedStrategy()); + assertEquals(VectorOp.ADD, plan.op()); + assertEquals(32, plan.elementBits()); + assertEquals(32, plan.laneCount()); + assertEquals(128, plan.opBytes()); + assertNotNull(plan.shape()); + assertEquals(128, plan.shape().maxszBytes()); + assertTrue(plan.shape().fullRange()); + assertTrue(plan.shape().contiguousLayout()); + assertTrue(plan.shape().paddingPreserved()); + assertEquals(OverlapPolicy.NO_PARTIAL_OVERLAP, plan.overlapPolicy()); + assertNotNull(plan.destination()); + assertEquals(List.of("vd"), bindingParamNames(plan.destination().accessorIndices())); + assertEquals(2, plan.operands().size()); + assertEquals(OperandKind.VECTOR_REGISTER, plan.operands().get(0).kind()); + assertNotNull(plan.operands().get(0).registerBinding()); + assertNotNull(plan.operands().get(1).registerBinding()); + assertEquals(List.of("vs2"), + bindingParamNames(plan.operands().get(0).registerBinding().accessorIndices())); + assertEquals(List.of("vs1"), + bindingParamNames(plan.operands().get(1).registerBinding().accessorIndices())); + assertEquals(InstrInfo.ExecStrategy.HELPER_CALL, instrInfo(instr).execStrategy()); + } + + @Test + void recognizesRv64vVsubVv() + throws IOException, DuplicatedPassKeyException { + var viam = analyze("sys/risc-v/rv64v.vadl"); + var executionPlan = executionPlan(findInstruction(viam, "RV64IMV::VSUB_VV")); + var evaluation = strategyEvaluation(executionPlan, StrategyKind.DIRECT_GVEC); + var plan = vectorPlan(evaluation); + + assertTrue(evaluation.isViable(), evaluation::toString); + assertEquals(VectorOp.SUB, plan.op()); + assertEquals(32, plan.elementBits()); + assertEquals(32, plan.laneCount()); + } + + @Test + void keepsVectorScalarInstructionOnFallbackPlan() + throws IOException, DuplicatedPassKeyException { + var viam = analyze("sys/risc-v/rv64v.vadl"); + var executionPlan = executionPlan(findInstruction(viam, "RV64IMV::VADD_VX")); + var evaluation = strategyEvaluation(executionPlan, StrategyKind.DIRECT_GVEC); + + assertEquals(StrategyKind.HELPER_CALL, executionPlan.selectedStrategy()); + assertTrue(!evaluation.isViable(), evaluation::toString); + assertTrue(evaluation.hasIssue("OPERAND_NOT_VECTOR_READ"), evaluation::toString); + } + + @Test + void keepsAliasVectorInstructionOnFallbackPlan() + throws IOException, DuplicatedPassKeyException { + var viam = analyze("sys/vectorbench/vectorbench64.vadl"); + var executionPlan = executionPlan(findInstruction(viam, "VectorBench64::VADD_DO_VV")); + var evaluation = strategyEvaluation(executionPlan, StrategyKind.DIRECT_GVEC); + + assertEquals(StrategyKind.HELPER_CALL, executionPlan.selectedStrategy()); + assertTrue(!evaluation.isViable(), evaluation::toString); + assertTrue(evaluation.hasIssue("READ_NOT_BASE_ELEMENT"), evaluation::toString); + } + + @Test + void keepsScalarInstructionOnTcgScalarStrategy() + throws IOException, DuplicatedPassKeyException { + var viam = analyze("sys/risc-v/rv64v.vadl"); + var instr = findInstruction(viam, "RV64IMV::VSETVLI"); + var executionPlan = executionPlan(instr); + var scalarEvaluation = strategyEvaluation(executionPlan, StrategyKind.TCG_SCALAR); + + assertEquals(StrategyKind.TCG_SCALAR, executionPlan.selectedStrategy()); + assertTrue(scalarEvaluation.isViable(), scalarEvaluation::toString); + assertEquals(InstrInfo.ExecStrategy.DIRECT_TCG, instrInfo(instr).execStrategy()); + } + + private Specification analyze(String specPath) throws IOException, DuplicatedPassKeyException { + var config = + new IssConfiguration(new GeneralConfiguration(Path.of("build/test-output"), DumpMode.NONE)); + return setupPassManagerAndRunSpec(specPath, + PassOrders.iss(config).untilFirst(IssExecStrategyPass.class) + ).specification(); + } + + private Instruction findInstruction(Specification viam, String name) { + return findDefinitionByNameIn(name, viam, Instruction.class); + } + + private InstrExecPlan executionPlan(Instruction instr) { + var plan = instrInfo(instr).executionPlan(); + return plan == null ? fail("Expected execution plan for " + instr.simpleName()) : plan; + } + + private StrategyEvaluation strategyEvaluation(InstrExecPlan executionPlan, + StrategyKind strategyKind) { + var evaluation = executionPlan.evaluation(strategyKind); + return evaluation == null ? fail("Expected evaluation for strategy " + strategyKind) : + evaluation; + } + + private VectorTensorPlan vectorPlan(StrategyEvaluation evaluation) { + var plan = evaluation.planAs(VectorTensorPlan.class); + return plan == null ? fail("Expected VectorTensorPlan payload") : plan; + } + + private List bindingParamNames(List indices) { + return indices.stream() + .map(index -> assertInstanceOf(ParamNode.class, index).definition().simpleName()) + .toList(); + } +}