LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
ConstGlobalPropagationPass.cpp
Go to the documentation of this file.
1//===-- ConstGlobalPropagationPass.cpp --------------------------*- C++ -*-===//
2//
3// Part of the LLZK Project, under the Apache License v2.0.
4// See LICENSE.txt for license information.
5// Copyright 2026 Project LLZK
6// SPDX-License-Identifier: Apache-2.0
7//
8//===----------------------------------------------------------------------===//
13//===----------------------------------------------------------------------===//
14
24#include "llzk/Util/Walk.h"
26#include <mlir/Dialect/Arith/IR/Arith.h>
28#include <llvm/ADT/DenseMap.h>
29#include <llvm/ADT/SmallVector.h>
31#include <optional>
33// Include the generated base pass class definitions.
34namespace llzk::global {
35#define GEN_PASS_DEF_CONSTGLOBALPROPAGATIONPASS
37} // namespace llzk::global
38
39#define DEBUG_TYPE "llzk-const-global-propagation"
41using namespace mlir;
42using namespace llzk;
43using namespace llzk::global;
44
45namespace {
46
47static Value convertToConstantValue(GlobalReadOp readOp, Attribute attr) {
48 OpBuilder bldr(readOp);
49 Location loc = readOp.getLoc();
50 if (auto intAttr = llvm::dyn_cast<IntegerAttr>(attr)) {
51 return arith::ConstantOp::create(bldr, loc, intAttr).getResult();
52 } else if (auto feltAttr = llvm::dyn_cast<felt::FeltConstAttr>(attr)) {
53 Value constant = felt::FeltConstantOp::create(bldr, loc, feltAttr).getResult();
54 if (constant.getType() == readOp.getType()) {
55 return constant;
56 }
57 return polymorphic::UnifiableCastOp::create(bldr, loc, readOp.getType(), constant).getResult();
58 } else if (auto strAttr = llvm::dyn_cast<StringAttr>(attr)) {
59 return string::LitStringOp::create(bldr, loc, readOp.getType(), strAttr).getResult();
60 } else {
61 llvm::outs() << "Encountered: " << attr.getAbstractAttribute().getName() << '\n';
62 llvm_unreachable("Unsupported constant attribute type");
63 }
64}
65
66class PassImpl : public llzk::global::impl::ConstGlobalPropagationPassBase<PassImpl> {
67 using Base = ConstGlobalPropagationPassBase<PassImpl>;
68
69public:
70 using Base::Base;
71
72 void runOnOperation() override {
73 ModuleOp root = getOperation();
74 auto constGlobals = walkCollect<GlobalDefOp>(root, [](auto g) { return g.isConstant(); });
75 if (constGlobals.empty()) {
76 return;
77 }
78
79 // Keep track of erased operations to skip them during the inner loop iteration and avoid
80 // use-after-free errors. These MUST NOT be dereferenced because the ops have been erased.
81 llvm::DenseSet<void *> erased;
82 SymbolUseGraph &useGraph = getAnalysis<SymbolUseGraph>();
83 llvm::DenseMap<Operation *, SmallVector<const SymbolUseGraphNode *>> useNodesByDefinition;
84 SymbolTableCollection tables;
85 for (const SymbolUseGraphNode *candidate : useGraph.nodesIter()) {
86 auto target = candidate->lookupSymbol(tables, /*reportMissing=*/false);
87 if (succeeded(target)) {
88 useNodesByDefinition[target->get()].push_back(candidate);
89 }
90 }
92 for (GlobalDefOp globalDef : constGlobals) {
93 Attribute constValue = globalDef.getInitialValueAttr();
94
95 // Array constants cannot yet be materialized as operations. Leave both
96 // the global and all of its uses unchanged until they are supported.
97 if (llvm::isa<ArrayAttr>(constValue)) {
98 continue;
99 }
100
101 // A global definition and its uses can have different closest LLZK root
102 // modules after include inlining. Find every use-graph node that resolves
103 // to this definition instead of looking it up by a single symbol path.
104 for (const SymbolUseGraphNode *node : useNodesByDefinition[globalDef.getOperation()]) {
105 SymbolRefAttr symbolAttr = node->getSymbolPath();
106 for (Operation *userOp : node->getUserOps()) {
107 // Skip erased ops to avoid use-after-free errors
108 if (erased.contains(userOp)) {
109 continue;
110 }
111 LLVM_DEBUG(
112 llvm::dbgs() << "Replacing '" << symbolAttr << "' with '" << constValue << "' in "
113 << *userOp << '\n'
114 );
115
116 // Special handling to fully replace GlobalReadOp with the appropriate constant op.
117 if (auto readOp = llvm::dyn_cast<GlobalReadOp>(userOp);
118 readOp && readOp.getNameRef() == symbolAttr) {
119 Value constantResult = convertToConstantValue(readOp, constValue);
120 readOp.getResult().replaceAllUsesWith(constantResult);
121 readOp.erase();
122 erased.insert(readOp);
123 continue;
124 }
125
126 // Standard case that just replaces all uses of the symbol with the constant value.
127 AttrTypeReplacer replacer;
128 replacer.addReplacement([symbolAttr, constValue](SymbolRefAttr a) {
129 return (a == symbolAttr) ? std::make_optional(constValue) : std::nullopt;
130 });
131 replacer.replaceElementsIn(
132 userOp,
133 /*replaceAttrs=*/true,
134 /*replaceLocs=*/false,
135 /*replaceTypes=*/true
136 );
137 }
138 }
139 // Delete the const global since there are no (remaining) uses.
140 globalDef.erase();
141 erased.insert(globalDef);
142 }
143 }
144};
145
146} // namespace
Builds a graph structure representing the relationships between symbols and their uses.
llvm::iterator_range< iterator > nodesIter() const
Range over all nodes in the graph.
::mlir::TypedValue<::mlir::Type > getResult()
Definition Ops.h.inc:1017
static FeltConstantOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::Type result, ::llzk::felt::FeltConstAttr value)
Definition Ops.cpp.inc:1274
::mlir::SymbolRefAttr getNameRef()
Definition Ops.cpp.inc:875
::mlir::TypedValue<::mlir::Type > getResult()
Definition Ops.h.inc:1557
static UnifiableCastOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::Type result, ::mlir::Value input)
Definition Ops.cpp.inc:1908
static LitStringOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::Type result, ::mlir::StringAttr value)
Definition Ops.cpp.inc:174
::mlir::TypedValue<::llzk::string::StringType > getResult()
Definition Ops.h.inc:169