LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
DynamicAPIntHelper.cpp
Go to the documentation of this file.
1//===-- DynamicAPIntHelper.cpp ----------------------------------*- 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//===----------------------------------------------------------------------===//
9
11
12#include "llzk/Util/Compare.h"
13
14#include <llvm/ADT/ArrayRef.h>
15#include <llvm/ADT/SmallString.h>
16#include <llvm/Support/Compiler.h>
17#include <llvm/Support/ErrorHandling.h>
18#include <llvm/Support/raw_ostream.h>
19
20#include <limits>
21#include <utility>
22
23using namespace llvm;
24using namespace std;
25
27static DynamicAPInt binaryBitOp(
28 const DynamicAPInt &lhs, const DynamicAPInt &rhs,
29 function_ref<APInt(const APInt &, const APInt &)> fn
30) {
31 APSInt a = llzk::toAPSInt(lhs), b = llzk::toAPSInt(rhs);
32 unsigned width = std::max(a.getBitWidth(), b.getBitWidth());
33 return DynamicAPInt(fn(a.sext(width), b.sext(width)));
34}
35
36namespace llzk {
37
38DynamicAPInt toSignedDynamicAPInt(ArrayRef<uint64_t> parts) {
39 if (parts.empty()) {
40 return DynamicAPInt(0);
41 }
42 if (parts.size() <= std::numeric_limits<unsigned>::max() / 64) {
43 return DynamicAPInt(APInt(static_cast<unsigned>(parts.size()) * 64, parts));
44 }
45 llvm::report_fatal_error("part array exceeds APInt's maximum bit width");
46}
47
48Expected<DynamicAPInt> parseDynamicAPInt(StringRef str) {
49 StringRef digits = str;
50 bool negative = digits.consume_front("-");
51 APInt magnitude;
52 if (digits.getAsInteger(10, magnitude)) {
53 return createStringError(inconvertibleErrorCode(), "expected signed decimal integer");
54 }
55 DynamicAPInt value = toDynamicAPInt(magnitude);
56 return negative ? -value : value;
57}
58
59hash_code hashDynamicAPInt(const DynamicAPInt &value) {
60 APSInt bits = toAPSInt(value);
61 return llvm::hash_value(bits.trunc(bits.getSignificantBits()));
62}
63
64Expected<APInt> checkedToAPInt(const DynamicAPInt &value, unsigned bitWidth, bool isSigned) {
65 APSInt bits = toAPSInt(value);
66 bool fits = bitWidth != 0 &&
67 (isSigned ? bits.isSignedIntN(bitWidth) : value >= 0 && bits.isIntN(bitWidth));
68 if (!fits) {
69 return createStringError(inconvertibleErrorCode(), "integer does not fit requested width");
70 }
71 return isSigned ? bits.sextOrTrunc(bitWidth) : bits.zextOrTrunc(bitWidth);
72}
73
74Expected<int64_t> checkedToInt64(const DynamicAPInt &value) {
75 if (value < numeric_limits<int64_t>::min() || value > numeric_limits<int64_t>::max()) {
76 return createStringError(inconvertibleErrorCode(), "integer does not fit requested width");
77 }
78 return static_cast<int64_t>(value);
79}
80
81Expected<uint64_t> checkedToUInt64(const DynamicAPInt &value) {
82 auto bits = checkedToAPInt(value, 64, false);
83 if (!bits) {
84 return bits.takeError();
85 }
86 return bits->getZExtValue();
87}
88
89Expected<size_t> checkedToSize(const DynamicAPInt &value, const Twine &context) {
90 if (value < 0) {
91 return createStringError(inconvertibleErrorCode(), context + " would underflow size_t");
92 }
93 constexpr size_t sizeMax = std::numeric_limits<size_t>::max();
94 if (value <= std::numeric_limits<int64_t>::max()) {
95 auto native = static_cast<uint64_t>(static_cast<int64_t>(value));
96 if (native > sizeMax) {
97 return createStringError(inconvertibleErrorCode(), context + " would overflow size_t");
98 }
99 return static_cast<size_t>(native);
100 }
101
102 static const llvm::DynamicAPInt maxValue = llzk::toDynamicAPInt(sizeMax);
103 if (value > maxValue) {
104 return createStringError(inconvertibleErrorCode(), context + " would overflow size_t");
105 }
106 // A valid value above INT64_MAX is in the upper half of a 64-bit size_t.
107 // Its distance from SIZE_MAX fits in int64_t, so extract that instead.
108 auto distance = static_cast<int64_t>(maxValue - value);
109 return sizeMax - static_cast<size_t>(distance);
110}
111
112DynamicAPInt operator&(const DynamicAPInt &lhs, const DynamicAPInt &rhs) {
113 return binaryBitOp(lhs, rhs, [](const APInt &a, const APInt &b) { return a & b; });
114}
115
116DynamicAPInt operator|(const DynamicAPInt &lhs, const DynamicAPInt &rhs) {
117 return binaryBitOp(lhs, rhs, [](const APInt &a, const APInt &b) { return a | b; });
118}
119
120DynamicAPInt operator^(const DynamicAPInt &lhs, const DynamicAPInt &rhs) {
121 return binaryBitOp(lhs, rhs, [](const APInt &a, const APInt &b) { return a ^ b; });
122}
123
124DynamicAPInt operator<<(const DynamicAPInt &lhs, const DynamicAPInt &rhs) {
125 if (rhs < numeric_limits<int64_t>::min() || rhs > numeric_limits<int64_t>::max()) {
126 llvm::report_fatal_error("invalid or unrepresentable left shift");
127 }
128 return lhs << static_cast<int64_t>(rhs);
129}
130
131DynamicAPInt operator<<(const DynamicAPInt &lhs, int64_t rhs) {
132 APSInt bits = toAPSInt(lhs);
133 unsigned width = bits.getSignificantBits();
134 if (rhs < 0 || static_cast<uint64_t>(rhs) > numeric_limits<unsigned>::max() - width) {
135 llvm::report_fatal_error("invalid or unrepresentable left shift");
136 }
137 if (lhs == 0) {
138 return DynamicAPInt(0);
139 }
140 unsigned amount = checkedCast<unsigned>(rhs);
141 return DynamicAPInt(bits.sextOrTrunc(width + amount).shl(amount));
142}
143
144DynamicAPInt operator>>(const DynamicAPInt &lhs, const DynamicAPInt &rhs) {
145 if (rhs < numeric_limits<int64_t>::min()) {
146 llvm::report_fatal_error("negative right shift");
147 }
148 if (rhs > numeric_limits<int64_t>::max()) {
149 return DynamicAPInt(lhs < 0 ? -1 : 0);
150 }
151 // The bounded count fits int64_t; signed extraction avoids an APSInt conversion.
152 return lhs >> static_cast<int64_t>(rhs);
153}
154
155DynamicAPInt operator>>(const DynamicAPInt &lhs, int64_t rhs) {
156 if (rhs < 0) {
157 llvm::report_fatal_error("negative right shift");
158 }
159 APSInt bits = toAPSInt(lhs);
160 if (static_cast<uint64_t>(rhs) >= bits.getSignificantBits()) {
161 return DynamicAPInt(lhs < 0 ? -1 : 0);
162 }
163 return DynamicAPInt(bits.ashr(checkedCast<unsigned>(rhs)));
164}
165
166DynamicAPInt toDynamicAPInt(StringRef str) { return cantFail(parseDynamicAPInt(str)); }
167
168DynamicAPInt toDynamicAPInt(const APSInt &i) {
169 // DynamicAPInt interprets APInt (implicit cast from APSInt for the constructor below) as
170 // signed. Extend unsigned APSInts with a 0 sign bit so their positive value is preserved.
171 if (i.isUnsigned() && i.isSignBitSet()) {
172 return DynamicAPInt(i.zext(i.getBitWidth() + 1));
173 } else {
174 return DynamicAPInt(i);
175 }
176}
177
178DynamicAPInt toDynamicAPInt(const APInt &i) {
179 if (i.isSignBitSet()) {
180 return DynamicAPInt(i.zext(i.getBitWidth() + 1));
181 } else {
182 return DynamicAPInt(i);
183 }
184}
185
189static LLVM_ATTRIBUTE_NOINLINE APSInt toAPSIntSlow(const DynamicAPInt &i) {
190 // 254-bit magnitude needs at most 77 decimal digits plus a minus sign; 96 inline characters cover
191 // this with headroom for larger intermediates so the pre-defined primes don't require resizing.
192 SmallString<96> repr;
193 raw_svector_ostream(repr) << i;
194
195 // This is based on `APSInt(StringRef)` and uses its conservative size estimate of 64/19 bits per
196 // decimal digit. However, we take some additional precautions to allocate space for the sign bit
197 // from the start, avoid truncating when a bitwidth change would not reduce the number of words
198 // required, and move the final result into the return value to avoid unnecessary copies.
199 size_t digits = repr.size() - (repr.front() == '-');
200 constexpr uint64_t maxDigits = ((uint64_t(numeric_limits<unsigned>::max()) - 2) * 19) / 64;
201 // Bound the digit count before multiplying or narrowing to APInt's unsigned bit width.
202 if (digits > maxDigits) {
203 llvm::report_fatal_error("decimal integer exceeds APInt's maximum parse width");
204 }
205 unsigned parseWidth = static_cast<unsigned>((uint64_t(digits) * 64) / 19 + 2);
206 APInt bits(parseWidth, repr, /*radix=*/10);
207 // getSignificantBits() includes the sign bit for either sign. Truncation
208 // allocates a new APInt, so only do it when it saves a storage word.
209 unsigned minWidth = bits.getSignificantBits();
210 if (APInt::getNumWords(minWidth) < bits.getNumWords()) {
211 bits = bits.trunc(minWidth);
212 }
213 return APSInt(std::move(bits), /*isUnsigned=*/false);
214}
215
216APSInt toAPSInt(const DynamicAPInt &i) {
217 if (numeric_limits<int64_t>::min() <= i && i <= numeric_limits<int64_t>::max()) {
218 // Fast path for smaller values, just use the int64_t conversion
219 return APSInt::get(int64_t(i));
220 }
221 return toAPSIntSlow(i);
222}
223
224DynamicAPInt modExp(const DynamicAPInt &base, const DynamicAPInt &exp, const DynamicAPInt &mod) {
225 if (exp < 0 || mod <= 0) {
226 llvm::report_fatal_error(
227 "modular exponentiation requires a nonnegative exponent and positive modulus"
228 );
229 }
230 DynamicAPInt result(mod == 1 ? 0 : 1);
231 DynamicAPInt b = llvm::mod(base, mod);
232 DynamicAPInt e = exp;
233
234 while (e != 0) {
235 if (e % 2 != 0) {
236 result = (result * b) % mod;
237 }
238
239 b = (b * b) % mod;
240 e = e >> 1;
241 }
242 return result;
243}
244
245DynamicAPInt modInversePrime(const DynamicAPInt &f, const DynamicAPInt &p) {
246 assert(f != 0 && "0 has no inverse");
247 // Fermat: f^(p-2) mod p
248 DynamicAPInt exp = p - 2;
249 DynamicAPInt result = modExp(f, exp, p);
250 assert(llvm::mod(f * result, p) == 1 && "inverse is incorrect");
251 return result;
252}
253
254} // namespace llzk
This file implements helpers for DynamicAPInt operations and conversions that LLVM does not provide.
ExpressionValue mod(const llvm::SMTSolverRef &solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
Expected< size_t > checkedToSize(const DynamicAPInt &value, const Twine &context)
Interval operator|(const Interval &lhs, const Interval &rhs)
hash_code hashDynamicAPInt(const DynamicAPInt &value)
Interval operator^(const Interval &lhs, const Interval &rhs)
DynamicAPInt toDynamicAPInt(StringRef str)
Expected< DynamicAPInt > parseDynamicAPInt(StringRef str)
Interval operator<<(const Interval &lhs, const Interval &rhs)
constexpr T checkedCast(U u) noexcept
Definition Compare.h:94
Expected< uint64_t > checkedToUInt64(const DynamicAPInt &value)
Interval operator>>(const Interval &lhs, const Interval &rhs)
Interval operator&(const Interval &lhs, const Interval &rhs)
Expected< int64_t > checkedToInt64(const DynamicAPInt &value)
DynamicAPInt toSignedDynamicAPInt(ArrayRef< uint64_t > parts)
DynamicAPInt modInversePrime(const DynamicAPInt &f, const DynamicAPInt &p)
APSInt toAPSInt(const DynamicAPInt &i)
Expected< APInt > checkedToAPInt(const DynamicAPInt &value, unsigned bitWidth, bool isSigned)
DynamicAPInt modExp(const DynamicAPInt &base, const DynamicAPInt &exp, const DynamicAPInt &mod)