diff --git a/docs/CoreDSLDialect.md b/docs/CoreDSLDialect.md index d181867..ff5b9a4 100644 --- a/docs/CoreDSLDialect.md +++ b/docs/CoreDSLDialect.md @@ -849,6 +849,72 @@ Traits: `HasParent`, `NoRegionArguments`, `SingleBlock`, `Termina +### `coredsl.switch` (coredsl::SwitchOp) + +_Switch-case operation on arbitrary integers_ + +Syntax: + +``` +operation ::= `coredsl.switch` $arg attr-dict `:` type($arg) (`->` type($results)^)? + custom($cases, $caseRegions) `\n` + `` `default` $defaultRegion +``` + +`coredsl.switch` is a modified version of `scf.index_switch` that branches +to one of the given regions based on the values of the argument and the +cases. As opposed to `scf.index_switch`, this operation accepts any integer +as argument. + +The operation always has a "default" region and any number of case regions +denoted by integer constants. Control-flow transfers to the case region +whose constant value equals the value of the argument. If the argument does +not equal any of the case values, control-flow transfer to the "default" +region. + +Example: + +```mlir +%0 = coredsl.switch %arg0 : ui32 -> ui32 +case 2 { + %1 = hwarith.constant 10 : ui32 + coredsl.yield %1 : ui32 +} +case 5 { + %2 = hwarith.constant 20 : ui32 + coredsl.yield %2 : ui32 +} +default { + %3 = hwarith.constant 30 : ui32 + coredsl.yield %3 : ui32 +} +``` + +Traits: `RecursiveMemoryEffects`, `SingleBlockImplicitTerminator`, `SingleBlock` + +Interfaces: `RegionBranchOpInterface` + +#### Attributes: + + + + +
AttributeMLIR TypeDescription
cases::mlir::ArrayAttrarray attribute
+ +#### Operands: + +| Operand | Description | +| :-----: | ----------- | +| `arg` | integer | + +#### Results: + +| Result | Description | +| :----: | ----------- | +| `results` | variadic of any type | + + + ### `coredsl.xor` (coredsl::XorOp) _Bitwise XOR operator._ @@ -890,6 +956,39 @@ Effects: `MemoryEffects::Effect{}` +### `coredsl.yield` (coredsl::YieldOp) + +_Loop yield and termination operation_ + +Syntax: + +``` +operation ::= `coredsl.yield` attr-dict ($results^ `:` type($results))? +``` + +The `coredsl.yield` operation is equivalent to the `scf.yield` operation, +but only works for `coredsl.switch` +If `coredsl.yield` has any operands, the operands must match the parent +operation's results. +If the parent operation defines no values, then the `coredsl.yield` may be +left out in the custom syntax and the builders will insert one implicitly. +Otherwise, it has to be present in the syntax to indicate which values are +yielded. + +Traits: `AlwaysSpeculatableImplTrait`, `HasParent`, `ReturnLike`, `Terminator` + +Interfaces: `ConditionallySpeculatable`, `NoMemoryEffect (MemoryEffectOpInterface)`, `RegionBranchTerminatorOpInterface` + +Effects: `MemoryEffects::Effect{}` + +#### Operands: + +| Operand | Description | +| :-----: | ----------- | +| `results` | variadic of any type | + + + ## Enums ### AddressSpaceAccessMode diff --git a/include/shortnail/Dialect/CoreDSL/CoreDSLOps.td b/include/shortnail/Dialect/CoreDSL/CoreDSLOps.td index 3eaeddc..52f57f3 100644 --- a/include/shortnail/Dialect/CoreDSL/CoreDSLOps.td +++ b/include/shortnail/Dialect/CoreDSL/CoreDSLOps.td @@ -685,4 +685,92 @@ def CastOp : CoreDSL_Op<"cast", [Pure]> { let hasVerifier = 1; } +//===----------------------------------------------------------------------===// +// YieldOp +//===----------------------------------------------------------------------===// + +def YieldOp : CoreDSL_Op<"yield", [Pure, ReturnLike, Terminator, + ParentOneOf<["SwitchOp"]>]> { + let summary = "loop yield and termination operation"; + let description = [{ + The `coredsl.yield` operation is equivalent to the `scf.yield` operation, + but only works for `coredsl.switch` + If `coredsl.yield` has any operands, the operands must match the parent + operation's results. + If the parent operation defines no values, then the `coredsl.yield` may be + left out in the custom syntax and the builders will insert one implicitly. + Otherwise, it has to be present in the syntax to indicate which values are + yielded. + }]; + + let arguments = (ins Variadic:$results); + let builders = [OpBuilder<(ins), [{ /* nothing to do */ }]>]; + + let assemblyFormat = + [{ attr-dict ($results^ `:` type($results))? }]; +} + +def SwitchOp : CoreDSL_Op<"switch", [RecursiveMemoryEffects, + SingleBlockImplicitTerminator<"coredsl::YieldOp">, + DeclareOpInterfaceMethods]> { + let summary = "switch-case operation on arbitrary integers"; + let description = [{ + `coredsl.switch` is a modified version of `scf.index_switch` that branches + to one of the given regions based on the values of the argument and the + cases. As opposed to `scf.index_switch`, this operation accepts any integer + as argument. + + The operation always has a "default" region and any number of case regions + denoted by integer constants. Control-flow transfers to the case region + whose constant value equals the value of the argument. If the argument does + not equal any of the case values, control-flow transfer to the "default" + region. + + Example: + + ```mlir + %0 = coredsl.switch %arg0 : ui32 -> ui32 + case 2 { + %1 = hwarith.constant 10 : ui32 + coredsl.yield %1 : ui32 + } + case 5 { + %2 = hwarith.constant 20 : ui32 + coredsl.yield %2 : ui32 + } + default { + %3 = hwarith.constant 30 : ui32 + coredsl.yield %3 : ui32 + } + ``` + }]; + + let arguments = (ins AnyInteger:$arg, ArrayAttr:$cases); + let results = (outs Variadic:$results); + let regions = (region SizedRegion<1>:$defaultRegion, + VariadicRegion>:$caseRegions); + + let assemblyFormat = [{ + $arg attr-dict `:` type($arg) (`->` type($results)^)? + custom($cases, $caseRegions) `\n` + `` `default` $defaultRegion + }]; + + let extraClassDeclaration = [{ + /// Get the number of cases. + unsigned getNumCases(); + + /// Get the default region body. + Block &getDefaultBlock(); + + /// Get the body of a case region. + Block &getCaseBlock(unsigned idx); + }]; + + let hasCanonicalizer = 1; + let hasVerifier = 1; +} + #endif // SHORTNAIL_DIALECT_COREDSL_COREDSLOPS_TD diff --git a/lib/Conversion/CoreDSLToPy/CoreDSLToPy.cpp b/lib/Conversion/CoreDSLToPy/CoreDSLToPy.cpp index 20095b9..61865f0 100644 --- a/lib/Conversion/CoreDSLToPy/CoreDSLToPy.cpp +++ b/lib/Conversion/CoreDSLToPy/CoreDSLToPy.cpp @@ -153,8 +153,26 @@ handleWordWiseAccess(Op op, } } +// Handles both scf::YieldOp and coredsl::YieldOp, as their handling is +// identical +template +static WalkResult emitYieldOp(mlir::raw_indented_ostream &os, YieldOp yieldOp) { + os << "return "; + bool first = true; + for (auto res : yieldOp.getResults()) { + if (!first) + os << ", "; + + valToPy(os, res); + first = false; + } + os << "\n"; + return WalkResult::advance(); +} + static WalkResult emitCoreDSLOp(mlir::raw_indented_ostream &os, Operation *op) { assert(isa(op->getDialect())); + static unsigned uniqueSwitchNumber = 0; return TypeSwitch(op) .Case([&](auto setOp) { @@ -468,6 +486,86 @@ static WalkResult emitCoreDSLOp(mlir::raw_indented_ostream &os, Operation *op) { << (targetType.isSigned() ? "True" : "False") << ")\n"; return WalkResult::advance(); }) + .Case([&](coredsl::SwitchOp switchOp) { + const unsigned switchId = uniqueSwitchNumber++; + for (unsigned i = 0, end = switchOp.getNumCases(); i < end; ++i) { + Block &caseBlock = switchOp.getCaseBlock(i); + // Emit each case as a helper function. This way each + // switch has the form + // 'ret1, ret2, ..., retn = helper_function', thereby + // making handling of the results easier and allowing + // us to just emit a return for every yield op + os << "def helper_function_coredsl_switch_" << switchId << "_case_" + << i << "():\n"; + os.indent(); + auto res = caseBlock.template walk( + [&os](Operation *op) { return emitOp(os, op); }); + if (res.wasInterrupted()) + return res; + os.unindent(); + } + os << "def helper_function_coresl_switch_" << switchId + << "_default_case():\n"; + os.indent(); + auto res = switchOp.getDefaultBlock().walk( + [&os](Operation *op) { return emitOp(os, op); }); + if (res.wasInterrupted()) + return res; + os.unindent(); + bool first = true; + for (const auto &[idx, attr] : llvm::enumerate(switchOp.getCases())) { + const IntegerAttr &intAttr = cast(attr); + const APInt &caseValAPInt = intAttr.getValue(); + const unsigned bitWidth = switchOp.getArg().getType().getWidth(); + const auto signedness = switchOp.getArg().getType().getSignedness(); + assert(signedness != IntegerType::Signless); + const APSInt caseVal = APSInt(caseValAPInt.sextOrTrunc(bitWidth), + signedness == IntegerType::Signed); + if (first) + os << "if "; + else + os << "elif "; + first = false; + valToPy(os, switchOp.getArg()); + os << "." << hwarith::ICmpPredicate::eq << "("; + os << intToPy(caseVal) << "):\n"; + os.indent(); + if (switchOp.getNumResults() > 0) { + bool first = true; + for (auto res : switchOp.getResults()) { + if (!first) { + os << ", "; + } + first = false; + valToPy(os, res); + } + os << " = "; + } + os << "helper_function_coredsl_switch_" << switchId << "_case_" << idx + << "()\n"; + os.unindent(); + } + os << "else:\n"; + os.indent(); + if (switchOp.getNumResults() > 0) { + bool first = true; + for (auto res : switchOp.getResults()) { + if (!first) { + os << ", "; + } + first = false; + valToPy(os, res); + } + os << " = "; + } + os << "helper_function_coredsl_switch_" << switchId + << "_default_case()\n"; + os.unindent(); + // Skip the child scopes, as we already visited them + return WalkResult::skip(); + }) + .Case( + [&](coredsl::YieldOp yieldOp) { return emitYieldOp(os, yieldOp); }) .Default([&](auto _) { op->emitError() << "CoreDSLToPy::emitCoreDSLOp lacks emission code for " "this operation!"; @@ -646,19 +744,8 @@ static WalkResult emitSCFOp(mlir::raw_indented_ostream &os, Operation *op) { return WalkResult::skip(); }) - .Case([&](auto yieldOp) { - os << "return "; - bool first = true; - for (auto res : yieldOp.getResults()) { - if (!first) - os << ", "; - - valToPy(os, res); - first = false; - } - os << "\n"; - return WalkResult::advance(); - }) + .Case( + [&](auto yieldOp) { return emitYieldOp(os, yieldOp); }) // .Case([&](auto whileOp) { // // TODO PITA // return WalkResult::interrupt(); diff --git a/lib/Dialect/CoreDSL/CoreDSLOps.cpp b/lib/Dialect/CoreDSL/CoreDSLOps.cpp index c862ea0..9c16e41 100644 --- a/lib/Dialect/CoreDSL/CoreDSLOps.cpp +++ b/lib/Dialect/CoreDSL/CoreDSLOps.cpp @@ -13,10 +13,14 @@ #include "circt/Dialect/HWArith/HWArithOps.h" #include "circt/Dialect/HWArith/HWArithTypes.h" +#include "mlir/Dialect/Utils/StaticValueUtils.h" #include "mlir/IR/Builders.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/IR/OpImplementation.h" +#include "llvm/ADT/APSInt.h" +#include "llvm/Support/Debug.h" + using namespace circt::hwarith; namespace mlir { @@ -1036,6 +1040,228 @@ LogicalResult CastOp::verify() { return success(); } +//===----------------------------------------------------------------------===// +// SwitchOp +//===----------------------------------------------------------------------===// + +/// Parse the case regions and values. +static ParseResult +parseSwitchCases(OpAsmParser &p, ArrayAttr &cases, + SmallVectorImpl> &caseRegions) { + SmallVector caseValues; + while (succeeded(p.parseOptionalKeyword("case"))) { + APInt signlessValue; + Region ®ion = *caseRegions.emplace_back(std::make_unique()); + if (p.parseInteger(signlessValue) || + p.parseRegion(region, /*arguments=*/{})) + return failure(); + // We can use APInt::isNegative here, because parseInteger() only sets the + // MSB if the literal is negative + APSInt signedValue{signlessValue, !signlessValue.isNegative()}; + IntegerAttr attr = IntegerAttr::get(p.getContext(), signedValue); + caseValues.push_back(attr); + } + cases = ArrayAttr::get(p.getContext(), caseValues); + return success(); +} + +/// Print the case regions and values. +static void printSwitchCases(OpAsmPrinter &p, Operation *op, + const ArrayAttr &cases, RegionRange caseRegions) { + for (auto [value, region] : llvm::zip(cases, caseRegions)) { + p.printNewline(); + const IntegerAttr &intAttr = cast(value); + p << "case " << intAttr.getAPSInt() << ' '; + p.printRegion(*region, /*printEntryBlockArgs=*/false); + } +} + +static SmallString<64> getAPSIntStr(const APSInt &value) { + SmallString<64> str; + value.toString(str); + return str; +} + +LogicalResult SwitchOp::verify() { + if (getCases().size() != getCaseRegions().size()) { + return emitOpError("has ") + << getCaseRegions().size() << " case regions but " + << getCases().size() << " case values"; + } + + Value operand = OneOperand::getOperand(); + const Type opType = operand.getType(); + if (!opType.isInteger()) { + return emitOpError("expected an integer type"); + } + const bool condIsSigned = opType.isSignedInteger(); + const unsigned condBitWidth = opType.getIntOrFloatBitWidth(); + DenseSet valueSet; + for (const Attribute &attr : getCases()) { + const IntegerAttr intAttr = cast(attr); + const APSInt value = intAttr.getAPSInt(); + const bool signedValueForUnsignedCond = value.isSigned() && !condIsSigned; + const bool isRepresentable = condIsSigned ? value.isSignedIntN(condBitWidth) + : value.isIntN(condBitWidth); + if (signedValueForUnsignedCond || !isRepresentable) { + return emitOpError("expects case value to be representable by ") + << (condIsSigned ? "si" : "ui") << condBitWidth << " but got " + << getAPSIntStr(value); + } + if (!valueSet.insert(value).second) { + return emitOpError("has duplicate case value: ") << getAPSIntStr(value); + } + } + auto verifyRegion = [&](Region ®ion, const Twine &name) -> LogicalResult { + auto yield = dyn_cast(region.front().back()); + if (!yield) + return emitOpError("expected region to end with scf.yield, but got ") + << region.front().back().getName(); + + if (yield.getNumOperands() != getNumResults()) { + return (emitOpError("expected each region to return ") + << getNumResults() << " values, but " << name << " returns " + << yield.getNumOperands()) + .attachNote(yield.getLoc()) + << "see yield operation here"; + } + for (auto [idx, result, operand] : + llvm::enumerate(getResultTypes(), yield.getOperands())) { + if (!operand) + return yield.emitOpError() << "operand " << idx << " is null\n"; + if (result == operand.getType()) + continue; + return (emitOpError("expected result #") + << idx << " of each region to be " << result) + .attachNote(yield.getLoc()) + << name << " returns " << operand.getType() << " here"; + } + return success(); + }; + + if (failed(verifyRegion(getDefaultRegion(), "default region"))) + return failure(); + for (auto [idx, caseRegion] : llvm::enumerate(getCaseRegions())) + if (failed(verifyRegion(caseRegion, "case region #" + Twine(idx)))) + return failure(); + + return success(); +} + +unsigned SwitchOp::getNumCases() { return getCases().size(); } + +Block &SwitchOp::getDefaultBlock() { return getDefaultRegion().front(); } + +Block &SwitchOp::getCaseBlock(unsigned idx) { + assert(idx < getNumCases() && "case index out-of-bounds"); + return getCaseRegions()[idx].front(); +} + +void SwitchOp::getSuccessorRegions( + RegionBranchPoint point, SmallVectorImpl &successors) { + // All regions branch back to the parent op. + if (!point.isParent()) { + successors.emplace_back(getOperation(), getResults()); + return; + } + + llvm::append_range(successors, getRegions()); +} + +void SwitchOp::getEntrySuccessorRegions( + ArrayRef operands, + SmallVectorImpl &successors) { + FoldAdaptor adaptor(operands, *this); + + // If a constant was not provided, all regions are possible successors. + auto arg = dyn_cast_or_null(adaptor.getArg()); + if (!arg) { + llvm::append_range(successors, getRegions()); + return; + } + + // Otherwise, try to find a case with a matching value. If not, the + // default region is the only successor. + for (auto [caseAttr, caseRegion] : llvm::zip(getCases(), getCaseRegions())) { + const IntegerAttr intAttr = cast(caseAttr); + const APInt caseValue = intAttr.getValue(); + if (caseValue == arg.getValue()) { + successors.emplace_back(&caseRegion); + return; + } + } + successors.emplace_back(&getDefaultRegion()); +} + +void SwitchOp::getRegionInvocationBounds( + ArrayRef operands, SmallVectorImpl &bounds) { + auto operandValue = llvm::dyn_cast_or_null(operands.front()); + if (!operandValue) { + // All regions are invoked at most once. + bounds.append(getNumRegions(), InvocationBounds(/*lb=*/0, /*ub=*/1)); + return; + } + + unsigned liveIndex = getNumRegions() - 1; + const auto it = llvm::find_if(getCases(), [&operandValue](Attribute attr) { + const IntegerAttr intAttr = cast(attr); + return intAttr.getValue() == operandValue.getValue(); + }); + if (it != getCases().end()) + liveIndex = std::distance(getCases().begin(), it); + for (unsigned i = 0, e = getNumRegions(); i < e; ++i) + bounds.emplace_back(/*lb=*/0, /*ub=*/i == liveIndex); +} + +struct FoldConstantCase : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SwitchOp op, + PatternRewriter &rewriter) const override { + // If `op.getArg()` is a constant, select the region that matches with + // the constant value. Use the default region if no matche is found. + std::optional> maybeCst = + getConstantAPIntValue(op.getArg()); + if (!maybeCst.has_value()) + return failure(); + // index type not supported + if (maybeCst->second) { + return failure(); + } + const APInt cst = maybeCst->first; + int64_t caseIdx, e = op.getNumCases(); + // Original code: + for (caseIdx = 0; caseIdx < e; ++caseIdx) { + const Attribute &attr = op.getCases()[caseIdx]; + const IntegerAttr &intAttr = cast(attr); + // TODO: need to check at runtime if this runs before validate + assert(intAttr); + const APSInt attrVal = intAttr.getAPSInt().extOrTrunc(cst.getBitWidth()); + if (cst == attrVal) + break; + } + + Region &r = (caseIdx < op.getNumCases()) ? op.getCaseRegions()[caseIdx] + : op.getDefaultRegion(); + Block &source = r.front(); + Operation *terminator = source.getTerminator(); + SmallVector results = terminator->getOperands(); + + rewriter.inlineBlockBefore(&source, op); + rewriter.eraseOp(terminator); + // Replace the operation with a potentially empty list of results. + // Fold mechanism doesn't support the case where the result list is empty. + rewriter.replaceOp(op, results); + + return success(); + } +}; + +void SwitchOp::getCanonicalizationPatterns(RewritePatternSet &results, + MLIRContext *context) { + results.add(context); +} + } // namespace coredsl } // namespace mlir diff --git a/test/CoreDSL/switch.mlir b/test/CoreDSL/switch.mlir new file mode 100644 index 0000000..7dea66c --- /dev/null +++ b/test/CoreDSL/switch.mlir @@ -0,0 +1,134 @@ +// RUN: shortnail-opt %s -canonicalize -split-input-file -verify-diagnostics | shortnail-opt | FileCheck %s + +coredsl.isax "SWITCH_TEST" { +// CHECK: coredsl.register core_x @X[32] : ui32 +// CHECK: coredsl.register local @ACC : ui64 + coredsl.register core_x @X[32] : ui32 + coredsl.register local @ACC : ui64 + + // CHECK: coredsl.instruction @SWITCH_TEST( + coredsl.instruction @SWITCH_TEST("000000", %isSigned : ui1, %rs2 : ui5, %rs1 : ui5, + "000", %rd : ui5, "0101011") { + %0 = coredsl.get @X[%rs1 : ui5] : ui32 + %1 = coredsl.get @X[%rs2 : ui5] : ui32 + // CHECK: %[[VAL_1:.*]] = hwarith.constant 54 : ui32 + // CHECK: %[[VAL_0:.*]] = hwarith.constant 2 : ui32 + // CHECK: %[[VAL_2:.*]] = hwarith.constant 3 : ui32 + // CHECK: %[[LOADED_0:.*]] = coredsl.get @X[%rs1 : ui5] : ui32 + // CHECK: %[[LOADED_1:.*]] = coredsl.get @X[%rs2 : ui5] : ui32 + // CHECK: %[[RES:.*]] = coredsl.switch %[[COND:.*]] : ui32 -> ui32 + // CHECK: case 0 { + // CHECK: coredsl.yield %[[VAL_0]] : ui32 + // CHECK: } + // CHECK: case 2 { + // CHECK: coredsl.yield %[[VAL_1]] : ui32 + // CHECK: } + // CHECK: default { + // CHECK: coredsl.yield %[[VAL_2]] : ui32 + // CHECK: } + // CHECK: coredsl.set @X[%rs1 : ui5] = %[[RES]] : ui32 + %2 = coredsl.switch %1 : ui32 -> ui32 + case 0 { + %2 = hwarith.constant 2 : ui32 + coredsl.yield %2 : ui32 + } + case 2 { + %2 = hwarith.constant 54 : ui32 + coredsl.yield %2 : ui32 + } + default { + %2 = hwarith.constant 3 : ui32 + coredsl.yield %2 : ui32 + } + coredsl.set @X[%rs1 : ui5] = %2 : ui32 + coredsl.end + } + + coredsl.instruction @NO_ERROR_SIGNED_INT("000000", %isSigned : ui1, %rs2 : ui5, %rs1 : ui5, + "000", %rd : ui5, "0101011") { + // CHECK: %[[RES_0:.*]] = hwarith.constant 54 : ui32 + // CHECK: %[[RES_1:.*]] = hwarith.constant 2 : ui32 + // CHECK: %[[RES_2:.*]] = hwarith.constant 3 : ui32 + // CHECK: %[[READ_VAL:.*]] = coredsl.get @X[%rs1 : ui5] : ui32 + // CHECK: %[[COND_UI32:.*]] = coredsl.get @X[%rs2 : ui5] : ui32 + // CHECK: %[[COND:.*]] = coredsl.cast %[[COND_UI32]] : ui32 to si8 + // CHECK: %[[COND_RES:.*]] = coredsl.switch %[[COND]] : si8 -> ui32 + // CHECK: case 0 { + // CHECK: coredsl.yield %[[RES_1]] : ui32 + // CHECK: } + // CHECK: case -1 { + // CHECK: coredsl.yield %[[RES_0]] : ui32 + // CHECK: } + // CHECK: default { + // CHECK: coredsl.yield %[[RES_2]] : ui32 + // CHECK: } + // CHECK: coredsl.set @X[%rs1 : ui5] = %[[COND_RES]] : ui32 + + %0 = coredsl.get @X[%rs1 : ui5] : ui32 + %1 = coredsl.get @X[%rs2 : ui5] : ui32 + %2 = coredsl.cast %1 : ui32 to si8 + %3 = coredsl.switch %2 : si8 -> ui32 + case 0 { + %3 = hwarith.constant 2 : ui32 + coredsl.yield %3 : ui32 + } + case -1 { + %3 = hwarith.constant 54 : ui32 + coredsl.yield %3 : ui32 + } + default { + %3 = hwarith.constant 3 : ui32 + coredsl.yield %3 : ui32 + } + coredsl.set @X[%rs1 : ui5] = %3 : ui32 + coredsl.end + } + + coredsl.instruction @CONST_CASE_FOLD("000000", %isSigned : ui1, %rs2 : ui5, %rs1 : ui5, + "000", %rd : ui5, "0101011") { + // CHECK: %[[RES:.*]] = hwarith.constant 54 : ui32 + // CHECK: %[[LOADED_0:.*]] = coredsl.get @X[%rs1 : ui5] : ui32 + // CHECK: %[[LOADED_1:.*]] = coredsl.get @X[%rs2 : ui5] : ui32 + // CHECK: coredsl.set @X[%rs1 : ui5] = %[[RES]] : ui32 + %0 = coredsl.get @X[%rs1 : ui5] : ui32 + %1 = coredsl.get @X[%rs2 : ui5] : ui32 + %2 = hwarith.constant -1 : si8 + %3 = coredsl.switch %2 : si8 -> ui32 + case 0 { + %3 = hwarith.constant 2 : ui32 + coredsl.yield %3 : ui32 + } + case -1 { + %3 = hwarith.constant 54 : ui32 + coredsl.yield %3 : ui32 + } + default { + %3 = hwarith.constant 3 : ui32 + coredsl.yield %3 : ui32 + } + coredsl.set @X[%rs1 : ui5] = %3 : ui32 + coredsl.end + } + + coredsl.instruction @CONST_CASE_FOLD_LARGER_THAN_64BIT("000000", %isSigned : ui1, %rs2 : ui5, %rs1 : ui5, + "000", %rd : ui5, "0101011") { + // CHECK: %[[RES:.*]] = hwarith.constant 54 : ui32 + // CHECK: coredsl.set @X[%rs1 : ui5] = %[[RES]] : ui32 + %2 = hwarith.constant 36893488147419103232 : ui128 + %3 = coredsl.switch %2 : ui128 -> ui32 + case 0 { + %3 = hwarith.constant 2 : ui32 + coredsl.yield %3 : ui32 + } + case 36893488147419103232 { + %3 = hwarith.constant 54 : ui32 + coredsl.yield %3 : ui32 + } + default { + %3 = hwarith.constant 3 : ui32 + coredsl.yield %3 : ui32 + } + coredsl.set @X[%rs1 : ui5] = %3 : ui32 + coredsl.end + } +} diff --git a/test/CoreDSL/switch_errors.mlir b/test/CoreDSL/switch_errors.mlir new file mode 100644 index 0000000..34d6640 --- /dev/null +++ b/test/CoreDSL/switch_errors.mlir @@ -0,0 +1,118 @@ +// RUN: shortnail-opt %s -split-input-file -verify-diagnostics + +coredsl.isax "SWITCH_ERRORS" { + coredsl.register core_x @X[32] : ui32 + coredsl.register local @ACC : ui64 + + coredsl.instruction @ERROR_VALUE_TOO_LARGE("000000", %isSigned : ui1, %rs2 : ui5, %rs1 : ui5, + "000", %rd : ui5, "0101011") { + %0 = coredsl.get @X[%rs1 : ui5] : ui32 + %1 = coredsl.get @X[%rs2 : ui5] : ui32 + %2 = coredsl.cast %1 : ui32 to ui8 + // expected-error @+1 {{'coredsl.switch' op expects case value to be representable by ui8 but got 50000}} + %3 = coredsl.switch %2 : ui8 -> ui32 + case 0 { + %3 = hwarith.constant 2 : ui32 + coredsl.yield %3 : ui32 + } + case 50000 { + %3 = hwarith.constant 54 : ui32 + coredsl.yield %3 : ui32 + } + default { + %3 = hwarith.constant 3 : ui32 + coredsl.yield %3 : ui32 + } + coredsl.end + } + + coredsl.instruction @ERROR_NEGATIVE_FOR_UNSIGNED("000000", %isSigned : ui1, %rs2 : ui5, %rs1 : ui5, + "000", %rd : ui5, "0101011") { + %0 = coredsl.get @X[%rs1 : ui5] : ui32 + %1 = coredsl.get @X[%rs2 : ui5] : ui32 + %2 = coredsl.cast %1 : ui32 to ui8 + // expected-error @+1 {{'coredsl.switch' op expects case value to be representable by ui8 but got -1}} + %3 = coredsl.switch %2 : ui8 -> ui32 + case 0 { + %3 = hwarith.constant 2 : ui32 + coredsl.yield %3 : ui32 + } + case -1 { + %3 = hwarith.constant 54 : ui32 + coredsl.yield %3 : ui32 + } + default { + %3 = hwarith.constant 3 : ui32 + coredsl.yield %3 : ui32 + } + coredsl.end + } + + coredsl.instruction @ERROR_SIGNED_INT_TOO_LARGE("000000", %isSigned : ui1, %rs2 : ui5, %rs1 : ui5, + "000", %rd : ui5, "0101011") { + %0 = coredsl.get @X[%rs1 : ui5] : ui32 + %1 = coredsl.get @X[%rs2 : ui5] : ui32 + %2 = coredsl.cast %1 : ui32 to si8 + // expected-error @+1 {{'coredsl.switch' op expects case value to be representable by si8 but got 150}} + %3 = coredsl.switch %2 : si8 -> ui32 + case 0 { + %3 = hwarith.constant 2 : ui32 + coredsl.yield %3 : ui32 + } + // should error + case 150 { + %3 = hwarith.constant 54 : ui32 + coredsl.yield %3 : ui32 + } + default { + %3 = hwarith.constant 3 : ui32 + coredsl.yield %3 : ui32 + } + coredsl.end + } + + coredsl.instruction @ERROR_SIGNED_INT_NEGATIVE_LARGE("000000", %isSigned : ui1, %rs2 : ui5, %rs1 : ui5, + "000", %rd : ui5, "0101011") { + %0 = coredsl.get @X[%rs1 : ui5] : ui32 + %1 = coredsl.get @X[%rs2 : ui5] : ui32 + %2 = coredsl.cast %1 : ui32 to ui128 + // expected-error @+1 {{'coredsl.switch' op expects case value to be representable by ui128 but got -18446744073709551616}} + %3 = coredsl.switch %2 : ui128 -> ui32 + case 0 { + %3 = hwarith.constant 2 : ui32 + coredsl.yield %3 : ui32 + } + // Should be a 65 bit signed integer + case -18446744073709551616 { + %3 = hwarith.constant 54 : ui32 + coredsl.yield %3 : ui32 + } + default { + %3 = hwarith.constant 3 : ui32 + coredsl.yield %3 : ui32 + } + coredsl.end + } + + coredsl.instruction @ERROR_DUPLICATE_CASE("000000", %isSigned : ui1, %rs2 : ui5, %rs1 : ui5, + "000", %rd : ui5, "0101011") { + %0 = coredsl.get @X[%rs1 : ui5] : ui32 + %1 = coredsl.get @X[%rs2 : ui5] : ui32 + %2 = coredsl.cast %1 : ui32 to ui8 + // expected-error @+1 {{'coredsl.switch' op has duplicate case value: 0}} + %3 = coredsl.switch %2 : ui8 -> ui32 + case 0 { + %3 = hwarith.constant 2 : ui32 + coredsl.yield %3 : ui32 + } + case 0 { + %3 = hwarith.constant 54 : ui32 + coredsl.yield %3 : ui32 + } + default { + %3 = hwarith.constant 3 : ui32 + coredsl.yield %3 : ui32 + } + coredsl.end + } +}