Skip to content

Commit acf964b

Browse files
authored
[mlir][SMT] add export smtlib (llvm#131492)
This PR adds the `ExportSMTLIB` translation/egress pass for `SMT` dialect.
1 parent 5bdad05 commit acf964b

15 files changed

+1822
-0
lines changed

mlir/include/mlir/InitAllTranslations.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ void registerToCppTranslation();
2222
void registerToLLVMIRTranslation();
2323
void registerToSPIRVTranslation();
2424

25+
namespace smt {
26+
void registerExportSMTLIBTranslation();
27+
}
28+
2529
// This function should be called before creating any MLIRContext if one
2630
// expects all the possible translations to be made available to the context
2731
// automatically.
@@ -32,6 +36,7 @@ inline void registerAllTranslations() {
3236
registerToCppTranslation();
3337
registerToLLVMIRTranslation();
3438
registerToSPIRVTranslation();
39+
smt::registerExportSMTLIBTranslation();
3540
return true;
3641
}();
3742
(void)initOnce;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//===- ExportSMTLIB.h - SMT-LIB Exporter ------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// Defines the interface to the SMT-LIB emitter.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#ifndef MLIR_TARGET_EXPORTSMTLIB_H
14+
#define MLIR_TARGET_EXPORTSMTLIB_H
15+
16+
#include "mlir/Support/LLVM.h"
17+
18+
namespace mlir {
19+
class Operation;
20+
namespace smt {
21+
22+
/// Emission options for the ExportSMTLIB pass. Allows controlling the emitted
23+
/// format and overall behavior.
24+
struct SMTEmissionOptions {
25+
// Don't produce 'let' expressions to bind expressions that are only used
26+
// once, but inline them directly at the use-site.
27+
bool inlineSingleUseValues = false;
28+
// Increase indentation for each 'let' expression body.
29+
bool indentLetBody = false;
30+
};
31+
32+
/// Run the ExportSMTLIB pass.
33+
LogicalResult
34+
exportSMTLIB(Operation *module, llvm::raw_ostream &os,
35+
const SMTEmissionOptions &options = SMTEmissionOptions());
36+
37+
/// Register the ExportSMTLIB pass.
38+
void registerExportSMTLIBTranslation();
39+
40+
} // namespace smt
41+
} // namespace mlir
42+
43+
#endif // MLIR_TARGET_EXPORTSMTLIB_H
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
//===- Namespace.h - Utilities for generating names -------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file provides utilities for generating new names that do not conflict
10+
// with existing names.
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#ifndef MLIR_SUPPORT_NAMESPACE_H
15+
#define MLIR_SUPPORT_NAMESPACE_H
16+
17+
#include "mlir/IR/BuiltinOps.h"
18+
#include "mlir/Target/SMTLIB/SymCache.h"
19+
#include "llvm/ADT/SmallString.h"
20+
#include "llvm/ADT/StringSet.h"
21+
#include "llvm/ADT/Twine.h"
22+
23+
namespace mlir {
24+
25+
/// A namespace that is used to store existing names and generate new names in
26+
/// some scope within the IR. This exists to work around limitations of
27+
/// SymbolTables. This acts as a base class providing facilities common to all
28+
/// namespaces implementations.
29+
class Namespace {
30+
public:
31+
Namespace() {
32+
// This fills an entry for an empty string beforehand so that `newName`
33+
// doesn't return an empty string.
34+
nextIndex.insert({"", 0});
35+
}
36+
Namespace(const Namespace &other) = default;
37+
Namespace(Namespace &&other)
38+
: nextIndex(std::move(other.nextIndex)), locked(other.locked) {}
39+
40+
Namespace &operator=(const Namespace &other) = default;
41+
Namespace &operator=(Namespace &&other) {
42+
nextIndex = std::move(other.nextIndex);
43+
locked = other.locked;
44+
return *this;
45+
}
46+
47+
void add(mlir::ModuleOp module) {
48+
assert(module->getNumRegions() == 1);
49+
for (auto &op : module.getBody(0)->getOperations())
50+
if (auto symbol = op.getAttrOfType<mlir::StringAttr>(
51+
mlir::SymbolTable::getSymbolAttrName()))
52+
nextIndex.insert({symbol.getValue(), 0});
53+
}
54+
55+
/// SymbolCache initializer; initialize from every key that is convertible to
56+
/// a StringAttr in the SymbolCache.
57+
void add(SymbolCache &symCache) {
58+
for (auto &&[attr, _] : symCache)
59+
if (auto strAttr = dyn_cast<mlir::StringAttr>(attr))
60+
nextIndex.insert({strAttr.getValue(), 0});
61+
}
62+
63+
void add(llvm::StringRef name) { nextIndex.insert({name, 0}); }
64+
65+
/// Removes a symbol from the namespace. Returns true if the symbol was
66+
/// removed, false if the symbol was not found.
67+
/// This is only allowed to be called _before_ any call to newName.
68+
bool erase(llvm::StringRef symbol) {
69+
assert(!locked && "Cannot erase names from a locked namespace");
70+
return nextIndex.erase(symbol);
71+
}
72+
73+
/// Empty the namespace.
74+
void clear() {
75+
nextIndex.clear();
76+
locked = false;
77+
}
78+
79+
/// Return a unique name, derived from the input `name`, and add the new name
80+
/// to the internal namespace. There are two possible outcomes for the
81+
/// returned name:
82+
///
83+
/// 1. The original name is returned.
84+
/// 2. The name is given a `_<n>` suffix where `<n>` is a number starting from
85+
/// `0` and incrementing by one each time (`_0`, ...).
86+
llvm::StringRef newName(const llvm::Twine &name) {
87+
locked = true;
88+
// Special case the situation where there is no name collision to avoid
89+
// messing with the SmallString allocation below.
90+
llvm::SmallString<64> tryName;
91+
auto inserted = nextIndex.insert({name.toStringRef(tryName), 0});
92+
if (inserted.second)
93+
return inserted.first->getKey();
94+
95+
// Try different suffixes until we get a collision-free one.
96+
if (tryName.empty())
97+
name.toVector(tryName); // toStringRef may leave tryName unfilled
98+
99+
// Indexes less than nextIndex[tryName] are lready used, so skip them.
100+
// Indexes larger than nextIndex[tryName] may be used in another name.
101+
size_t &i = nextIndex[tryName];
102+
tryName.push_back('_');
103+
size_t baseLength = tryName.size();
104+
do {
105+
tryName.resize(baseLength);
106+
llvm::Twine(i++).toVector(tryName); // append integer to tryName
107+
inserted = nextIndex.insert({tryName, 0});
108+
} while (!inserted.second);
109+
110+
return inserted.first->getKey();
111+
}
112+
113+
/// Return a unique name, derived from the input `name` and ensure the
114+
/// returned name has the input `suffix`. Also add the new name to the
115+
/// internal namespace.
116+
/// There are two possible outcomes for the returned name:
117+
/// 1. The original name + `_<suffix>` is returned.
118+
/// 2. The name is given a suffix `_<n>_<suffix>` where `<n>` is a number
119+
/// starting from `0` and incrementing by one each time.
120+
llvm::StringRef newName(const llvm::Twine &name, const llvm::Twine &suffix) {
121+
locked = true;
122+
// Special case the situation where there is no name collision to avoid
123+
// messing with the SmallString allocation below.
124+
llvm::SmallString<64> tryName;
125+
auto inserted = nextIndex.insert(
126+
{name.concat("_").concat(suffix).toStringRef(tryName), 0});
127+
if (inserted.second)
128+
return inserted.first->getKey();
129+
130+
// Try different suffixes until we get a collision-free one.
131+
tryName.clear();
132+
name.toVector(tryName); // toStringRef may leave tryName unfilled
133+
tryName.push_back('_');
134+
size_t baseLength = tryName.size();
135+
136+
// Get the initial number to start from. Since `:` is not a valid character
137+
// in a verilog identifier, we use it separate the name and suffix.
138+
// Next number for name+suffix is stored with key `name_:suffix`.
139+
tryName.push_back(':');
140+
suffix.toVector(tryName);
141+
142+
// Indexes less than nextIndex[tryName] are already used, so skip them.
143+
// Indexes larger than nextIndex[tryName] may be used in another name.
144+
size_t &i = nextIndex[tryName];
145+
do {
146+
tryName.resize(baseLength);
147+
llvm::Twine(i++).toVector(tryName); // append integer to tryName
148+
tryName.push_back('_');
149+
suffix.toVector(tryName);
150+
inserted = nextIndex.insert({tryName, 0});
151+
} while (!inserted.second);
152+
153+
return inserted.first->getKey();
154+
}
155+
156+
protected:
157+
// The "next index" that will be tried when trying to unique a string within a
158+
// namespace. It follows that all values less than the "next index" value are
159+
// already used.
160+
llvm::StringMap<size_t> nextIndex;
161+
162+
// When true, no names can be erased from the namespace. This is to prevent
163+
// erasing names after they have been used, thus leaving users of the
164+
// namespace in an inconsistent state.
165+
bool locked = false;
166+
};
167+
168+
} // namespace mlir
169+
170+
#endif // MLIR_SUPPORT_NAMESPACE_H
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
//===- SymCache.h - Declare Symbol Cache ------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file declares a Symbol Cache.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#ifndef MLIR_SUPPORT_SYMCACHE_H
14+
#define MLIR_SUPPORT_SYMCACHE_H
15+
16+
#include "mlir/IR/SymbolTable.h"
17+
#include "llvm/ADT/iterator.h"
18+
#include "llvm/Support/Casting.h"
19+
20+
namespace mlir {
21+
22+
/// Base symbol cache class to allow for cache lookup through a pointer to some
23+
/// abstract cache. A symbol cache stores lookup tables to make manipulating and
24+
/// working with the IR more efficient.
25+
class SymbolCacheBase {
26+
public:
27+
virtual ~SymbolCacheBase();
28+
29+
/// Defines 'op' as associated with the 'symbol' in the cache.
30+
virtual void addDefinition(mlir::Attribute symbol, mlir::Operation *op) = 0;
31+
32+
/// Adds the symbol-defining 'op' to the cache.
33+
void addSymbol(mlir::SymbolOpInterface op) {
34+
addDefinition(op.getNameAttr(), op);
35+
}
36+
37+
/// Populate the symbol cache with all symbol-defining operations within the
38+
/// 'top' operation.
39+
void addDefinitions(mlir::Operation *top);
40+
41+
/// Lookup a definition for 'symbol' in the cache.
42+
virtual mlir::Operation *getDefinition(mlir::Attribute symbol) const = 0;
43+
44+
/// Lookup a definition for 'symbol' in the cache.
45+
mlir::Operation *getDefinition(mlir::FlatSymbolRefAttr symbol) const {
46+
return getDefinition(symbol.getAttr());
47+
}
48+
49+
/// Iterator support through a pointer to some abstract cache.
50+
/// The implementing cache must provide an iterator that carries values on the
51+
/// form of <mlir::Attribute, mlir::Operation*>.
52+
using CacheItem = std::pair<mlir::Attribute, mlir::Operation *>;
53+
struct CacheIteratorImpl {
54+
virtual ~CacheIteratorImpl() {}
55+
virtual void operator++() = 0;
56+
virtual CacheItem operator*() = 0;
57+
virtual bool operator==(CacheIteratorImpl *other) = 0;
58+
};
59+
60+
struct Iterator
61+
: public llvm::iterator_facade_base<Iterator, std::forward_iterator_tag,
62+
CacheItem> {
63+
Iterator(std::unique_ptr<CacheIteratorImpl> &&impl)
64+
: impl(std::move(impl)) {}
65+
CacheItem operator*() const { return **impl; }
66+
using llvm::iterator_facade_base<Iterator, std::forward_iterator_tag,
67+
CacheItem>::operator++;
68+
bool operator==(const Iterator &other) const {
69+
return *impl == other.impl.get();
70+
}
71+
void operator++() { impl->operator++(); }
72+
73+
private:
74+
std::unique_ptr<CacheIteratorImpl> impl;
75+
};
76+
virtual Iterator begin() = 0;
77+
virtual Iterator end() = 0;
78+
};
79+
80+
/// Default symbol cache implementation; stores associations between names
81+
/// (StringAttr's) to mlir::Operation's.
82+
/// Adding/getting definitions from the symbol cache is not
83+
/// thread safe. If this is required, synchronizing cache acccess should be
84+
/// ensured by the caller.
85+
class SymbolCache : public SymbolCacheBase {
86+
public:
87+
/// In the building phase, add symbols.
88+
void addDefinition(mlir::Attribute key, mlir::Operation *op) override {
89+
symbolCache.try_emplace(key, op);
90+
}
91+
92+
// Pull in getDefinition(mlir::FlatSymbolRefAttr symbol)
93+
using SymbolCacheBase::getDefinition;
94+
mlir::Operation *getDefinition(mlir::Attribute attr) const override {
95+
auto it = symbolCache.find(attr);
96+
if (it == symbolCache.end())
97+
return nullptr;
98+
return it->second;
99+
}
100+
101+
protected:
102+
/// This stores a lookup table from symbol attribute to the operation
103+
/// that defines it.
104+
llvm::DenseMap<mlir::Attribute, mlir::Operation *> symbolCache;
105+
106+
private:
107+
/// Iterator support: A simple mapping between decltype(symbolCache)::iterator
108+
/// to SymbolCacheBase::Iterator.
109+
using Iterator = decltype(symbolCache)::iterator;
110+
struct SymbolCacheIteratorImpl : public CacheIteratorImpl {
111+
SymbolCacheIteratorImpl(Iterator it) : it(it) {}
112+
CacheItem operator*() override { return {it->getFirst(), it->getSecond()}; }
113+
void operator++() override { it++; }
114+
bool operator==(CacheIteratorImpl *other) override {
115+
return it == static_cast<SymbolCacheIteratorImpl *>(other)->it;
116+
}
117+
Iterator it;
118+
};
119+
120+
public:
121+
SymbolCacheBase::Iterator begin() override {
122+
return SymbolCacheBase::Iterator(
123+
std::make_unique<SymbolCacheIteratorImpl>(symbolCache.begin()));
124+
}
125+
SymbolCacheBase::Iterator end() override {
126+
return SymbolCacheBase::Iterator(
127+
std::make_unique<SymbolCacheIteratorImpl>(symbolCache.end()));
128+
}
129+
};
130+
131+
} // namespace mlir
132+
133+
#endif // MLIR_SUPPORT_SYMCACHE_H

mlir/lib/Target/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ add_subdirectory(Cpp)
22
add_subdirectory(SPIRV)
33
add_subdirectory(LLVMIR)
44
add_subdirectory(LLVM)
5+
add_subdirectory(SMTLIB)

mlir/lib/Target/SMTLIB/CMakeLists.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
add_mlir_translation_library(MLIRExportSMTLIB
2+
ExportSMTLIB.cpp
3+
4+
LINK_COMPONENTS
5+
Core
6+
7+
LINK_LIBS PUBLIC
8+
MLIRSMT
9+
MLIRSupport
10+
MLIRFuncDialect
11+
MLIRIR
12+
MLIRTranslateLib
13+
)

0 commit comments

Comments
 (0)