diff --git a/include/circt/Dialect/Arc/ArcPasses.td b/include/circt/Dialect/Arc/ArcPasses.td index d097a017ba60..73ae9cc257f2 100644 --- a/include/circt/Dialect/Arc/ArcPasses.td +++ b/include/circt/Dialect/Arc/ArcPasses.td @@ -371,6 +371,15 @@ def StripSV : Pass<"arc-strip-sv", "mlir::ModuleOp"> { ]; } +def RemoveI0Types : Pass<"arc-remove-i0-types", "mlir::ModuleOp"> { + let summary = "Remove i0 types completely"; + let description = [{ + i0 types are tautologous and usually exist due to array<1xT> types. i0 is + not valid in LLVM-IR, so we transform array<1xT> -> T and ban i0 types. + }]; + let dependentDialects = ["arc::ArcDialect", "hw::HWDialect"]; +} + def ResolveXMRRef : Pass<"arc-resolve-xmr", "mlir::ModuleOp"> { let summary = "Resolve sv.xmr.ref into direct signal references"; let description = [{ diff --git a/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp b/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp index a5d71ac4a6e1..475d3e4ebcab 100644 --- a/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp +++ b/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp @@ -1769,29 +1769,6 @@ struct LowerArcToLLVMPass } // namespace void LowerArcToLLVMPass::runOnOperation() { - // Replace any `i0` values with an `hw.constant 0 : i0` to avoid later issues - // in LLVM conversion. - { - DenseMap zeros; - getOperation().walk([&](Operation *op) { - if (op->hasTrait()) - return; - for (auto result : op->getResults()) { - auto type = dyn_cast(result.getType()); - if (!type || type.getWidth() != 0) - continue; - auto *region = op->getParentRegion(); - auto &zero = zeros[region]; - if (!zero) { - auto builder = OpBuilder::atBlockBegin(®ion->front()); - zero = hw::ConstantOp::create(builder, result.getLoc(), - APInt::getZero(0)); - } - result.replaceAllUsesWith(zero); - } - }); - } - // Add `dereferenceable()` attributes to all function arguments that take // ArrayRefTypes. for (func::FuncOp func : getOperation().getOps()) { diff --git a/lib/Dialect/Arc/Transforms/CMakeLists.txt b/lib/Dialect/Arc/Transforms/CMakeLists.txt index b22d71e9f8aa..44b4400a3f98 100644 --- a/lib/Dialect/Arc/Transforms/CMakeLists.txt +++ b/lib/Dialect/Arc/Transforms/CMakeLists.txt @@ -22,6 +22,7 @@ add_circt_dialect_library(CIRCTArcTransforms MergeTaps.cpp MuxToControlFlow.cpp PrintCostModel.cpp + RemoveI0Types.cpp ResolveXMRRef.cpp SimplifyVariadicOps.cpp SplitFuncs.cpp diff --git a/lib/Dialect/Arc/Transforms/RemoveI0Types.cpp b/lib/Dialect/Arc/Transforms/RemoveI0Types.cpp new file mode 100644 index 000000000000..9f6942aec69e --- /dev/null +++ b/lib/Dialect/Arc/Transforms/RemoveI0Types.cpp @@ -0,0 +1,323 @@ +//===- RemoveI0Types.cpp --------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/Arc/ArcOps.h" +#include "circt/Dialect/Arc/ArcPasses.h" +#include "circt/Dialect/HW/HWTypes.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/Location.h" +#include "mlir/IR/Value.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Support/LogicalResult.h" +#include "mlir/Transforms/DialectConversion.h" +#include "mlir/Transforms/GreedyPatternRewriteDriver.h" + +namespace circt { +namespace arc { +#define GEN_PASS_DEF_REMOVEI0TYPES +#include "circt/Dialect/Arc/ArcPasses.h.inc" +} // namespace arc +} // namespace circt + +using namespace mlir; +using namespace circt; +using namespace arc; + +namespace { +struct RemoveI0TypesPass + : public arc::impl::RemoveI0TypesBase { + using RemoveI0TypesBase::RemoveI0TypesBase; + void runOnOperation() override; +}; + +bool isI0(Type type) { + auto intType = dyn_cast(type); + return intType && intType.getWidth() == 0; +} + +bool isPoison(Type type) { return isa(type); } + +SmallVector filterOutPoison(TypeRange range) { + return llvm::filter_to_vector(range, [](Type t) { return !isPoison(t); }); +} + +SmallVector filterOutPoison(ValueRange range) { + return llvm::filter_to_vector(range, + [](Value v) { return !isPoison(v.getType()); }); +} + +struct ConvertFunc : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(func::FuncOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + const TypeConverter &converter = *getTypeConverter(); + FunctionType fty = op.getFunctionType(); + + rewriter.setInsertionPointToStart(&op.getBody().front()); + TypeConverter::SignatureConversion sig(op.getNumArguments()); + for (auto [origIdx, type] : enumerate(fty.getInputs())) { + Type newType = converter.convertType(type); + if (!newType) + return failure(); + if (isPoison(newType)) { + Value cast = UnrealizedConversionCastOp::create(rewriter, op.getLoc(), + newType, ValueRange{}) + .getResult(0); + sig.remapInput(origIdx, cast); + continue; + } + sig.addInputs(origIdx, newType); + } + + if (failed(rewriter.convertRegionTypes(&op.getBody(), converter, &sig))) { + return failure(); + } + + SmallVector filteredResultTypes; + if (failed(converter.convertTypes(fty.getResults(), filteredResultTypes))) + return failure(); + filteredResultTypes = filterOutPoison(filteredResultTypes); + + rewriter.modifyOpInPlace(op, [&]() { + op.setFunctionType(FunctionType::get( + getContext(), sig.getConvertedTypes(), filteredResultTypes)); + }); + + return success(); + } +}; + +struct ConvertReturn : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(func::ReturnOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + SmallVector filteredOperands = + filterOutPoison(adaptor.getOperands()); + rewriter.modifyOpInPlace(op, [&]() { op->setOperands(filteredOperands); }); + + return success(); + } +}; + +struct ConvertCall : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(func::CallOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + const TypeConverter &converter = *getTypeConverter(); + SmallVector filteredOperands = + filterOutPoison(adaptor.getOperands()); + SmallVector resultTypes; + if (failed(converter.convertTypes(op.getResultTypes(), resultTypes))) + return failure(); + SmallVector filteredResultTypes = filterOutPoison(resultTypes); + + auto newOp = + func::CallOp::create(rewriter, op.getLoc(), filteredResultTypes, + filteredOperands, op->getAttrs()); + + auto newOpResultIt = newOp.result_begin(); + SmallVector results; + for (auto type : resultTypes) { + if (isPoison(type)) { + results.push_back(UnrealizedConversionCastOp::create( + rewriter, op.getLoc(), type, ValueRange{}) + .getResult(0)); + } else { + results.push_back(*newOpResultIt++); + } + } + rewriter.replaceOp(op, results); + return success(); + } +}; + +struct ConvertArrayGet : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(hw::ArrayGetOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + rewriter.replaceOp(op, adaptor.getInput()); + return success(); + } +}; + +struct ConvertArrayCreate : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(hw::ArrayCreateOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + rewriter.replaceOp(op, adaptor.getInputs().front()); + return success(); + } +}; + +struct ConvertArrayInject : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(hw::ArrayInjectOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + rewriter.replaceOp(op, adaptor.getElement()); + return success(); + } +}; + +struct HandleGenericOp : public ConversionPattern { + HandleGenericOp(TypeConverter &converter, MLIRContext *context) + : ConversionPattern(converter, MatchAnyOpTypeTag{}, + /*benefit=*/0, context) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const override { + // Any op returning i0 must be dead. + if (llvm::any_of(op->getResults(), + [](Value v) { return isI0(v.getType()); })) { + rewriter.eraseOp(op); + return success(); + } + + // Otherwise just perform type replacement. + auto result = + convertOpResultTypes(op, operands, *getTypeConverter(), rewriter); + if (failed(result)) + return failure(); + + rewriter.replaceOp(op, *result); + return success(); + } +}; + +struct ConvertAggregateConstant + : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(hw::AggregateConstantOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + Type resultType = getTypeConverter()->convertType(op.getResult().getType()); + + // Recursively rewrite the attribute. + Attribute newFields = + rewriteArrayAttr(op.getFields(), op.getResult().getType()); + + if (!isa(newFields)) { + // Scalar result -> becomes hw.constant. + IntegerAttr attr = cast(newFields); + auto result = + hw::ConstantOp::create(rewriter, op.getLoc(), resultType, attr); + rewriter.replaceOp(op, result); + return success(); + } + + // Composite result -> new aggregate_constant op. + auto newOp = hw::AggregateConstantOp::create( + rewriter, op.getLoc(), resultType, cast(newFields)); + rewriter.replaceOp(op, newOp); + return success(); + } + + Attribute rewriteArrayAttr(ArrayAttr array, Type type) const { + if (getTypeConverter()->convertType(type) == type) + return array; + if (auto arrayType = dyn_cast(type); + arrayType && arrayType.getNumElements() == 1) { + return *array.begin(); + } + + // Collect the immediate subtypes. FieldIDTypeInterface is supported by + // ArrayType, UnpackedArrayType, StructType, UnionType. + auto fieldIdInterface = cast(type); + SmallVector attrs; + for (auto [index, attr] : llvm::enumerate(array)) { + uint64_t fieldId = fieldIdInterface.getFieldID(index); + Type subType = fieldIdInterface.getSubTypeByFieldID(fieldId).first; + if (auto subArrayAttr = dyn_cast(attr)) { + attrs.push_back(rewriteArrayAttr(subArrayAttr, subType)); + } else { + attrs.push_back(attr); + } + } + return ArrayAttr::get(array.getContext(), attrs); + } +}; +} // namespace + +void RemoveI0TypesPass::runOnOperation() { + TypeConverter converter; + ConversionTarget target(getContext()); + RewritePatternSet patterns(&getContext()); + + converter.addConversion([](Type type) -> Type { + if (isI0(type)) + return OpaqueType::get(StringAttr::get(type.getContext(), "arc"), + StringAttr::get(type.getContext(), "poison")); + return type; + }); + converter.addConversion([&converter](hw::ArrayType type) -> Type { + if (type.getNumElements() == 1) + return converter.convertType(type.getElementType()); + // Recursively apply type conversion to inner types. + return hw::ArrayType::get(converter.convertType(type.getElementType()), + type.getNumElements()); + }); + + // Composite types - recursively apply type conversion to inner types. + converter.addConversion([&converter](hw::StructType type) { + auto newMembers = + map_to_vector(type.getElements(), [&](hw::StructType::FieldInfo field) { + field.type = converter.convertType(field.type); + return field; + }); + return hw::StructType::get(type.getContext(), newMembers); + }); + converter.addConversion([&converter](hw::UnionType type) { + auto newMembers = + map_to_vector(type.getElements(), [&](hw::UnionType::FieldInfo field) { + field.type = converter.convertType(field.type); + return field; + }); + return hw::UnionType::get(type.getContext(), newMembers); + }); + converter.addConversion([&converter](hw::TypeAliasType type) { + return converter.convertType(type.getCanonicalType()); + }); + converter.addConversion([&converter](arc::StateType type) { + return arc::StateType::get(converter.convertType(type.getType())); + }); + + target.markUnknownOpDynamicallyLegal( + [&](Operation *op) { return converter.isLegal(op); }); + target.addDynamicallyLegalOp([&](func::FuncOp func) { + FunctionType fty = func.getFunctionType(); + return converter.isLegal(fty.getInputs()) && + converter.isLegal(fty.getResults()); + }); + + patterns.add(converter, &getContext()); + if (failed( + applyFullConversion(getOperation(), target, std::move(patterns)))) { + return signalPassFailure(); + } + + // Run an empty set of patterns through applyPatternsGreedily to perform + // a poor-man's DCE. + if (failed(applyPatternsGreedily(getOperation(), + RewritePatternSet(&getContext())))) { + return signalPassFailure(); + } +} diff --git a/lib/Tools/arcilator/pipelines.cpp b/lib/Tools/arcilator/pipelines.cpp index b980eef85df8..8bcfafb53470 100644 --- a/lib/Tools/arcilator/pipelines.cpp +++ b/lib/Tools/arcilator/pipelines.cpp @@ -126,6 +126,7 @@ void circt::populateArcStateLoweringPipeline( void circt::populateArcStateAllocationPipeline( OpPassManager &pm, const ArcStateAllocationOptions &options) { pm.addPass(arc::createLowerArcsToFuncs()); + pm.addPass(arc::createRemoveI0Types()); { AllocateStateOptions allocStateOpts; allocStateOpts.insertTraceTaps = options.insertTraceTaps; diff --git a/test/Conversion/ArcToLLVM/lower-arc-to-llvm.mlir b/test/Conversion/ArcToLLVM/lower-arc-to-llvm.mlir index b18ec9e9e591..8a125bc15825 100644 --- a/test/Conversion/ArcToLLVM/lower-arc-to-llvm.mlir +++ b/test/Conversion/ArcToLLVM/lower-arc-to-llvm.mlir @@ -233,22 +233,6 @@ func.func @WriteArray(%arg0: !arc.state>, %arg1: !hw.array<4xi1> return } -// The LLVM IR does not like `i0` types. The lowering replaces all `i0` values -// with constants to allow canonicalizers to elide i0 values as needed. -// See https://github.com/llvm/circt/pull/8871. -// CHECK-LABEL: llvm.func @DontCrashOnI0( -func.func @DontCrashOnI0(%arg0: i1, %arg1: !hw.array<1xi42>) -> i42 { - // CHECK: [[STACK:%.+]] = llvm.alloca {{%.+}} x !llvm.array<1 x i42> - // CHECK: [[ZERO:%.+]] = llvm.mlir.constant(0 : i0) : i0 - // CHECK: [[ZEXT:%.+]] = llvm.zext [[ZERO]] : i0 to i1 - // CHECK: [[GEP:%.+]] = llvm.getelementptr [[STACK]][0, [[ZEXT]]] : - // CHECK: [[RESULT:%.+]] = llvm.load [[GEP]] : !llvm.ptr -> i42 - // CHECK: llvm.return [[RESULT]] - %0 = comb.extract %arg0 from 0 : (i1) -> i0 - %1 = hw.array_get %arg1[%0] : !hw.array<1xi42>, i0 - return %1 : i42 -} - // CHECK-LABEL: llvm.func @ExecuteEmpty func.func @ExecuteEmpty() { // CHECK-NEXT: llvm.br [[BB:\^.+]] diff --git a/test/Dialect/Arc/remove-i0-types.mlir b/test/Dialect/Arc/remove-i0-types.mlir new file mode 100644 index 000000000000..2b8bfc0089e0 --- /dev/null +++ b/test/Dialect/Arc/remove-i0-types.mlir @@ -0,0 +1,209 @@ +// RUN: circt-opt --arc-remove-i0-types %s | FileCheck %s + +// The i0 argument is removed entirely from the function signature. +// CHECK-LABEL: func.func @TakesI0() +// CHECK-NEXT: return +// CHECK-NEXT: } +func.func @TakesI0(%a: i0) { + return +} + +// All i0 operations are dead code and are erased. +// CHECK-LABEL: func.func @DeadCode() +// CHECK-NEXT: return +// CHECK-NEXT: } +func.func @DeadCode(%a: i0) { + %0 = comb.and %a, %a : i0 + %1 = comb.or %0, %a : i0 + return +} + +// Both the i0 argument and the i0 result are removed. +// CHECK-LABEL: func.func @ReturnsI0() +// CHECK-NEXT: return +// CHECK-NEXT: } +func.func @ReturnsI0(%a: i0) -> i0 { + return %a : i0 +} + +// The i0 constant and return value are removed. +// CHECK-LABEL: func.func @ConstantI0() +// CHECK-NEXT: return +// CHECK-NEXT: } +func.func @ConstantI0() -> i0 { + %cst = hw.constant 0 : i0 + return %cst : i0 +} + +// The i0 index argument is removed, and the single-element array is unwrapped +// to its element type. The array_get is replaced by the element directly. +// CHECK-LABEL: func.func @ArrayGet(%arg0: i32) -> i32 +// CHECK-NEXT: return %arg0 : i32 +// CHECK-NEXT: } +func.func @ArrayGet(%a: !hw.array<1xi32>, %b: i0) -> i32 { + %0 = hw.array_get %a[%b] : !hw.array<1xi32>, i0 + return %0 : i32 +} + +// The single-element array create is replaced by its element directly. +// CHECK-LABEL: func.func @ArrayCreate(%arg0: i32) -> i32 +// CHECK-NEXT: return %arg0 : i32 +// CHECK-NEXT: } +func.func @ArrayCreate(%a: i32) -> !hw.array<1xi32> { + %0 = hw.array_create %a : i32 + return %0 : !hw.array<1xi32> +} + +// The single-element array inject is replaced by the injected element; the i0 +// index argument is removed. +// CHECK-LABEL: func.func @ArrayInject(%arg0: i32, %arg1: i32) -> i32 +// CHECK-NEXT: return %arg1 : i32 +// CHECK-NEXT: } +func.func @ArrayInject(%a: !hw.array<1xi32>, %b: i0, %c: i32) -> !hw.array<1xi32> { + %0 = hw.array_inject %a[%b], %c : !hw.array<1xi32>, i0 + return %0 : !hw.array<1xi32> +} + +// The i0 argument is removed from both the caller and the callee signature. +// CHECK-LABEL: func.func @Call() +// CHECK-NEXT: call @TakesI0() : () -> () +// CHECK-NEXT: return +// CHECK-NEXT: } +func.func @Call(%a: i0) { + func.call @TakesI0(%a) : (i0) -> () + return +} + +// The i0 return value is removed from both the caller and the callee signature. +// CHECK-LABEL: func.func @Call2() +// CHECK-NEXT: call @ReturnsI0() : () -> () +// CHECK-NEXT: return +// CHECK-NEXT: } +func.func @Call2(%a: i0) -> i0 { + %0 = func.call @ReturnsI0(%a) : (i0) -> (i0) + return %0 : i0 +} + +// The single-element array inside the struct is unwrapped to its element type. +// CHECK-LABEL: func.func @StructCreate(%arg0: i32) -> !hw.struct +// CHECK-NEXT: %0 = hw.struct_create (%arg0) : !hw.struct +// CHECK-NEXT: return %0 : !hw.struct +// CHECK-NEXT: } +func.func @StructCreate(%a: !hw.array<1xi32>) -> !hw.struct> { + %0 = hw.struct_create (%a) : !hw.struct> + return %0 : !hw.struct> +} + +// CHECK-LABEL: func.func @StructExtract(%arg0: !hw.struct) -> i32 +// CHECK-NEXT: %x = hw.struct_extract %arg0["x"] : !hw.struct +// CHECK-NEXT: return %x : i32 +// CHECK-NEXT: } +func.func @StructExtract(%a: !hw.struct) -> i32 { + %0 = hw.struct_extract %a["x"] : !hw.struct + return %0 : i32 +} + +// Mixed i0 and non-i0 arguments: the i0 arguments are removed while the +// non-i0 arguments are preserved. +// CHECK-LABEL: func.func @MixedArgs(%arg0: i32, %arg1: i8) -> i32 +// CHECK-NEXT: return %arg0 : i32 +// CHECK-NEXT: } +func.func @MixedArgs(%a: i0, %b: i32, %c: i0, %d: i8) -> i32 { + return %b : i32 +} + +// Mixed i0 and non-i0 return values: the i0 results are removed. +// CHECK-LABEL: func.func @MixedResults(%arg0: i32) -> i32 +// CHECK-NEXT: return %arg0 : i32 +// CHECK-NEXT: } +func.func @MixedResults(%a: i32) -> (i32, i0) { + %cst = hw.constant 0 : i0 + return %a, %cst : i32, i0 +} + +// Calling a function with mixed i0 and non-i0 arguments and results. +// CHECK-LABEL: func.func @CallMixed(%arg0: i32) -> i32 +// CHECK-NEXT: %0 = call @MixedResults(%arg0) : (i32) -> i32 +// CHECK-NEXT: return %0 : i32 +// CHECK-NEXT: } +func.func @CallMixed(%a: i32) -> i32 { + %cst = hw.constant 0 : i0 + %0:2 = func.call @MixedResults(%a) : (i32) -> (i32, i0) + return %0#0 : i32 +} + +// A struct with an i0 field: the i0 field type is replaced with !arc.poison +// by the type converter. Struct fields are not removed, only type-converted. +// CHECK-LABEL: func.func @StructWithI0Field(%arg0: i32) -> !hw.struct +// CHECK-NEXT: %0 = builtin.unrealized_conversion_cast to !arc.poison +// CHECK-NEXT: %1 = hw.struct_create (%arg0, %0) : !hw.struct +// CHECK-NEXT: return %1 : !hw.struct +// CHECK-NEXT: } +func.func @StructWithI0Field(%a: i32, %b: i0) -> !hw.struct { + %0 = hw.struct_create (%a, %b) : !hw.struct + return %0 : !hw.struct +} + +// Extracting a non-i0 field from a struct that also contains an i0 field. +// CHECK-LABEL: func.func @StructExtractNonI0(%arg0: !hw.struct) -> i32 +// CHECK-NEXT: %x = hw.struct_extract %arg0["x"] : !hw.struct +// CHECK-NEXT: return %x : i32 +// CHECK-NEXT: } +func.func @StructExtractNonI0(%a: !hw.struct) -> i32 { + %0 = hw.struct_extract %a["x"] : !hw.struct + return %0 : i32 +} + +// A function that has all i0 arguments: all are removed, leaving an empty +// argument list. +// CHECK-LABEL: func.func @AllI0Args() +// CHECK-NEXT: return +// CHECK-NEXT: } +func.func @AllI0Args(%a: i0, %b: i0, %c: i0) { + return +} + +// Multi-element array should remain an array type (only single-element arrays +// are unwrapped). +// CHECK-LABEL: func.func @MultiElementArray(%arg0: !hw.array<4xi32>) -> !hw.array<4xi32> +// CHECK-NEXT: return %arg0 : !hw.array<4xi32> +// CHECK-NEXT: } +func.func @MultiElementArray(%a: !hw.array<4xi32>) -> !hw.array<4xi32> { + return %a : !hw.array<4xi32> +} + +// Non-i0 types should pass through unchanged. +// CHECK-LABEL: func.func @NoI0(%arg0: i32, %arg1: i64) -> i32 +// CHECK-NEXT: return %arg0 : i32 +// CHECK-NEXT: } +func.func @NoI0(%a: i32, %b: i64) -> i32 { + return %a : i32 +} + +// CHECK-LABEL: func.func @ArrayOfArrayOf1 +// CHECK-SAME: !hw.array<2xi32> +func.func @ArrayOfArrayOf1(%arg0: !hw.array<2x!hw.array<1xi32>>) -> + !hw.array<2x!hw.array<1xi32>> { + return %arg0 : !hw.array<2x!hw.array<1xi32>> +} + +// CHECK-LABEL: func.func @StateType +// CHECK-SAME: !arc.state +func.func @StateType(%arg0: !arc.state>) { + return +} + +// CHECK-LABEL: func.func @AggregateConstantBecomesScalar +// CHECK-SAME: -> i32 +// CHECK-NEXT: hw.constant 0 : i32 +func.func @AggregateConstantBecomesScalar() -> !hw.array<1xi32> { + %0 = hw.aggregate_constant [0 : i32] : !hw.array<1xi32> + return %0 : !hw.array<1xi32> +} + +// CHECK-LABEL: func.func @AggregateConstantStaysAggregate +// CHECK-NEXT: hw.aggregate_constant [0 : i32, 1 : i32] : !hw.array<2xi32> +func.func @AggregateConstantStaysAggregate() -> !hw.array<2x!hw.array<1xi32>> { + %0 = hw.aggregate_constant [[0 : i32], [1 : i32]] : !hw.array<2x!hw.array<1xi32>> + return %0 : !hw.array<2x!hw.array<1xi32>> +}