blob: ee2cb4087a6814a1f0f0f4d70afa9591caa8ec56 [file] [log] [blame]
Ben Olmsteadc0d77842019-07-31 17:34:05 -07001// Copyright 2019 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Implementations for the operations and builtin functions in the Emboss
16// expression language.
Ben Olmsteadf81d4f02019-10-04 16:46:54 -070017#ifndef EMBOSS_RUNTIME_CPP_EMBOSS_ARITHMETIC_H_
18#define EMBOSS_RUNTIME_CPP_EMBOSS_ARITHMETIC_H_
Ben Olmsteadc0d77842019-07-31 17:34:05 -070019
20#include <cstdint>
21#include <type_traits>
22
reventlov6731fc42019-10-03 15:23:13 -070023#include "runtime/cpp/emboss_bit_util.h"
24#include "runtime/cpp/emboss_maybe.h"
Ben Olmsteadc0d77842019-07-31 17:34:05 -070025
26namespace emboss {
27namespace support {
28
29// Arithmetic operations
30//
31// Emboss arithmetic is performed by special-purpose functions, not (directly)
32// using C++ operators. This allows Emboss to handle the minor differences
33// between the ways that Emboss operations are defined and the way that C++
34// operations are defined, and provides a convenient way to handle arithmetic on
35// values that might not be readable.
36//
37// The biggest differences are:
38//
39// Emboss's And and Or are defined to return false or true, respectively, if at
40// least one operand is false or true, respectively, even if the other operand
41// is not Known(). This is similar to C/C++ shortcut evaluation, except that it
42// is symmetric.
43//
44// Emboss's expression type system uses (notionally) infinite-size integers, but
45// it is an error in Emboss if the full range of any subexpression cannot fit in
46// either [-(2**63), 2**63 - 1] or [0, 2**64 - 1]. Additionally, either all
47// arguments to and the return type of an operation, if integers, must fit in
48// int64_t, or they must all fit in uin64_t. This means that C++ integer types
49// can be used directly for each operation, but casting may be required in
50// between operations.
51
Dmitri Primecab26822020-10-03 18:28:08 -070052
53// AllKnown(...) returns true if all of its arguments are Known(). The base
54// case is no arguments.
Ben Olmsteadc0d77842019-07-31 17:34:05 -070055inline constexpr bool AllKnown() { return true; }
56
Dmitri Primecab26822020-10-03 18:28:08 -070057// The rest of AllKnown() could be:
58//
59// template <typename T, typename... RestT>
60// inline constexpr bool AllKnown(T v, RestT... rest) {
61// return v.Known() && AllKnown(rest...);
62// }
63//
64// ... unfortunately, some compilers do not optimize this well, and it ends
65// up using linear stack space instead of constant stack space; for complex
66// structs on systems with limited stack (such as typical microcontrollers),
67// this can cause methods like Ok() to blow the stack.
68//
69// The C++14 solution would be to use a std::initializer_list and iterate over
70// the arguments. Unfortunately, C++11 std::initializer_list is not
71// constexpr, and C++11 constexpr does not allow iteration.
72//
Dmitri Prime0d3197d2020-10-07 23:52:40 -070073// Instead, for "small" numbers of arguments (up to 64, at time of writing,
74// controlled by OVERLOADS in generators/all_known.py), we have generated
75// overloads of the form:
Dmitri Primecab26822020-10-03 18:28:08 -070076//
77// template <typename T0, ... typename TN>
78// inline constexpr bool AllKnown(T0 v0, ... TN vN) {
79// return v0.Known() && ... && vN.Known();
80// }
81//
82// This reduces stack frames by ~64x.
Aaron Webster7a29b0a2021-09-20 12:38:57 -070083#include "emboss_arithmetic_all_known_generated.h"
Ben Olmsteadc0d77842019-07-31 17:34:05 -070084
85// MaybeDo implements the logic of checking for known values, unwrapping the
86// known values, passing the unwrapped values to OperatorT, and then rewrapping
87// the result.
88template <typename IntermediateT, typename ResultT, typename OperatorT,
89 typename... ArgsT>
90inline constexpr Maybe<ResultT> MaybeDo(Maybe<ArgsT>... args) {
91 return AllKnown(args...)
92 ? Maybe<ResultT>(static_cast<ResultT>(OperatorT::template Do(
93 static_cast<IntermediateT>(args.ValueOrDefault())...)))
94 : Maybe<ResultT>();
95}
96
97//// Operations intended to be passed to MaybeDo:
98
99struct SumOperation {
100 template <typename T>
101 static inline constexpr T Do(T l, T r) {
102 return l + r;
103 }
104};
105
106struct DifferenceOperation {
107 template <typename T>
108 static inline constexpr T Do(T l, T r) {
109 return l - r;
110 }
111};
112
113struct ProductOperation {
114 template <typename T>
115 static inline constexpr T Do(T l, T r) {
116 return l * r;
117 }
118};
119
120// Assertions for the template types of comparisons.
121template <typename ResultT, typename LeftT, typename RightT>
122inline constexpr bool AssertComparisonInPartsTypes() {
123 static_assert(::std::is_same<ResultT, bool>::value,
124 "EMBOSS BUG: Comparisons must return bool.");
125 static_assert(
126 ::std::is_signed<LeftT>::value || ::std::is_signed<RightT>::value,
127 "EMBOSS BUG: Comparisons in parts expect one side to be signed.");
128 static_assert(
129 ::std::is_unsigned<LeftT>::value || ::std::is_unsigned<RightT>::value,
130 "EMBOSS BUG: Comparisons in parts expect one side to be unsigned.");
131 return true; // A literal return type is required for a constexpr function.
132}
133
134struct EqualOperation {
135 template <typename T>
136 static inline constexpr bool Do(T l, T r) {
137 return l == r;
138 }
139};
140
141struct NotEqualOperation {
142 template <typename T>
143 static inline constexpr bool Do(T l, T r) {
144 return l != r;
145 }
146};
147
148struct LessThanOperation {
149 template <typename T>
150 static inline constexpr bool Do(T l, T r) {
151 return l < r;
152 }
153};
154
155struct LessThanOrEqualOperation {
156 template <typename T>
157 static inline constexpr bool Do(T l, T r) {
158 return l <= r;
159 }
160};
161
162struct GreaterThanOperation {
163 template <typename T>
164 static inline constexpr bool Do(T l, T r) {
165 return l > r;
166 }
167};
168
169struct GreaterThanOrEqualOperation {
170 template <typename T>
171 static inline constexpr bool Do(T l, T r) {
172 return l >= r;
173 }
174};
175
176// MaximumOperation is a bit more complex, in order to handle the variable
177// number of parameters.
178struct MaximumOperation {
Dmitri Primecab26822020-10-03 18:28:08 -0700179 // Maximum of 1 element is just itself.
Ben Olmsteadc0d77842019-07-31 17:34:05 -0700180 template <typename T>
181 static inline constexpr T Do(T arg) {
Ben Olmsteadc0d77842019-07-31 17:34:05 -0700182 return arg;
183 }
184
Dmitri Primecab26822020-10-03 18:28:08 -0700185 // The rest of MaximumOperation::Do could be:
Ben Olmsteadc0d77842019-07-31 17:34:05 -0700186 //
Dmitri Primecab26822020-10-03 18:28:08 -0700187 // template <typename T, typename... RestT>
188 // static inline constexpr T Do(T v0, T v1, RestT... rest) {
189 // return Do(v0 < v1 ? v1 : v0, rest...);
190 // }
191 //
192 // ... unfortunately, some compilers do not optimize this well, and it ends
193 // up using linear stack space instead of constant stack space; for complex
194 // structs on systems with limited stack (such as typical microcontrollers),
195 // this can cause methods like Ok() to blow the stack.
196 //
197 // The C++14 solution would be to use a std::initializer_list and iterate over
198 // the arguments. Unfortunately, C++11 std::initializer_list is not
199 // constexpr, and C++11 constexpr does not allow iteration.
200 //
201 // Instead, we have a small number of hand-written overloads and a large
Dmitri Prime0d3197d2020-10-07 23:52:40 -0700202 // number (59, at time of writing, controlled by OVERLOADS in
203 // generators/maximum_operation_do.py) of generated overloads, which use
204 // O(lg(N)) stack for "small" numbers of arguments (128 or fewer, at time of
205 // writing), and O(N) stack for more arguments, but with a much, much smaller
206 // constant multiplier: one additional stack frame per 64 arguments, instead
207 // of one per argument.
Dmitri Primecab26822020-10-03 18:28:08 -0700208
209 // Maximum of 2-4 elements are special-cased.
210 template <typename T>
211 static inline constexpr T Do(T v0, T v1) {
Ben Olmsteadc0d77842019-07-31 17:34:05 -0700212 // C++11 std::max is not constexpr, so we can't just call it.
Dmitri Primecab26822020-10-03 18:28:08 -0700213 return v0 < v1 ? v1 : v0;
Ben Olmsteadc0d77842019-07-31 17:34:05 -0700214 }
Dmitri Primecab26822020-10-03 18:28:08 -0700215
216 template <typename T>
217 static inline constexpr T Do(T v0, T v1, T v2) {
218 return Do(v0 < v1 ? v1 : v0, v2);
219 }
220
221 template <typename T>
222 static inline constexpr T Do(T v0, T v1, T v2, T v3) {
223 return Do(v0 < v1 ? v1 : v0, v2 < v3 ? v3 : v2);
224 }
225
226 // The remaining overloads (5+ arguments) are generated by a script and
227 // #included, so that they do not clutter the hand-written code.
228 //
229 // They are of the form:
230 //
231 // template <typename T>
232 // static inline constexpr Do(T v0, ... T vN, T vN_plus_1, ... T v2N) {
233 // return Do(Do(v0, ... vN), Do(vN_plus_1, ... v2N));
234 // }
235 //
236 // In each case, they cut their argument lists in half, calling Do(Do(first
237 // half), Do(second half)).
238 //
239 // Note that, if there are enough arguments, this still falls back onto
240 // linear-stack-space recursion.
Aaron Webster7a29b0a2021-09-20 12:38:57 -0700241#include "emboss_arithmetic_maximum_operation_generated.h"
Ben Olmsteadc0d77842019-07-31 17:34:05 -0700242};
243
244//// Special operations, where either un-Known() operands do not always result
245//// in un-Known() results, or where Known() operands do not always result in
246//// Known() results.
247
248// Assertions for And and Or.
249template <typename IntermediateT, typename ResultT, typename LeftT,
250 typename RightT>
251inline constexpr bool AssertBooleanOperationTypes() {
252 // And and Or are templates so that the Emboss code generator
253 // doesn't have to special case AND, but they should only be instantiated with
254 // <bool, bool, bool>. This pushes a bit of extra work onto the C++ compiler.
255 static_assert(::std::is_same<IntermediateT, bool>::value,
256 "EMBOSS BUG: Boolean operations must have bool IntermediateT.");
257 static_assert(::std::is_same<ResultT, bool>::value,
258 "EMBOSS BUG: Boolean operations must return bool.");
259 static_assert(::std::is_same<LeftT, bool>::value,
260 "EMBOSS BUG: Boolean operations require boolean operands.");
261 static_assert(::std::is_same<RightT, bool>::value,
262 "EMBOSS BUG: Boolean operations require boolean operands.");
263 return true; // A literal return type is required for a constexpr function.
264}
265
266template <typename IntermediateT, typename ResultT, typename LeftT,
267 typename RightT>
268inline constexpr Maybe<ResultT> And(Maybe<LeftT> l, Maybe<RightT> r) {
269 // If either value is false, the result is false, even if the other value is
270 // unknown. Otherwise, if either value is unknown, the result is unknown.
271 // Otherwise, both values are true, and the result is true.
272 return AssertBooleanOperationTypes<IntermediateT, ResultT, LeftT, RightT>(),
273 !l.ValueOr(true) || !r.ValueOr(true)
274 ? Maybe<ResultT>(false)
275 : (!l.Known() || !r.Known() ? Maybe<ResultT>()
276 : Maybe<ResultT>(true));
277}
278
279template <typename IntermediateT, typename ResultT, typename LeftT,
280 typename RightT>
281inline constexpr Maybe<ResultT> Or(Maybe<LeftT> l, Maybe<RightT> r) {
282 // If either value is true, the result is true, even if the other value is
283 // unknown. Otherwise, if either value is unknown, the result is unknown.
284 // Otherwise, both values are false, and the result is false.
285 return AssertBooleanOperationTypes<IntermediateT, ResultT, LeftT, RightT>(),
286 l.ValueOr(false) || r.ValueOr(false)
287 ? Maybe<ResultT>(true)
288 : (!l.Known() || !r.Known() ? Maybe<ResultT>()
289 : Maybe<ResultT>(false));
290}
291
292template <typename ResultT, typename ValueT>
293inline constexpr Maybe<ResultT> MaybeStaticCast(Maybe<ValueT> value) {
294 return value.Known()
295 ? Maybe<ResultT>(static_cast<ResultT>(value.ValueOrDefault()))
296 : Maybe<ResultT>();
297}
298
299template <typename IntermediateT, typename ResultT, typename ConditionT,
300 typename TrueT, typename FalseT>
301inline constexpr Maybe<ResultT> Choice(Maybe<ConditionT> condition,
302 Maybe<TrueT> if_true,
303 Maybe<FalseT> if_false) {
304 // Since the result of a condition could be any value from either if_true or
305 // if_false, it should be the same type as IntermediateT.
306 static_assert(::std::is_same<IntermediateT, ResultT>::value,
307 "Choice's IntermediateT should be the same as ResultT.");
308 static_assert(::std::is_same<ConditionT, bool>::value,
309 "Choice operation requires a boolean condition.");
310 // If the condition is un-Known(), then the result is un-Known(). Otherwise,
311 // the result is if_true if condition, or if_false if not condition. For
312 // integral types, ResultT may differ from TrueT or FalseT, so Known() results
313 // must be unwrapped, cast to ResultT, and re-wrapped in Maybe<ResultT>. For
314 // non-integral TrueT/FalseT/ResultT, the cast is unnecessary, but safe.
315 return condition.Known() ? condition.ValueOrDefault()
316 ? MaybeStaticCast<ResultT, TrueT>(if_true)
317 : MaybeStaticCast<ResultT, FalseT>(if_false)
318 : Maybe<ResultT>();
319}
320
321//// From here down: boilerplate instantiations of the various operations, which
322//// only forward to MaybeDo:
323
324template <typename IntermediateT, typename ResultT, typename LeftT,
325 typename RightT>
326inline constexpr Maybe<ResultT> Sum(Maybe<LeftT> l, Maybe<RightT> r) {
327 return MaybeDo<IntermediateT, ResultT, SumOperation, LeftT, RightT>(l, r);
328}
329
330template <typename IntermediateT, typename ResultT, typename LeftT,
331 typename RightT>
332inline constexpr Maybe<ResultT> Difference(Maybe<LeftT> l, Maybe<RightT> r) {
333 return MaybeDo<IntermediateT, ResultT, DifferenceOperation, LeftT, RightT>(l,
334 r);
335}
336
337template <typename IntermediateT, typename ResultT, typename LeftT,
338 typename RightT>
339inline constexpr Maybe<ResultT> Product(Maybe<LeftT> l, Maybe<RightT> r) {
340 return MaybeDo<IntermediateT, ResultT, ProductOperation, LeftT, RightT>(l, r);
341}
342
343template <typename IntermediateT, typename ResultT, typename LeftT,
344 typename RightT>
345inline constexpr Maybe<ResultT> Equal(Maybe<LeftT> l, Maybe<RightT> r) {
346 return MaybeDo<IntermediateT, ResultT, EqualOperation, LeftT, RightT>(l, r);
347}
348
349template <typename IntermediateT, typename ResultT, typename LeftT,
350 typename RightT>
351inline constexpr Maybe<ResultT> NotEqual(Maybe<LeftT> l, Maybe<RightT> r) {
352 return MaybeDo<IntermediateT, ResultT, NotEqualOperation, LeftT, RightT>(l,
353 r);
354}
355
356template <typename IntermediateT, typename ResultT, typename LeftT,
357 typename RightT>
358inline constexpr Maybe<ResultT> LessThan(Maybe<LeftT> l, Maybe<RightT> r) {
359 return MaybeDo<IntermediateT, ResultT, LessThanOperation, LeftT, RightT>(l,
360 r);
361}
362
363template <typename IntermediateT, typename ResultT, typename LeftT,
364 typename RightT>
365inline constexpr Maybe<ResultT> LessThanOrEqual(Maybe<LeftT> l,
366 Maybe<RightT> r) {
367 return MaybeDo<IntermediateT, ResultT, LessThanOrEqualOperation, LeftT,
368 RightT>(l, r);
369}
370
371template <typename IntermediateT, typename ResultT, typename LeftT,
372 typename RightT>
373inline constexpr Maybe<ResultT> GreaterThan(Maybe<LeftT> l, Maybe<RightT> r) {
374 return MaybeDo<IntermediateT, ResultT, GreaterThanOperation, LeftT, RightT>(
375 l, r);
376}
377
378template <typename IntermediateT, typename ResultT, typename LeftT,
379 typename RightT>
380inline constexpr Maybe<ResultT> GreaterThanOrEqual(Maybe<LeftT> l,
381 Maybe<RightT> r) {
382 return MaybeDo<IntermediateT, ResultT, GreaterThanOrEqualOperation, LeftT,
383 RightT>(l, r);
384}
385
386template <typename IntermediateT, typename ResultT, typename... ArgsT>
387inline constexpr Maybe<ResultT> Maximum(Maybe<ArgsT>... args) {
388 return MaybeDo<IntermediateT, ResultT, MaximumOperation, ArgsT...>(args...);
389}
390
391} // namespace support
392} // namespace emboss
393
Ben Olmsteadf81d4f02019-10-04 16:46:54 -0700394#endif // EMBOSS_RUNTIME_CPP_EMBOSS_ARITHMETIC_H_