LLZK 0.1.0
Veridise's ZK Language IR
Loading...
Searching...
No Matches
SymbolHelper.cpp
Go to the documentation of this file.
1//===-- SymbolHelper.cpp - LLZK Symbol Helpers ------------------*- C++ -*-===//
2//
3// Part of the LLZK Project, under the Apache License v2.0.
4// See LICENSE.txt for license information.
5// Copyright 2025 Veridise Inc.
6// SPDX-License-Identifier: Apache-2.0
7//
8//===----------------------------------------------------------------------===//
13//===----------------------------------------------------------------------===//
14
21
22#include <mlir/IR/BuiltinOps.h>
23#include <mlir/IR/Operation.h>
24
25#include <llvm/Support/Debug.h>
26
27#define DEBUG_TYPE "llzk-symbol-helpers"
28
29using namespace mlir;
30
31namespace llzk {
32
33using namespace array;
34using namespace component;
35using namespace function;
36using namespace global;
37using namespace polymorphic;
38
39namespace {
40
41enum RootSelector { CLOSEST, FURTHEST };
42
48FailureOr<ModuleOp> collectPathToRoot(
49 Operation *from, Operation *origin, std::vector<FlatSymbolRefAttr> &path, RootSelector whichRoot
50) {
51 Operation *check = from;
52 ModuleOp currRoot = nullptr;
53 do {
54 if (ModuleOp m = llvm::dyn_cast_if_present<ModuleOp>(check)) {
55 // We need this attribute restriction because some stages of parsing have
56 // an extra module wrapping the top-level module from the input file.
57 // This module, even if it has a name, does not contribute to path names.
58 if (m->hasAttr(LANG_ATTR_NAME)) {
59 if (whichRoot == RootSelector::CLOSEST) {
60 return m;
61 }
62 currRoot = m;
63 }
64 if (StringAttr modName = m.getSymNameAttr()) {
65 path.push_back(FlatSymbolRefAttr::get(modName));
66 } else if (!currRoot) {
67 return origin->emitOpError()
68 .append(
69 "has ancestor '", ModuleOp::getOperationName(), "' without \"", LANG_ATTR_NAME,
70 "\" attribute or a name"
71 )
72 .attachNote(m.getLoc())
73 .append("unnamed '", ModuleOp::getOperationName(), "' here");
74 }
75 }
76 } while ((check = check->getParentOp()));
77
78 if (whichRoot == RootSelector::FURTHEST && currRoot) {
79 return currRoot;
80 }
81
82 return origin->emitOpError().append(
83 "has no ancestor '", ModuleOp::getOperationName(), "' with \"", LANG_ATTR_NAME, "\" attribute"
84 );
85}
86
89FailureOr<SymbolRefAttr> buildPathFromRoot(
90 Operation *position, Operation *origin, std::vector<FlatSymbolRefAttr> &&path,
91 RootSelector whichRoot
92) {
93 // Collect the rest of the path to the root module
94 if (failed(collectPathToRoot(position, origin, path, whichRoot))) {
95 return failure();
96 }
97 // Reverse the vector and convert it to a SymbolRefAttr
98 std::vector<FlatSymbolRefAttr> reversedVec(path.rbegin(), path.rend());
99 return asSymbolRefAttr(reversedVec);
100}
101
104FailureOr<SymbolRefAttr> buildPathFromRoot(
105 StructDefOp &to, Operation *origin, std::vector<FlatSymbolRefAttr> &&path,
106 RootSelector whichRoot
107) {
108 // Add the name of the struct (its name is not optional) and then delegate to helper
109 path.push_back(FlatSymbolRefAttr::get(to.getSymNameAttr()));
110 return buildPathFromRoot(to.getOperation(), origin, std::move(path), whichRoot);
111}
112
113FailureOr<SymbolRefAttr> getPathFromRoot(StructDefOp &to, RootSelector whichRoot) {
114 std::vector<FlatSymbolRefAttr> path;
115 return buildPathFromRoot(to, to.getOperation(), std::move(path), whichRoot);
116}
117
118FailureOr<SymbolRefAttr> getPathFromRoot(FuncDefOp &to, RootSelector whichRoot) {
119 std::vector<FlatSymbolRefAttr> path;
120 // Add the name of the function (its name is not optional)
121 path.push_back(FlatSymbolRefAttr::get(to.getSymNameAttr()));
122
123 // Delegate based on the type of the parent op
124 Operation *current = to.getOperation();
125 Operation *parent = current->getParentOp();
126 if (StructDefOp parentStruct = llvm::dyn_cast_if_present<StructDefOp>(parent)) {
127 return buildPathFromRoot(parentStruct, current, std::move(path), whichRoot);
128 } else if (ModuleOp parentMod = llvm::dyn_cast_if_present<ModuleOp>(parent)) {
129 return buildPathFromRoot(parentMod.getOperation(), current, std::move(path), whichRoot);
130 } else {
131 // This is an error in the compiler itself. In current implementation,
132 // FuncDefOp must have either StructDefOp or ModuleOp as its parent.
133 return current->emitError().append("orphaned '", FuncDefOp::getOperationName(), "'");
134 }
135}
136} // namespace
137
138llvm::SmallVector<StringRef> getNames(SymbolRefAttr ref) {
139 llvm::SmallVector<StringRef> names;
140 names.push_back(ref.getRootReference().getValue());
141 for (const FlatSymbolRefAttr &r : ref.getNestedReferences()) {
142 names.push_back(r.getValue());
143 }
144 return names;
145}
146
147llvm::SmallVector<FlatSymbolRefAttr> getPieces(SymbolRefAttr ref) {
148 llvm::SmallVector<FlatSymbolRefAttr> pieces;
149 pieces.push_back(FlatSymbolRefAttr::get(ref.getRootReference()));
150 for (const FlatSymbolRefAttr &r : ref.getNestedReferences()) {
151 pieces.push_back(r);
152 }
153 return pieces;
154}
155
156namespace {
157
158SymbolRefAttr changeLeafImpl(
159 StringAttr origRoot, ArrayRef<FlatSymbolRefAttr> origTail, FlatSymbolRefAttr newLeaf,
160 size_t drop = 1
161) {
162 llvm::SmallVector<FlatSymbolRefAttr> newTail;
163 newTail.append(origTail.begin(), origTail.drop_back(drop).end());
164 newTail.push_back(newLeaf);
165 return SymbolRefAttr::get(origRoot, newTail);
166}
167
168} // namespace
169
170SymbolRefAttr replaceLeaf(SymbolRefAttr orig, FlatSymbolRefAttr newLeaf) {
171 ArrayRef<FlatSymbolRefAttr> origTail = orig.getNestedReferences();
172 if (origTail.empty()) {
173 // If there is no tail, the root is the leaf so replace the whole thing
174 return newLeaf;
175 } else {
176 return changeLeafImpl(orig.getRootReference(), origTail, newLeaf);
177 }
178}
179
180SymbolRefAttr appendLeaf(SymbolRefAttr orig, FlatSymbolRefAttr newLeaf) {
181 return changeLeafImpl(orig.getRootReference(), orig.getNestedReferences(), newLeaf, 0);
182}
183
184SymbolRefAttr appendLeafName(SymbolRefAttr orig, const Twine &newLeafSuffix) {
185 ArrayRef<FlatSymbolRefAttr> origTail = orig.getNestedReferences();
186 if (origTail.empty()) {
187 // If there is no tail, the root is the leaf so append on the root instead
189 orig.getContext(), orig.getRootReference().getValue() + newLeafSuffix
190 );
191 } else {
192 return changeLeafImpl(
193 orig.getRootReference(), origTail,
194 getFlatSymbolRefAttr(orig.getContext(), origTail.back().getValue() + newLeafSuffix)
195 );
196 }
197}
198
199FailureOr<ModuleOp> getRootModule(Operation *from) {
200 std::vector<FlatSymbolRefAttr> path;
201 return collectPathToRoot(from, from, path, RootSelector::CLOSEST);
202}
203
204FailureOr<SymbolRefAttr> getPathFromRoot(StructDefOp &to) {
205 return getPathFromRoot(to, RootSelector::CLOSEST);
206}
207
208FailureOr<SymbolRefAttr> getPathFromRoot(FuncDefOp &to) {
209 return getPathFromRoot(to, RootSelector::CLOSEST);
210}
211
212FailureOr<ModuleOp> getTopRootModule(Operation *from) {
213 std::vector<FlatSymbolRefAttr> path;
214 return collectPathToRoot(from, from, path, RootSelector::FURTHEST);
215}
216
217FailureOr<SymbolRefAttr> getPathFromTopRoot(StructDefOp &to) {
218 return getPathFromRoot(to, RootSelector::FURTHEST);
219}
220
221FailureOr<SymbolRefAttr> getPathFromTopRoot(FuncDefOp &to) {
222 return getPathFromRoot(to, RootSelector::FURTHEST);
223}
224
225bool hasUsesWithin(Operation *symbol, Operation *from) {
226 assert(symbol && "pre-condition");
227 assert(from && "pre-condition");
228 bool result = false;
229 SymbolTable::walkSymbolTables(from, false, [symbol, &result](Operation *symbolTableOp, bool) {
230 assert(symbolTableOp->hasTrait<OpTrait::SymbolTable>());
231 bool hasUse = (symbol != symbolTableOp) &&
232 !SymbolTable::symbolKnownUseEmpty(symbol, &symbolTableOp->getRegion(0));
233 result |= hasUse;
234 LLVM_DEBUG({
235 if (hasUse) {
236 auto uses = SymbolTable::getSymbolUses(symbol, &symbolTableOp->getRegion(0));
237 assert(uses.has_value()); // must be consistent with symbolKnownUseEmpty()
238 llvm::dbgs() << "Found users of " << *symbol << "\n";
239 for (SymbolTable::SymbolUse user : uses.value()) {
240 llvm::dbgs() << " * " << *user.getUser() << "\n";
241 }
242 }
243 });
244 });
245 return result;
246}
247
248LogicalResult verifyParamOfType(
249 SymbolTableCollection &tables, SymbolRefAttr param, Type parameterizedType, Operation *origin
250) {
251 // Most often, StructType and ArrayType SymbolRefAttr parameters will be defined as parameters of
252 // the StructDefOp that the current Operation is nested within. These are always flat references
253 // (i.e. contain no nested references).
254 if (param.getNestedReferences().empty()) {
255 FailureOr<StructDefOp> getParentRes = getParentOfType<StructDefOp>(origin);
256 if (succeeded(getParentRes)) {
257 if (getParentRes->hasParamNamed(param.getRootReference())) {
258 return success();
259 }
260 }
261 }
262 // Otherwise, see if the symbol can be found via lookup from the `origin` Operation.
263 auto lookupRes = lookupTopLevelSymbol(tables, param, origin);
264 if (failed(lookupRes)) {
265 return failure(); // lookupTopLevelSymbol() already emits a sufficient error message
266 }
267 Operation *foundOp = lookupRes->get();
268 if (!llvm::isa<GlobalDefOp>(foundOp)) {
269 return origin->emitError() << "ref \"" << param << "\" in type " << parameterizedType
270 << " refers to a '" << foundOp->getName()
271 << "' which is not allowed";
272 }
273 return success();
274}
275
276LogicalResult verifyParamsOfType(
277 SymbolTableCollection &tables, ArrayRef<Attribute> tyParams, Type parameterizedType,
278 Operation *origin
279) {
280 // Rather than immediately returning on failure, we check all params and aggregate to provide as
281 // many errors are possible in a single verifier run.
282 LogicalResult paramCheckResult = success();
283 for (Attribute attr : tyParams) {
285 if (SymbolRefAttr symRefParam = llvm::dyn_cast<SymbolRefAttr>(attr)) {
286 if (failed(verifyParamOfType(tables, symRefParam, parameterizedType, origin))) {
287 paramCheckResult = failure();
288 }
289 } else if (TypeAttr typeParam = llvm::dyn_cast<TypeAttr>(attr)) {
290 if (failed(verifyTypeResolution(tables, origin, typeParam.getValue()))) {
291 paramCheckResult = failure();
292 }
293 }
294 // IntegerAttr and AffineMapAttr cannot contain symbol references
295 }
296 return paramCheckResult;
297}
298
299FailureOr<StructDefOp>
300verifyStructTypeResolution(SymbolTableCollection &tables, StructType ty, Operation *origin) {
301 auto res = ty.getDefinition(tables, origin);
302 if (failed(res)) {
303 return failure();
304 }
305 StructDefOp defForType = res.value().get();
306 if (!structTypesUnify(ty, defForType.getType({}), res->getIncludeSymNames())) {
307 return origin->emitError()
308 .append(
309 "Cannot unify parameters of type ", ty, " with parameters of '",
310 StructDefOp::getOperationName(), "' \"", defForType.getHeaderString(), "\""
311 )
312 .attachNote(defForType.getLoc())
313 .append("type parameters must unify with parameters defined here");
314 }
315 // If there are any SymbolRefAttr parameters on the StructType, ensure those refs are valid.
316 if (ArrayAttr tyParams = ty.getParams()) {
317 if (failed(verifyParamsOfType(tables, tyParams.getValue(), ty, origin))) {
318 return failure(); // verifyParamsOfType() already emits a sufficient error message
319 }
320 }
321 return defForType;
322}
323
324LogicalResult verifyTypeResolution(SymbolTableCollection &tables, Operation *origin, Type ty) {
325 if (StructType sTy = llvm::dyn_cast<StructType>(ty)) {
326 return verifyStructTypeResolution(tables, sTy, origin);
327 } else if (ArrayType aTy = llvm::dyn_cast<ArrayType>(ty)) {
328 if (failed(verifyParamsOfType(tables, aTy.getDimensionSizes(), aTy, origin))) {
329 return failure();
330 }
331 return verifyTypeResolution(tables, origin, aTy.getElementType());
332 } else if (TypeVarType vTy = llvm::dyn_cast<TypeVarType>(ty)) {
333 return verifyParamOfType(tables, vTy.getNameRef(), vTy, origin);
334 } else {
335 return success();
336 }
337}
338
339} // namespace llzk
within a display generated by the Derivative if and wherever such third party notices normally appear The contents of the NOTICE file are for informational purposes only and do not modify the License You may add Your own attribution notices within Derivative Works that You alongside or as an addendum to the NOTICE text from the provided that such additional attribution notices cannot be construed as modifying the License You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for or distribution of Your or for any such Derivative Works as a provided Your and distribution of the Work otherwise complies with the conditions stated in this License Submission of Contributions Unless You explicitly state any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this without any additional terms or conditions Notwithstanding the nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions Trademarks This License does not grant permission to use the trade names
Definition LICENSE.txt:139
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for and distribution as defined by Sections through of this document Licensor shall mean the copyright owner or entity authorized by the copyright owner that is granting the License Legal Entity shall mean the union of the acting entity and all other entities that control are controlled by or are under common control with that entity For the purposes of this definition control direct or to cause the direction or management of such whether by contract or including but not limited to software source documentation and configuration files Object form shall mean any form resulting from mechanical transformation or translation of a Source including but not limited to compiled object generated and conversions to other media types Work shall mean the work of whether in Source or Object made available under the as indicated by a copyright notice that is included in or attached to the whether in Source or Object that is based or other modifications as a an original work of authorship For the purposes of this Derivative Works shall not include works that remain separable from
Definition LICENSE.txt:45
This file defines methods symbol lookup across LLZK operations and included files.
StructType getType(::std::optional<::mlir::ArrayAttr > constParams={})
Gets the StructType representing this struct.
Definition Ops.cpp:142
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:919
::std::string getHeaderString()
Generate header string, in the same format as the assemblyFormat.
Definition Ops.cpp:148
::mlir::ArrayAttr getParams() const
::mlir::FailureOr< SymbolLookupResult< StructDefOp > > getDefinition(::mlir::SymbolTableCollection &symbolTable, ::mlir::Operation *op) const
Gets the struct op that defines this struct.
Definition Types.cpp:39
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:478
void assertValidAttrForParamOfType(Attribute attr)
FailureOr< SymbolRefAttr > getPathFromRoot(StructDefOp &to)
SymbolRefAttr appendLeafName(SymbolRefAttr orig, const Twine &newLeafSuffix)
constexpr char LANG_ATTR_NAME[]
Name of the attribute on the top-level ModuleOp that specifies the IR language name.
Definition Constants.h:31
bool hasUsesWithin(Operation *symbol, Operation *from)
mlir::FlatSymbolRefAttr getFlatSymbolRefAttr(mlir::MLIRContext *context, const mlir::Twine &twine)
Construct a FlatSymbolRefAttr with the given content.
mlir::FailureOr< SymbolLookupResultUntyped > lookupTopLevelSymbol(mlir::SymbolTableCollection &tables, mlir::SymbolRefAttr symbol, mlir::Operation *origin, bool reportMissing=true)
llvm::SmallVector< StringRef > getNames(SymbolRefAttr ref)
bool structTypesUnify(StructType lhs, StructType rhs, ArrayRef< StringRef > rhsReversePrefix, UnificationMap *unifications)
FailureOr< ModuleOp > getRootModule(Operation *from)
SymbolRefAttr appendLeaf(SymbolRefAttr orig, FlatSymbolRefAttr newLeaf)
SymbolRefAttr replaceLeaf(SymbolRefAttr orig, FlatSymbolRefAttr newLeaf)
FailureOr< StructDefOp > verifyStructTypeResolution(SymbolTableCollection &tables, StructType ty, Operation *origin)
FailureOr< SymbolRefAttr > getPathFromTopRoot(StructDefOp &to)
FailureOr< ModuleOp > getTopRootModule(Operation *from)
LogicalResult verifyTypeResolution(SymbolTableCollection &tables, Operation *origin, Type ty)
LogicalResult verifyParamsOfType(SymbolTableCollection &tables, ArrayRef< Attribute > tyParams, Type parameterizedType, Operation *origin)
mlir::SymbolRefAttr asSymbolRefAttr(mlir::StringAttr root, mlir::SymbolRefAttr tail)
Build a SymbolRefAttr that prepends tail with root, i.e. root::tail.
mlir::FailureOr< OpClass > getParentOfType(mlir::Operation *op)
Return the closest surrounding parent operation that is of type 'OpClass'.
Definition OpHelpers.h:32
LogicalResult verifyParamOfType(SymbolTableCollection &tables, SymbolRefAttr param, Type parameterizedType, Operation *origin)
llvm::SmallVector< FlatSymbolRefAttr > getPieces(SymbolRefAttr ref)