diff --git a/changelogs/unreleased/fix__deferred-template-expressions.yaml b/changelogs/unreleased/fix__deferred-template-expressions.yaml new file mode 100644 index 0000000000..b9a412a60c --- /dev/null +++ b/changelogs/unreleased/fix__deferred-template-expressions.yaml @@ -0,0 +1,2 @@ +fixed: + - Preserve used template expressions during partial specialization without blocking unrelated targets diff --git a/include/llzk/Dialect/Polymorphic/Transforms/TransformationPasses.td b/include/llzk/Dialect/Polymorphic/Transforms/TransformationPasses.td index 52475288d1..f0510875e3 100644 --- a/include/llzk/Dialect/Polymorphic/Transforms/TransformationPasses.td +++ b/include/llzk/Dialect/Polymorphic/Transforms/TransformationPasses.td @@ -61,6 +61,8 @@ def FlatteningPass : LLZKPass<"llzk-flatten"> { - Replace parameterized structs with flattened (i.e., no parameter) versions of those structs based on requested return type at calls to `compute()` functions and unroll loops + - Preserve template expressions needed by partial specializations until + their remaining parameters become concrete - Unroll loops }]; // Implementation note: These options should be kept in sync with diff --git a/lib/Dialect/Polymorphic/Transforms/FlatteningPass.cpp b/lib/Dialect/Polymorphic/Transforms/FlatteningPass.cpp index a6b28744bf..6abb1e7d8f 100644 --- a/lib/Dialect/Polymorphic/Transforms/FlatteningPass.cpp +++ b/lib/Dialect/Polymorphic/Transforms/FlatteningPass.cpp @@ -343,6 +343,84 @@ class ClonedBodyConstReadOpPattern } }; +/// Converts template type variables whose bindings became concrete. More specialized converters +/// extend this for compound types, while deferred expressions need this common scalar behavior. +class TemplateParamTypeConverter : public TypeConverter { + const DenseMap ¶mNameToValue; + +protected: + Attribute convertIfPossible(Attribute attr) const { + auto res = paramNameToValue.find(attr); + return (res != paramNameToValue.end()) ? res->second : attr; + } + +public: + explicit TemplateParamTypeConverter(const DenseMap ¶mNameToConcrete) + : TypeConverter(), paramNameToValue(paramNameToConcrete) { + addConversion([](Type type) { return type; }); + addConversion([this](TypeVarType inputTy) -> Type { + if (TypeAttr tyAttr = llvm::dyn_cast(convertIfPossible(inputTy.getNameRef()))) { + Type convertedType = tyAttr.getValue(); + if (isConcreteType(convertedType)) { + return convertedType; + } + } + return inputTy; + }); + } + + Attribute convertAttr(Attribute attr) const { + if (TypeAttr tyAttr = llvm::dyn_cast(attr)) { + Type convertedTy = convertType(tyAttr.getValue()); + if (convertedTy != tyAttr.getValue()) { + return TypeAttr::get(convertedTy); + } + } + return convertIfPossible(attr); + } + + bool containsParam(Attribute nameAttr) const { return paramNameToValue.contains(nameAttr); } + const DenseMap &getParamMap() const { return paramNameToValue; } +}; + +/// Clone a deferred template expression and materialize parameters that became concrete. The +/// reduced template preserves neither their symbols nor type variables, so both value reads and +/// operation types must be converted before the expression is retained for later instantiation. +/// An empty result defers the whole partial instantiation when a concrete value's type is not yet +/// known; removing that value before it can be materialized would lose a required binding. +static FailureOr> cloneDeferredExpr( + TemplateExprOp exprOp, const DenseMap ¶mNameToConcrete, + SmallVector &diagnostics +) { + MLIRContext *ctx = exprOp.getContext(); + TemplateParamTypeConverter tyConv(paramNameToConcrete); + WalkResult blocked = exprOp.walk([&](ConstReadOp readOp) { + if (!paramNameToConcrete.contains(readOp.getConstNameAttr())) { + return WalkResult::advance(); + } + Type convertedType = tyConv.convertType(readOp.getType()); + return (!convertedType || !isConcreteType(convertedType)) ? WalkResult::interrupt() + : WalkResult::advance(); + }); + if (blocked.wasInterrupted()) { + return std::optional(); + } + + TemplateExprOp clonedExpr = llvm::cast(exprOp->clone()); + ConversionTarget target = newConverterDefinedTarget<>(tyConv, ctx); + target.addDynamicallyLegalOp([&](ConstReadOp op) { + return !paramNameToConcrete.contains(op.getConstNameAttr()) && defaultLegalityCheck(tyConv, op); + }); + + RewritePatternSet patterns = newGeneralRewritePatternSet<>(tyConv, ctx, target); + patterns.add(tyConv, ctx, paramNameToConcrete, diagnostics); + if (failed(applyFullConversion(clonedExpr, target, std::move(patterns)))) { + clonedExpr->destroy(); + return failure(); + } + return std::make_optional(clonedExpr); +} + /// Patterns can use this listener and call notifyMatchFailure(..) for failures where the entire /// pass must fail, i.e., where instantiation would introduce an illegal type conversion. struct MatchFailureListener : public RewriterBase::Listener { @@ -418,24 +496,36 @@ static bool calleeReferencesTemplateParam(CallOp op) { return parentTemplate.hasConstNamed(callee.getRootReference()); } -/// Attempt to evaluate the concrete result of a single `TemplateExprOp` expression given -/// the currently-known concrete param values in `paramNameToConcrete`. Returns the result -/// attribute if all referenced params are concrete and all operations in the body can be -/// constant-folded; otherwise returns `std::nullopt`. -static std::optional +/// Evaluate a single template expression. An unresolved parameter defers evaluation; malformed, +/// incompatible, or non-foldable concrete expressions are semantic errors. +static FailureOr> evaluateExpr(TemplateExprOp exprOp, const DenseMap ¶mNameToConcrete) { + // Deferral depends on the expression's complete parameter set, not operation order. Do not + // diagnose a non-foldable prefix while a later read still requires partial instantiation. + WalkResult unresolvedParam = exprOp.walk([&](ConstReadOp op) { + return paramNameToConcrete.contains(op.getConstNameAttr()) ? WalkResult::advance() + : WalkResult::interrupt(); + }); + if (unresolvedParam.wasInterrupted()) { + return std::optional(); + } + // Map from SSA value in the expr body to its concrete Attribute. DenseMap valueMap; for (Operation &bodyOp : exprOp.getInitializerRegion().front()) { if (auto yieldOp = llvm::dyn_cast(bodyOp)) { auto it = valueMap.find(yieldOp.getVal()); - return it != valueMap.end() ? std::make_optional(it->second) : std::nullopt; + if (it != valueMap.end()) { + return std::make_optional(it->second); + } + yieldOp.emitOpError("cannot evaluate yielded value as a concrete template constant"); + return failure(); } if (auto constReadOp = llvm::dyn_cast(bodyOp)) { auto it = paramNameToConcrete.find(constReadOp.getConstNameAttr()); if (it == paramNameToConcrete.end()) { - return std::nullopt; // a referenced param is not concrete + return std::optional(); } // If the attribute type is `FeltType` but it's stored as an IntegerAttr, promote to // a `FeltConstAttr`. @@ -455,51 +545,79 @@ evaluateExpr(TemplateExprOp exprOp, const DenseMap ¶mN for (Value operand : bodyOp.getOperands()) { auto it = valueMap.find(operand); if (it == valueMap.end()) { - return std::nullopt; // operand not known as a constant + bodyOp.emitOpError("cannot evaluate operand as a concrete template constant"); + return failure(); } operandAttrs.push_back(it->second); } // Try constant folding. SmallVector foldResults; - if (succeeded(bodyOp.fold(operandAttrs, foldResults)) && - foldResults.size() == bodyOp.getNumResults()) { - for (auto [result, fr] : llvm::zip_equal(bodyOp.getResults(), foldResults)) { - if (Attribute a = llvm::dyn_cast(fr)) { - valueMap[result] = a; - } else { - return std::nullopt; - } + if (failed(bodyOp.fold(operandAttrs, foldResults)) || + foldResults.size() != bodyOp.getNumResults()) { + bodyOp.emitOpError("cannot fold concrete template expression"); + return failure(); + } + for (auto [result, fr] : llvm::zip_equal(bodyOp.getResults(), foldResults)) { + if (Attribute a = llvm::dyn_cast(fr)) { + valueMap[result] = a; + } else { + bodyOp.emitOpError("template expression fold did not produce a constant attribute"); + return failure(); } } } - return std::nullopt; // no YieldOp found (shouldn't happen in a valid expr) + exprOp.emitOpError("initializer has no yield operation"); + return failure(); } -/// Evaluate all `TemplateExprOp`s in `templateOp` that can be computed from the currently-known -/// concrete param values in `paramNameToConcrete`, and add their results to the map. -/// Exprs whose operands are not all concrete are silently skipped (partial instantiation). -static void -evaluateTemplateExprs(TemplateOp templateOp, DenseMap ¶mNameToConcrete) { +/// Return whether `target` may use `exprOp`. Symbol-use analysis stops at symbol-table boundaries, +/// so inspect target regions separately. An unknown result is conservatively treated as a use. +static bool targetMayUseTemplateExpr(Operation *target, TemplateExprOp exprOp) { + if (!symbolKnownUseEmpty(exprOp.getOperation(), target)) { + return true; + } + return llvm::any_of(target->getRegions(), [&](Region ®ion) { + return !symbolKnownUseEmpty(exprOp.getOperation(), ®ion); + }); +} + +/// Evaluate the `TemplateExprOp`s used by `target` that can be computed from the currently-known +/// concrete param values, adding results to the map and returning the expressions that must remain +/// available for a later partial instantiation. +static FailureOr> evaluateTemplateExprs( + TemplateOp templateOp, Operation *target, DenseMap ¶mNameToConcrete +) { LLVM_DEBUG( llvm::dbgs() << "[evaluateTemplateExprs] before: " << debug::toStringList(paramNameToConcrete) << '\n' ); + SmallVector deferredExprs; for (TemplateExprOp exprOp : templateOp.getConstOps()) { - std::optional result = evaluateExpr(exprOp, paramNameToConcrete); - if (result.has_value()) { + if (!targetMayUseTemplateExpr(target, exprOp)) { + continue; + } + FailureOr> result = evaluateExpr(exprOp, paramNameToConcrete); + if (failed(result)) { + return failure(); + } + if (*result) { + Attribute value = result->value(); auto exprNameAttr = FlatSymbolRefAttr::get(exprOp.getSymNameAttr()); - paramNameToConcrete.try_emplace(exprNameAttr, *result); + paramNameToConcrete.try_emplace(exprNameAttr, value); LLVM_DEBUG( llvm::dbgs() << "[evaluateTemplateExprs] expr @" << exprOp.getSymName() - << " evaluated to " << *result << '\n' + << " evaluated to " << value << '\n' ); + } else { + deferredExprs.push_back(exprOp); } } LLVM_DEBUG( llvm::dbgs() << "[evaluateTemplateExprs] after: " << debug::toStringList(paramNameToConcrete) << '\n' ); + return deferredExprs; } namespace Step1_InstantiateStructs { @@ -516,15 +634,9 @@ class StructCloner { SymbolTableCollection symTables; bool reportMissing = true; - class MappedTypeConverter : public TypeConverter { + class MappedTypeConverter : public TemplateParamTypeConverter { StructType origTy; StructType newTy; - const DenseMap ¶mNameToValue; - - inline Attribute convertIfPossible(Attribute a) const { - auto res = this->paramNameToValue.find(a); - return (res != this->paramNameToValue.end()) ? res->second : a; - } public: MappedTypeConverter( @@ -532,10 +644,8 @@ class StructCloner { /// Instantiated values for the parameter names in `originalType` const DenseMap ¶mNameToInstantiatedValue ) - : TypeConverter(), origTy(originalType), newTy(newType), - paramNameToValue(paramNameToInstantiatedValue) { - - addConversion([](Type inputTy) { return inputTy; }); + : TemplateParamTypeConverter(paramNameToInstantiatedValue), origTy(originalType), + newTy(newType) { addConversion([this](StructType inputTy) { LLVM_DEBUG(llvm::dbgs() << "[MappedTypeConverter] convert " << inputTy << '\n'); @@ -548,11 +658,7 @@ class StructCloner { if (ArrayAttr inputTyParams = inputTy.getParams()) { SmallVector updated; for (Attribute a : inputTyParams) { - if (TypeAttr ta = dyn_cast(a)) { - updated.push_back(TypeAttr::get(this->convertType(ta.getValue()))); - } else { - updated.push_back(convertIfPossible(a)); - } + updated.push_back(convertAttr(a)); } return getStructTypeWithParams(inputTy.getNameRef(), inputTy.getContext(), updated); } @@ -573,20 +679,6 @@ class StructCloner { // Otherwise, return the type unchanged return inputTy; }); - - addConversion([this](TypeVarType inputTy) -> Type { - // Check for replacement of parameter symbol name with a concrete type - if (TypeAttr tyAttr = llvm::dyn_cast(convertIfPossible(inputTy.getNameRef()))) { - Type convertedType = tyAttr.getValue(); - // Use the new type unless it contains a TypeVarType because a TypeVarType from a - // different struct references a parameter name from that other struct, not from the - // current struct so the reference would be invalid. - if (isConcreteType(convertedType)) { - return convertedType; - } - } - return inputTy; - }); } }; @@ -708,11 +800,23 @@ class StructCloner { // Evaluate any poly.expr symbols whose param dependencies are now concrete; add them to the // map so ClonedBodyConstReadOpPattern can replace uses of those symbols too. - evaluateTemplateExprs(parentTemplate, paramNameToConcrete); + FailureOr> exprEvaluation = + evaluateTemplateExprs(parentTemplate, origStruct.getOperation(), paramNameToConcrete); + if (failed(exprEvaluation)) { + return failure(); + } + SmallVector deferredExprs = std::move(*exprEvaluation); + if (remainingNames.empty() && !deferredExprs.empty()) { + deferredExprs.front().emitOpError( + "cannot complete instantiation while a template expression remains deferred" + ); + return failure(); + } // Clone the original struct. StructDefOp newStruct = origStruct.clone(); convertCalleesInPlace(newStruct, paramNameToConcrete); + SmallVector deferredExprDiagnostics; if (remainingNames.empty()) { // FULL INSTANTIATION CASE // Set name of the new struct by prepending its name with instantiated template name. newStruct.setSymName( @@ -739,6 +843,16 @@ class StructCloner { assert(symOp && "symbol must exist"); newTemplate.insert(newTemplate.begin(), symOp->clone()); } + for (TemplateExprOp exprOp : deferredExprs) { + FailureOr> clonedExpr = + cloneDeferredExpr(exprOp, paramNameToConcrete, deferredExprDiagnostics); + if (failed(clonedExpr) || !clonedExpr->has_value()) { + newTemplate->destroy(); + newStruct->destroy(); + return failure(); + } + newTemplate.getBodyRegion().front().push_back(**clonedExpr); + } // Insert the struct into the template and the template into the module. Use the // `SymbolTable::insert()` function so that the name will be made unique if necessary. @@ -753,6 +867,13 @@ class StructCloner { // Retrieve the new type AFTER inserting since the struct name may be appended to make // it unique and use the remaining non-concrete parameters from the original type. StructType newLocalType = newStruct.getType(reducedCallerParams); + if (!deferredExprDiagnostics.empty()) { + SmallVector &diagnostics = tracker_.delayedDiagnosticSet(newLocalType); + diagnostics.append( + std::make_move_iterator(deferredExprDiagnostics.begin()), + std::make_move_iterator(deferredExprDiagnostics.end()) + ); + } typeAtCallerSymPieces.push_back( FlatSymbolRefAttr::get(newLocalType.getNameRef().getLeafReference()) ); @@ -979,28 +1100,10 @@ namespace Step2_InstantiateFunctions { /// TypeConverter for function instantiation that replaces TypeVarType and symbolic /// ArrayType/StructType parameters with their concrete values determined by unification. -class FuncInstTypeConverter : public TypeConverter { - DenseMap paramNameToValue; - - Attribute convertIfPossible(Attribute a) const { - auto res = paramNameToValue.find(a); - return (res != paramNameToValue.end()) ? res->second : a; - } - +class FuncInstTypeConverter : public TemplateParamTypeConverter { public: - explicit FuncInstTypeConverter(DenseMap paramNameToConcrete) - : TypeConverter(), paramNameToValue(std::move(paramNameToConcrete)) { - addConversion([](Type t) { return t; }); - - addConversion([this](TypeVarType inputTy) -> Type { - if (TypeAttr tyAttr = llvm::dyn_cast(convertIfPossible(inputTy.getNameRef()))) { - Type convertedType = tyAttr.getValue(); - if (isConcreteType(convertedType)) { - return convertedType; - } - } - return inputTy; - }); + explicit FuncInstTypeConverter(const DenseMap ¶mNameToConcrete) + : TemplateParamTypeConverter(paramNameToConcrete) { addConversion([this](ArrayType inputTy) { SmallVector updated; @@ -1050,19 +1153,6 @@ class FuncInstTypeConverter : public TypeConverter { return inputTy; }); } - - Attribute convertAttr(Attribute attr) const { - if (TypeAttr tyAttr = llvm::dyn_cast(attr)) { - Type convertedTy = convertType(tyAttr.getValue()); - if (convertedTy != tyAttr.getValue()) { - return TypeAttr::get(convertedTy); - } - } - return convertIfPossible(attr); - } - - bool containsParam(Attribute nameAttr) const { return paramNameToValue.contains(nameAttr); } - const DenseMap &getParamMap() const { return paramNameToValue; } }; /// Return the callee-side unification-derived value for a template parameter, if any. @@ -1356,7 +1446,12 @@ class InstantiateFuncAtCallOp final : public OpRewritePattern { return failure(); } - evaluateTemplateExprs(parentTemplate, paramNameToConcrete); + FailureOr> exprEvaluation = + evaluateTemplateExprs(parentTemplate, callTgt.getOperation(), paramNameToConcrete); + if (failed(exprEvaluation)) { + return failure(); + } + SmallVector deferredExprs = std::move(*exprEvaluation); InstantiationLayout layout = buildInstantiationLayout(parentTemplate, op.getTemplateParamsAttr(), paramNameToConcrete); @@ -1364,6 +1459,12 @@ class InstantiateFuncAtCallOp final : public OpRewritePattern { assert(parentModule && "TemplateOp must be nested in a ModuleOp"); SymbolRefAttr originalCalleeAttr = op.getCalleeAttr(); + if (layout.remainingNames.empty() && !deferredExprs.empty()) { + deferredExprs.front().emitOpError( + "cannot complete instantiation while a template expression remains deferred" + ); + return failure(); + } FailureOr newCalleeAttr = layout.remainingNames.empty() ? instantiateFully( @@ -1372,7 +1473,7 @@ class InstantiateFuncAtCallOp final : public OpRewritePattern { ) : instantiatePartially( op, rewriter, symTables, callTgt, parentTemplate, parentModule, layout, - paramNameToConcrete + paramNameToConcrete, deferredExprs ); if (failed(newCalleeAttr)) { return failure(); @@ -1581,7 +1682,8 @@ class InstantiateFuncAtCallOp final : public OpRewritePattern { static FailureOr instantiatePartially( CallOp op, PatternRewriter &rewriter, SymbolTableCollection &symTables, FuncDefOp callTgt, TemplateOp parentTemplate, ModuleOp parentModule, const InstantiationLayout &layout, - const DenseMap ¶mNameToConcrete + const DenseMap ¶mNameToConcrete, + ArrayRef deferredExprs ) { TemplateOp newTemplate; if (Operation *existing = @@ -1601,6 +1703,16 @@ class InstantiateFuncAtCallOp final : public OpRewritePattern { assert(paramOp && "symbol must exist"); newTemplateBody.push_back(paramOp->clone()); } + SmallVector deferredExprDiagnostics; + for (TemplateExprOp exprOp : deferredExprs) { + FailureOr> clonedExpr = + cloneDeferredExpr(exprOp, paramNameToConcrete, deferredExprDiagnostics); + if (failed(clonedExpr) || !clonedExpr->has_value()) { + newTemplate->destroy(); + return failure(); + } + newTemplateBody.push_back(**clonedExpr); + } // Clone and partially convert the function (concretize only the concrete params). FuncDefOp newFunc = callTgt.clone(); @@ -1621,6 +1733,7 @@ class InstantiateFuncAtCallOp final : public OpRewritePattern { diag.append("failure while creating instantiated function '", newFuncName, '\''); }); } + ::reportDelayedDiagnostics(op, std::move(deferredExprDiagnostics)); LLVM_DEBUG( llvm::dbgs() << "[InstantiateFuncAtCallOp] created partial instantiation template: " diff --git a/test/Transforms/Flattening/instantiate_expr_fail.llzk b/test/Transforms/Flattening/instantiate_expr_fail.llzk new file mode 100644 index 0000000000..31b69c0409 --- /dev/null +++ b/test/Transforms/Flattening/instantiate_expr_fail.llzk @@ -0,0 +1,59 @@ +// RUN: llzk-opt -split-input-file -llzk-flatten -verify-diagnostics %s 2>&1 | FileCheck --enable-var-scope %s + +module attributes {llzk.lang, llzk.main = !struct.type<@StructExpr::@Value<[5]>>} { + poly.template @StructExpr { + poly.param @N : index + poly.expr @NestedN { + // expected-error@+1 {{'scf.execute_region' op cannot fold concrete template expression}} + %value = scf.execute_region -> index { + %n = poly.read_const @N : index + scf.yield %n : index + } + poly.yield %value : index + } + struct.def @Value { + function.def @compute() -> !struct.type<@StructExpr::@Value<[@N]>> { + %value = poly.read_const @NestedN : index + %self = struct.new : <@StructExpr::@Value<[@N]>> + function.return %self : !struct.type<@StructExpr::@Value<[@N]>> + } + function.def @constrain(%self: !struct.type<@StructExpr::@Value<[@N]>>) { + function.return + } + } + } +} + +// ----- + +module attributes {llzk.lang} { + poly.template @FunctionExpr { + poly.param @N : index + poly.expr @NestedN { + // expected-error@+1 {{'scf.execute_region' op cannot fold concrete template expression}} + %value = scf.execute_region -> index { + %n = poly.read_const @N : index + scf.yield %n : index + } + poly.yield %value : index + } + function.def @value() -> index { + %value = poly.read_const @NestedN : index + function.return %value : index + } + } + + struct.def @Main { + function.def @compute() -> !struct.type<@Main> { + %self = struct.new : <@Main> + %value = function.call @FunctionExpr::@value<[5]>() : () -> index + function.return %self : !struct.type<@Main> + } + function.def @constrain(%self: !struct.type<@Main>) { + function.return + } + } +} + +// Verify the command-level pass failure after the split-local diagnostics. +// CHECK: llzk-flatten failed while instantiating the main struct diff --git a/test/Transforms/Flattening/instantiate_expr_partial.llzk b/test/Transforms/Flattening/instantiate_expr_partial.llzk new file mode 100644 index 0000000000..e19e458c9d --- /dev/null +++ b/test/Transforms/Flattening/instantiate_expr_partial.llzk @@ -0,0 +1,398 @@ +// RUN: llzk-opt -split-input-file -llzk-flatten %s | FileCheck %s + +#id = affine_map<(i)->(i)> +module attributes {llzk.lang, llzk.main = !struct.type<@Outer::@Wrapper<[7]>>} { + poly.template @Inner { + poly.param @N : index + poly.param @M : index + poly.expr @TwiceM { + %m = poly.read_const @M : index + %two = arith.constant 2 : index + %result = arith.muli %m, %two : index + poly.yield %result : index + } + poly.expr @NPlusM { + %n = poly.read_const @N : index + %m = poly.read_const @M : index + %result = arith.addi %n, %m : index + poly.yield %result : index + } + struct.def @Value { + struct.member @values : !array.type<#id x !felt.type> + function.def @compute() -> !struct.type<@Inner::@Value<[@N, @M]>> { + %sum = poly.read_const @NPlusM : index + %self = struct.new : <@Inner::@Value<[@N, @M]>> + %values = array.new{(%sum)[]} : !array.type<#id x !felt.type> + struct.writem %self[@values] = %values : <@Inner::@Value<[@N, @M]>>, !array.type<#id x !felt.type> + function.return %self : !struct.type<@Inner::@Value<[@N, @M]>> + } + function.def @constrain(%self: !struct.type<@Inner::@Value<[@N, @M]>>) { + function.return + } + } + } + + poly.template @Outer { + poly.param @M : index + struct.def @Wrapper { + struct.member @value : !struct.type<@Inner::@Value<[5, @M]>> + function.def @compute() -> !struct.type<@Outer::@Wrapper<[@M]>> { + %self = struct.new : <@Outer::@Wrapper<[@M]>> + function.return %self : !struct.type<@Outer::@Wrapper<[@M]>> + } + function.def @constrain(%self: !struct.type<@Outer::@Wrapper<[@M]>>) { + function.return + } + } + } +} + +// CHECK: #[[$ATTR_0:[0-9a-zA-Z_\.]+]] = affine_map<(d0) -> (d0)> +// CHECK-LABEL: module attributes {llzk.lang, llzk.main = !struct.type<@Outer_7_Wrapper>} { +// CHECK-NEXT: struct.def @Inner_5_7_Value { +// CHECK-NEXT: struct.member @values : !array.type<12 x !felt.type> +// CHECK-NEXT: function.def @compute() -> !struct.type<@Inner_5_7_Value> attributes {function.allow_witness} { +// CHECK-NEXT: %[[VAL_0:[0-9a-zA-Z_\.]+]] = struct.new : <@Inner_5_7_Value> +// CHECK-NEXT: %[[VAL_1:[0-9a-zA-Z_\.]+]] = array.new : <12 x !felt.type> +// CHECK-NEXT: struct.writem %[[VAL_0]][@values] = %[[VAL_1]] : <@Inner_5_7_Value>, !array.type<12 x !felt.type> +// CHECK-NEXT: function.return %[[VAL_0]] : !struct.type<@Inner_5_7_Value> +// CHECK-NEXT: } +// CHECK-NEXT: function.def @constrain(%[[VAL_2:[0-9a-zA-Z_\.]+]]: !struct.type<@Inner_5_7_Value>) attributes {function.allow_constraint} { +// CHECK-NEXT: function.return +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: poly.template @"Inner_5_\1A" { +// CHECK-NEXT: poly.param @M : index +// CHECK-NEXT: poly.expr @NPlusM { +// CHECK-NEXT: %[[VAL_3:[0-9a-zA-Z_\.]+]] = arith.constant 5 : index +// CHECK-NEXT: %[[VAL_4:[0-9a-zA-Z_\.]+]] = poly.read_const @M : index +// CHECK-NEXT: %[[VAL_5:[0-9a-zA-Z_\.]+]] = arith.addi %[[VAL_4]], %[[VAL_3]] : index +// CHECK-NEXT: poly.yield %[[VAL_5]] : index +// CHECK-NEXT: } +// CHECK-NEXT: struct.def @Value { +// CHECK-NEXT: struct.member @values : !array.type<#[[$ATTR_0]] x !felt.type> +// CHECK-NEXT: function.def @compute() -> !struct.type<@"Inner_5_\1A"::@Value<[@M]>> attributes {function.allow_witness} { +// CHECK-NEXT: %[[VAL_6:[0-9a-zA-Z_\.]+]] = poly.read_const @NPlusM : index +// CHECK-NEXT: %[[VAL_7:[0-9a-zA-Z_\.]+]] = struct.new : <@"Inner_5_\1A"::@Value<[@M]>> +// CHECK-NEXT: %[[VAL_8:[0-9a-zA-Z_\.]+]] = array.new{(%[[VAL_6]])} : <#[[$ATTR_0]] x !felt.type> +// CHECK-NEXT: struct.writem %[[VAL_7]][@values] = %[[VAL_8]] : <@"Inner_5_\1A"::@Value<[@M]>>, !array.type<#[[$ATTR_0]] x !felt.type> +// CHECK-NEXT: function.return %[[VAL_7]] : !struct.type<@"Inner_5_\1A"::@Value<[@M]>> +// CHECK-NEXT: } +// CHECK-NEXT: function.def @constrain(%[[VAL_9:[0-9a-zA-Z_\.]+]]: !struct.type<@"Inner_5_\1A"::@Value<[@M]>>) attributes {function.allow_constraint} { +// CHECK-NEXT: function.return +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: struct.def @Outer_7_Wrapper { +// CHECK-NEXT: struct.member @value : !struct.type<@Inner_5_7_Value> +// CHECK-NEXT: function.def @compute() -> !struct.type<@Outer_7_Wrapper> attributes {function.allow_witness} { +// CHECK-NEXT: %[[VAL_10:[0-9a-zA-Z_\.]+]] = struct.new : <@Outer_7_Wrapper> +// CHECK-NEXT: function.return %[[VAL_10]] : !struct.type<@Outer_7_Wrapper> +// CHECK-NEXT: } +// CHECK-NEXT: function.def @constrain(%[[VAL_11:[0-9a-zA-Z_\.]+]]: !struct.type<@Outer_7_Wrapper>) attributes {function.allow_constraint} { +// CHECK-NEXT: function.return +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: } + +// ----- + +module attributes {llzk.lang} { + poly.template @InnerFunc { + poly.param @N : index + poly.param @M : index + poly.expr @TwiceM { + %m = poly.read_const @M : index + %two = arith.constant 2 : index + %result = arith.muli %m, %two : index + poly.yield %result : index + } + poly.expr @NPlusM { + %n = poly.read_const @N : index + %m = poly.read_const @M : index + %result = arith.addi %n, %m : index + poly.yield %result : index + } + function.def @value(%input: !array.type<@N,@M x !felt.type>) -> index { + %sum = poly.read_const @NPlusM : index + function.return %sum : index + } + } + + poly.template @OuterFunc { + poly.param @M : index + function.def @value(%input: !array.type<5,@M x !felt.type>) -> index { + %result = function.call @InnerFunc::@value(%input) : (!array.type<5,@M x !felt.type>) -> index + function.return %result : index + } + } + + struct.def @Main { + function.def @compute(%input: !array.type<5,7 x !felt.type>) -> !struct.type<@Main> { + %self = struct.new : <@Main> + %result = function.call @OuterFunc::@value(%input) : (!array.type<5,7 x !felt.type>) -> index + function.return %self : !struct.type<@Main> + } + function.def @constrain(%self: !struct.type<@Main>, %input: !array.type<5,7 x !felt.type>) { + function.return + } + } +} + +// CHECK-LABEL: module attributes {llzk.lang} { +// CHECK-NEXT: function.def @InnerFunc_5_7_value(%[[VAL_0:[0-9a-zA-Z_\.]+]]: !array.type<5,7 x !felt.type>) -> index { +// CHECK-NEXT: %[[VAL_1:[0-9a-zA-Z_\.]+]] = arith.constant 12 : index +// CHECK-NEXT: function.return %[[VAL_1]] : index +// CHECK-NEXT: } +// CHECK-NEXT: function.def @OuterFunc_7_value(%[[VAL_2:[0-9a-zA-Z_\.]+]]: !array.type<5,7 x !felt.type>) -> index { +// CHECK-NEXT: %[[VAL_3:[0-9a-zA-Z_\.]+]] = function.call @InnerFunc_5_7_value(%[[VAL_2]]) : (!array.type<5,7 x !felt.type>) -> index +// CHECK-NEXT: function.return %[[VAL_3]] : index +// CHECK-NEXT: } +// CHECK-NEXT: struct.def @Main { +// CHECK-NEXT: function.def @compute(%[[VAL_4:[0-9a-zA-Z_\.]+]]: !array.type<5,7 x !felt.type>) -> !struct.type<@Main> attributes {function.allow_witness} { +// CHECK-NEXT: %[[VAL_5:[0-9a-zA-Z_\.]+]] = struct.new : <@Main> +// CHECK-NEXT: %[[VAL_6:[0-9a-zA-Z_\.]+]] = function.call @OuterFunc_7_value(%[[VAL_4]]) : (!array.type<5,7 x !felt.type>) -> index +// CHECK-NEXT: function.return %[[VAL_5]] : !struct.type<@Main> +// CHECK-NEXT: } +// CHECK-NEXT: function.def @constrain(%[[VAL_7:[0-9a-zA-Z_\.]+]]: !struct.type<@Main>, %[[VAL_8:[0-9a-zA-Z_\.]+]]: !array.type<5,7 x !felt.type>) attributes {function.allow_constraint} { +// CHECK-NEXT: function.return +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: } + +// ----- + +module attributes {llzk.lang} { + poly.template @Inner { + poly.param @N : index + poly.param @M : index + poly.expr @NestedM { + %value = scf.execute_region -> index { + %m = poly.read_const @M : index + scf.yield %m : index + } + poly.yield %value : index + } + function.def @value() -> index { + %value = poly.read_const @NestedM : index + function.return %value : index + } + } + + poly.template @Outer { + poly.param @M : index + function.def @value() -> index { + %value = function.call @Inner::@value<[5, @M]>() : () -> index + function.return %value : index + } + } +} + +// CHECK-LABEL: module attributes {llzk.lang} { +// CHECK-NEXT: poly.template @"Inner_5_\1A" { +// CHECK-NEXT: poly.param @M : index +// CHECK-NEXT: poly.expr @NestedM { +// CHECK-NEXT: %[[VAL_0:[0-9a-zA-Z_\.]+]] = scf.execute_region -> index { +// CHECK-NEXT: %[[VAL_1:[0-9a-zA-Z_\.]+]] = poly.read_const @M : index +// CHECK-NEXT: scf.yield %[[VAL_1]] : index +// CHECK-NEXT: } +// CHECK-NEXT: poly.yield %[[VAL_0]] : index +// CHECK-NEXT: } +// CHECK-NEXT: function.def @value() -> index { +// CHECK-NEXT: %[[VAL_2:[0-9a-zA-Z_\.]+]] = poly.read_const @NestedM : index +// CHECK-NEXT: function.return %[[VAL_2]] : index +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: poly.template @Outer { +// CHECK-NEXT: poly.param @M : index +// CHECK-NEXT: function.def @value() -> index { +// CHECK-NEXT: %[[VAL_3:[0-9a-zA-Z_\.]+]] = function.call @"Inner_5_\1A"::@value<[@M]>() : () -> index +// CHECK-NEXT: function.return %[[VAL_3]] : index +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: } + +// ----- + +module attributes {llzk.lang} { + poly.template @InnerFunc { + poly.param @N : index + poly.param @M : index + poly.expr @TwiceM { + %m = poly.read_const @M : index + %two = arith.constant 2 : index + %result = arith.muli %m, %two : index + poly.yield %result : index + } + poly.expr @NPlusM { + %n = poly.read_const @N : index + %m = poly.read_const @M : index + %result = arith.addi %n, %m : index + poly.yield %result : index + } + function.def @value(%input: !array.type<@N,@M x !felt.type>) -> index { + %sum = poly.read_const @NPlusM : index + function.return %sum : index + } + } + + poly.template @OuterFunc { + poly.param @M : index + function.def @value(%input: !array.type<5,@M x !felt.type>) -> index { + %result = function.call @InnerFunc::@value(%input) : (!array.type<5,@M x !felt.type>) -> index + function.return %result : index + } + } +} + +// CHECK-LABEL: module attributes {llzk.lang} { +// CHECK-NEXT: poly.template @"InnerFunc_5_\1A" { +// CHECK-NEXT: poly.param @M : index +// CHECK-NEXT: poly.expr @NPlusM { +// CHECK-NEXT: %[[VAL_0:[0-9a-zA-Z_\.]+]] = arith.constant 5 : index +// CHECK-NEXT: %[[VAL_1:[0-9a-zA-Z_\.]+]] = poly.read_const @M : index +// CHECK-NEXT: %[[VAL_2:[0-9a-zA-Z_\.]+]] = arith.addi %[[VAL_1]], %[[VAL_0]] : index +// CHECK-NEXT: poly.yield %[[VAL_2]] : index +// CHECK-NEXT: } +// CHECK-NEXT: function.def @value(%[[VAL_3:[0-9a-zA-Z_\.]+]]: !array.type<5,@M x !felt.type>) -> index { +// CHECK-NEXT: %[[VAL_4:[0-9a-zA-Z_\.]+]] = poly.read_const @NPlusM : index +// CHECK-NEXT: function.return %[[VAL_4]] : index +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: poly.template @OuterFunc { +// CHECK-NEXT: poly.param @M : index +// CHECK-NEXT: function.def @value(%[[VAL_5:[0-9a-zA-Z_\.]+]]: !array.type<5,@M x !felt.type>) -> index { +// CHECK-NEXT: %[[VAL_6:[0-9a-zA-Z_\.]+]] = function.call @"InnerFunc_5_\1A"::@value(%[[VAL_5]]) : (!array.type<5,@M x !felt.type>) -> index +// CHECK-NEXT: function.return %[[VAL_6]] : index +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: } + +// ----- + +module attributes {llzk.lang} { + poly.template @Inner { + poly.param @Num + poly.param @Ty : !poly.tvar<@Ty> + poly.param @M : index + poly.expr @Sum { + %num = poly.read_const @Num : !poly.tvar<@Ty> + %cast = poly.unifiable_cast %num : (!poly.tvar<@Ty>) -> index + %m = poly.read_const @M : index + %sum = arith.addi %m, %cast : index + poly.yield %sum : index + } + struct.def @Value { + function.def @compute() -> !struct.type<@Inner::@Value<[@Num, @Ty, @M]>> { + %sum = poly.read_const @Sum : index + %self = struct.new : <@Inner::@Value<[@Num, @Ty, @M]>> + function.return %self : !struct.type<@Inner::@Value<[@Num, @Ty, @M]>> + } + function.def @constrain(%self: !struct.type<@Inner::@Value<[@Num, @Ty, @M]>>) { + function.return + } + } + } + + poly.template @Outer { + poly.param @M : index + struct.def @Wrapper { + struct.member @value : !struct.type<@Inner::@Value<[35, index, @M]>> + function.def @compute() -> !struct.type<@Outer::@Wrapper<[@M]>> { + %self = struct.new : <@Outer::@Wrapper<[@M]>> + function.return %self : !struct.type<@Outer::@Wrapper<[@M]>> + } + function.def @constrain(%self: !struct.type<@Outer::@Wrapper<[@M]>>) { + function.return + } + } + } +} + +// CHECK-LABEL: module attributes {llzk.lang} { +// CHECK-NEXT: poly.template @"Inner_35_i_\1A" { +// CHECK-NEXT: poly.param @M : index +// CHECK-NEXT: poly.expr @Sum { +// CHECK-NEXT: %[[VAL_0:[0-9a-zA-Z_\.]+]] = arith.constant 35 : index +// CHECK-NEXT: %[[VAL_1:[0-9a-zA-Z_\.]+]] = poly.unifiable_cast %[[VAL_0]] : (index) -> index +// CHECK-NEXT: %[[VAL_2:[0-9a-zA-Z_\.]+]] = poly.read_const @M : index +// CHECK-NEXT: %[[VAL_3:[0-9a-zA-Z_\.]+]] = arith.addi %[[VAL_2]], %[[VAL_1]] : index +// CHECK-NEXT: poly.yield %[[VAL_3]] : index +// CHECK-NEXT: } +// CHECK-NEXT: struct.def @Value { +// CHECK-NEXT: function.def @compute() -> !struct.type<@"Inner_35_i_\1A"::@Value<[@M]>> attributes {function.allow_witness} { +// CHECK-NEXT: %[[VAL_4:[0-9a-zA-Z_\.]+]] = struct.new : <@"Inner_35_i_\1A"::@Value<[@M]>> +// CHECK-NEXT: function.return %[[VAL_4]] : !struct.type<@"Inner_35_i_\1A"::@Value<[@M]>> +// CHECK-NEXT: } +// CHECK-NEXT: function.def @constrain(%[[VAL_5:[0-9a-zA-Z_\.]+]]: !struct.type<@"Inner_35_i_\1A"::@Value<[@M]>>) attributes {function.allow_constraint} { +// CHECK-NEXT: function.return +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: poly.template @Outer { +// CHECK-NEXT: poly.param @M : index +// CHECK-NEXT: struct.def @Wrapper { +// CHECK-NEXT: struct.member @value : !struct.type<@"Inner_35_i_\1A"::@Value<[@M]>> +// CHECK-NEXT: function.def @compute() -> !struct.type<@Outer::@Wrapper<[@M]>> attributes {function.allow_witness} { +// CHECK-NEXT: %[[VAL_6:[0-9a-zA-Z_\.]+]] = struct.new : <@Outer::@Wrapper<[@M]>> +// CHECK-NEXT: function.return %[[VAL_6]] : !struct.type<@Outer::@Wrapper<[@M]>> +// CHECK-NEXT: } +// CHECK-NEXT: function.def @constrain(%[[VAL_7:[0-9a-zA-Z_\.]+]]: !struct.type<@Outer::@Wrapper<[@M]>>) attributes {function.allow_constraint} { +// CHECK-NEXT: function.return +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: } + +// ----- + +module attributes {llzk.lang} { + poly.template @Inner { + poly.param @Num + poly.param @Ty : !poly.tvar<@Ty> + poly.param @M : index + poly.expr @Sum { + %num = poly.read_const @Num : !poly.tvar<@Ty> + %cast = poly.unifiable_cast %num : (!poly.tvar<@Ty>) -> index + %m = poly.read_const @M : index + %sum = arith.addi %m, %cast : index + poly.yield %sum : index + } + function.def @value() -> index { + %sum = poly.read_const @Sum : index + function.return %sum : index + } + } + + poly.template @Outer { + poly.param @M : index + function.def @value() -> index { + %sum = function.call @Inner::@value<[35, index, @M]>() : () -> index + function.return %sum : index + } + } +} + +// CHECK-LABEL: module attributes {llzk.lang} { +// CHECK-NEXT: poly.template @"Inner_35_i_\1A" { +// CHECK-NEXT: poly.param @M : index +// CHECK-NEXT: poly.expr @Sum { +// CHECK-NEXT: %[[VAL_0:[0-9a-zA-Z_\.]+]] = arith.constant 35 : index +// CHECK-NEXT: %[[VAL_1:[0-9a-zA-Z_\.]+]] = poly.unifiable_cast %[[VAL_0]] : (index) -> index +// CHECK-NEXT: %[[VAL_2:[0-9a-zA-Z_\.]+]] = poly.read_const @M : index +// CHECK-NEXT: %[[VAL_3:[0-9a-zA-Z_\.]+]] = arith.addi %[[VAL_2]], %[[VAL_1]] : index +// CHECK-NEXT: poly.yield %[[VAL_3]] : index +// CHECK-NEXT: } +// CHECK-NEXT: function.def @value() -> index { +// CHECK-NEXT: %[[VAL_4:[0-9a-zA-Z_\.]+]] = poly.read_const @Sum : index +// CHECK-NEXT: function.return %[[VAL_4]] : index +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: poly.template @Outer { +// CHECK-NEXT: poly.param @M : index +// CHECK-NEXT: function.def @value() -> index { +// CHECK-NEXT: %[[VAL_5:[0-9a-zA-Z_\.]+]] = function.call @"Inner_35_i_\1A"::@value<[@M]>() : () -> index +// CHECK-NEXT: function.return %[[VAL_5]] : index +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: } diff --git a/test/Transforms/Flattening/instantiate_expr_target_local.llzk b/test/Transforms/Flattening/instantiate_expr_target_local.llzk new file mode 100644 index 0000000000..b4c8479bfc --- /dev/null +++ b/test/Transforms/Flattening/instantiate_expr_target_local.llzk @@ -0,0 +1,131 @@ +// RUN: llzk-opt -split-input-file --pass-pipeline='builtin.module(llzk-flatten{cleanup=disabled})' %s | FileCheck %s + +module attributes {llzk.lang, llzk.main = !struct.type<@UnusedStructExpr::@Value<[5]>>} { + poly.template @UnusedStructExpr { + poly.param @N : index + poly.expr @Unused { + %value = scf.execute_region -> index { + %n = poly.read_const @N : index + scf.yield %n : index + } + poly.yield %value : index + } + function.def @other() -> index { + %value = poly.read_const @Unused : index + function.return %value : index + } + struct.def @Value { + function.def @compute() -> !struct.type<@UnusedStructExpr::@Value<[@N]>> { + %self = struct.new : <@UnusedStructExpr::@Value<[@N]>> + function.return %self : !struct.type<@UnusedStructExpr::@Value<[@N]>> + } + function.def @constrain(%self: !struct.type<@UnusedStructExpr::@Value<[@N]>>) { + function.return + } + } + } +} + +// CHECK-LABEL: module attributes {llzk.lang, llzk.main = !struct.type<@UnusedStructExpr_5_Value>} { +// CHECK-NEXT: struct.def @UnusedStructExpr_5_Value { +// CHECK-NEXT: function.def @compute() -> !struct.type<@UnusedStructExpr_5_Value> attributes {function.allow_witness} { +// CHECK-NEXT: %[[VAL_0:[0-9a-zA-Z_\.]+]] = struct.new : <@UnusedStructExpr_5_Value> +// CHECK-NEXT: function.return %[[VAL_0]] : !struct.type<@UnusedStructExpr_5_Value> +// CHECK-NEXT: } +// CHECK-NEXT: function.def @constrain(%[[VAL_1:[0-9a-zA-Z_\.]+]]: !struct.type<@UnusedStructExpr_5_Value>) attributes {function.allow_constraint} { +// CHECK-NEXT: function.return +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: poly.template @UnusedStructExpr { +// CHECK-NEXT: poly.param @N : index +// CHECK-NEXT: poly.expr @Unused { +// CHECK-NEXT: %[[VAL_2:[0-9a-zA-Z_\.]+]] = scf.execute_region -> index { +// CHECK-NEXT: %[[VAL_3:[0-9a-zA-Z_\.]+]] = poly.read_const @N : index +// CHECK-NEXT: scf.yield %[[VAL_3]] : index +// CHECK-NEXT: } +// CHECK-NEXT: poly.yield %[[VAL_2]] : index +// CHECK-NEXT: } +// CHECK-NEXT: function.def @other() -> index { +// CHECK-NEXT: %[[VAL_4:[0-9a-zA-Z_\.]+]] = poly.read_const @Unused : index +// CHECK-NEXT: function.return %[[VAL_4]] : index +// CHECK-NEXT: } +// CHECK-NEXT: struct.def @Value { +// CHECK-NEXT: function.def @compute() -> !struct.type<@UnusedStructExpr::@Value<[@N]>> attributes {function.allow_witness} { +// CHECK-NEXT: %[[VAL_5:[0-9a-zA-Z_\.]+]] = struct.new : <@UnusedStructExpr::@Value<[@N]>> +// CHECK-NEXT: function.return %[[VAL_5]] : !struct.type<@UnusedStructExpr::@Value<[@N]>> +// CHECK-NEXT: } +// CHECK-NEXT: function.def @constrain(%[[VAL_6:[0-9a-zA-Z_\.]+]]: !struct.type<@UnusedStructExpr::@Value<[@N]>>) attributes {function.allow_constraint} { +// CHECK-NEXT: function.return +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: } + +// ----- + +module attributes {llzk.lang} { + poly.template @UnusedFunctionExpr { + poly.param @N : index + poly.expr @Unused { + %value = scf.execute_region -> index { + %n = poly.read_const @N : index + scf.yield %n : index + } + poly.yield %value : index + } + function.def @other() -> index { + %value = poly.read_const @Unused : index + function.return %value : index + } + function.def @value() -> index { + %result = arith.constant 1 : index + function.return %result : index + } + } + + struct.def @Main { + function.def @compute() -> !struct.type<@Main> { + %self = struct.new : <@Main> + %result = function.call @UnusedFunctionExpr::@value<[5]>() : () -> index + function.return %self : !struct.type<@Main> + } + function.def @constrain(%self: !struct.type<@Main>) { + function.return + } + } +} + +// CHECK-LABEL: module attributes {llzk.lang} { +// CHECK-NEXT: function.def @UnusedFunctionExpr_5_value() -> index { +// CHECK-NEXT: %[[VAL_0:[0-9a-zA-Z_\.]+]] = arith.constant 1 : index +// CHECK-NEXT: function.return %[[VAL_0]] : index +// CHECK-NEXT: } +// CHECK-NEXT: poly.template @UnusedFunctionExpr { +// CHECK-NEXT: poly.param @N : index +// CHECK-NEXT: poly.expr @Unused { +// CHECK-NEXT: %[[VAL_1:[0-9a-zA-Z_\.]+]] = scf.execute_region -> index { +// CHECK-NEXT: %[[VAL_2:[0-9a-zA-Z_\.]+]] = poly.read_const @N : index +// CHECK-NEXT: scf.yield %[[VAL_2]] : index +// CHECK-NEXT: } +// CHECK-NEXT: poly.yield %[[VAL_1]] : index +// CHECK-NEXT: } +// CHECK-NEXT: function.def @other() -> index { +// CHECK-NEXT: %[[VAL_3:[0-9a-zA-Z_\.]+]] = poly.read_const @Unused : index +// CHECK-NEXT: function.return %[[VAL_3]] : index +// CHECK-NEXT: } +// CHECK-NEXT: function.def @value() -> index { +// CHECK-NEXT: %[[VAL_4:[0-9a-zA-Z_\.]+]] = arith.constant 1 : index +// CHECK-NEXT: function.return %[[VAL_4]] : index +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: struct.def @Main { +// CHECK-NEXT: function.def @compute() -> !struct.type<@Main> attributes {function.allow_witness} { +// CHECK-NEXT: %[[VAL_5:[0-9a-zA-Z_\.]+]] = struct.new : <@Main> +// CHECK-NEXT: %[[VAL_6:[0-9a-zA-Z_\.]+]] = function.call @UnusedFunctionExpr_5_value() : () -> index +// CHECK-NEXT: function.return %[[VAL_5]] : !struct.type<@Main> +// CHECK-NEXT: } +// CHECK-NEXT: function.def @constrain(%[[VAL_7:[0-9a-zA-Z_\.]+]]: !struct.type<@Main>) attributes {function.allow_constraint} { +// CHECK-NEXT: function.return +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: }