xref: /aosp_15_r20/external/emboss/runtime/cpp/emboss_arithmetic.h (revision 99e0aae7469b87d12f0ad23e61142c2d74c1ef70)
1 // 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.
17 #ifndef EMBOSS_RUNTIME_CPP_EMBOSS_ARITHMETIC_H_
18 #define EMBOSS_RUNTIME_CPP_EMBOSS_ARITHMETIC_H_
19 
20 #include <cstdint>
21 #include <type_traits>
22 
23 #include "runtime/cpp/emboss_bit_util.h"
24 #include "runtime/cpp/emboss_maybe.h"
25 
26 namespace emboss {
27 namespace 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 
52 
53 // AllKnown(...) returns true if all of its arguments are Known().  The base
54 // case is no arguments.
AllKnown()55 inline constexpr bool AllKnown() { return true; }
56 
57 // 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 //
73 // 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:
76 //
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.
83 #include "emboss_arithmetic_all_known_generated.h"
84 
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.
88 template <typename IntermediateT, typename ResultT, typename OperatorT,
89           typename... ArgsT>
MaybeDo(Maybe<ArgsT>...args)90 inline 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 
99 struct SumOperation {
100   template <typename T>
DoSumOperation101   static inline constexpr T Do(T l, T r) {
102     return l + r;
103   }
104 };
105 
106 struct DifferenceOperation {
107   template <typename T>
DoDifferenceOperation108   static inline constexpr T Do(T l, T r) {
109     return l - r;
110   }
111 };
112 
113 struct ProductOperation {
114   template <typename T>
DoProductOperation115   static inline constexpr T Do(T l, T r) {
116     return l * r;
117   }
118 };
119 
120 // Assertions for the template types of comparisons.
121 template <typename ResultT, typename LeftT, typename RightT>
AssertComparisonInPartsTypes()122 inline 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 
134 struct EqualOperation {
135   template <typename T>
DoEqualOperation136   static inline constexpr bool Do(T l, T r) {
137     return l == r;
138   }
139 };
140 
141 struct NotEqualOperation {
142   template <typename T>
DoNotEqualOperation143   static inline constexpr bool Do(T l, T r) {
144     return l != r;
145   }
146 };
147 
148 struct LessThanOperation {
149   template <typename T>
DoLessThanOperation150   static inline constexpr bool Do(T l, T r) {
151     return l < r;
152   }
153 };
154 
155 struct LessThanOrEqualOperation {
156   template <typename T>
DoLessThanOrEqualOperation157   static inline constexpr bool Do(T l, T r) {
158     return l <= r;
159   }
160 };
161 
162 struct GreaterThanOperation {
163   template <typename T>
DoGreaterThanOperation164   static inline constexpr bool Do(T l, T r) {
165     return l > r;
166   }
167 };
168 
169 struct GreaterThanOrEqualOperation {
170   template <typename T>
DoGreaterThanOrEqualOperation171   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.
178 struct MaximumOperation {
179   // Maximum of 1 element is just itself.
180   template <typename T>
DoMaximumOperation181   static inline constexpr T Do(T arg) {
182     return arg;
183   }
184 
185   // The rest of MaximumOperation::Do could be:
186   //
187   // 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
202   // 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.
208 
209   // Maximum of 2-4 elements are special-cased.
210   template <typename T>
DoMaximumOperation211   static inline constexpr T Do(T v0, T v1) {
212     // C++11 std::max is not constexpr, so we can't just call it.
213     return v0 < v1 ? v1 : v0;
214   }
215 
216   template <typename T>
DoMaximumOperation217   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>
DoMaximumOperation222   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.
241 #include "emboss_arithmetic_maximum_operation_generated.h"
242 };
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.
249 template <typename IntermediateT, typename ResultT, typename LeftT,
250           typename RightT>
AssertBooleanOperationTypes()251 inline 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 
266 template <typename IntermediateT, typename ResultT, typename LeftT,
267           typename RightT>
And(Maybe<LeftT> l,Maybe<RightT> r)268 inline 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 
279 template <typename IntermediateT, typename ResultT, typename LeftT,
280           typename RightT>
Or(Maybe<LeftT> l,Maybe<RightT> r)281 inline 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 
292 template <typename ResultT, typename ValueT>
MaybeStaticCast(Maybe<ValueT> value)293 inline constexpr Maybe<ResultT> MaybeStaticCast(Maybe<ValueT> value) {
294   return value.Known()
295              ? Maybe<ResultT>(static_cast<ResultT>(value.ValueOrDefault()))
296              : Maybe<ResultT>();
297 }
298 
299 template <typename IntermediateT, typename ResultT, typename ConditionT,
300           typename TrueT, typename FalseT>
Choice(Maybe<ConditionT> condition,Maybe<TrueT> if_true,Maybe<FalseT> if_false)301 inline 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 
324 template <typename IntermediateT, typename ResultT, typename LeftT,
325           typename RightT>
Sum(Maybe<LeftT> l,Maybe<RightT> r)326 inline constexpr Maybe<ResultT> Sum(Maybe<LeftT> l, Maybe<RightT> r) {
327   return MaybeDo<IntermediateT, ResultT, SumOperation, LeftT, RightT>(l, r);
328 }
329 
330 template <typename IntermediateT, typename ResultT, typename LeftT,
331           typename RightT>
Difference(Maybe<LeftT> l,Maybe<RightT> r)332 inline constexpr Maybe<ResultT> Difference(Maybe<LeftT> l, Maybe<RightT> r) {
333   return MaybeDo<IntermediateT, ResultT, DifferenceOperation, LeftT, RightT>(l,
334                                                                              r);
335 }
336 
337 template <typename IntermediateT, typename ResultT, typename LeftT,
338           typename RightT>
Product(Maybe<LeftT> l,Maybe<RightT> r)339 inline constexpr Maybe<ResultT> Product(Maybe<LeftT> l, Maybe<RightT> r) {
340   return MaybeDo<IntermediateT, ResultT, ProductOperation, LeftT, RightT>(l, r);
341 }
342 
343 template <typename IntermediateT, typename ResultT, typename LeftT,
344           typename RightT>
Equal(Maybe<LeftT> l,Maybe<RightT> r)345 inline constexpr Maybe<ResultT> Equal(Maybe<LeftT> l, Maybe<RightT> r) {
346   return MaybeDo<IntermediateT, ResultT, EqualOperation, LeftT, RightT>(l, r);
347 }
348 
349 template <typename IntermediateT, typename ResultT, typename LeftT,
350           typename RightT>
NotEqual(Maybe<LeftT> l,Maybe<RightT> r)351 inline constexpr Maybe<ResultT> NotEqual(Maybe<LeftT> l, Maybe<RightT> r) {
352   return MaybeDo<IntermediateT, ResultT, NotEqualOperation, LeftT, RightT>(l,
353                                                                            r);
354 }
355 
356 template <typename IntermediateT, typename ResultT, typename LeftT,
357           typename RightT>
LessThan(Maybe<LeftT> l,Maybe<RightT> r)358 inline constexpr Maybe<ResultT> LessThan(Maybe<LeftT> l, Maybe<RightT> r) {
359   return MaybeDo<IntermediateT, ResultT, LessThanOperation, LeftT, RightT>(l,
360                                                                            r);
361 }
362 
363 template <typename IntermediateT, typename ResultT, typename LeftT,
364           typename RightT>
LessThanOrEqual(Maybe<LeftT> l,Maybe<RightT> r)365 inline constexpr Maybe<ResultT> LessThanOrEqual(Maybe<LeftT> l,
366                                                 Maybe<RightT> r) {
367   return MaybeDo<IntermediateT, ResultT, LessThanOrEqualOperation, LeftT,
368                  RightT>(l, r);
369 }
370 
371 template <typename IntermediateT, typename ResultT, typename LeftT,
372           typename RightT>
GreaterThan(Maybe<LeftT> l,Maybe<RightT> r)373 inline constexpr Maybe<ResultT> GreaterThan(Maybe<LeftT> l, Maybe<RightT> r) {
374   return MaybeDo<IntermediateT, ResultT, GreaterThanOperation, LeftT, RightT>(
375       l, r);
376 }
377 
378 template <typename IntermediateT, typename ResultT, typename LeftT,
379           typename RightT>
GreaterThanOrEqual(Maybe<LeftT> l,Maybe<RightT> r)380 inline constexpr Maybe<ResultT> GreaterThanOrEqual(Maybe<LeftT> l,
381                                                    Maybe<RightT> r) {
382   return MaybeDo<IntermediateT, ResultT, GreaterThanOrEqualOperation, LeftT,
383                  RightT>(l, r);
384 }
385 
386 template <typename IntermediateT, typename ResultT, typename... ArgsT>
Maximum(Maybe<ArgsT>...args)387 inline constexpr Maybe<ResultT> Maximum(Maybe<ArgsT>... args) {
388   return MaybeDo<IntermediateT, ResultT, MaximumOperation, ArgsT...>(args...);
389 }
390 
391 }  // namespace support
392 }  // namespace emboss
393 
394 #endif  // EMBOSS_RUNTIME_CPP_EMBOSS_ARITHMETIC_H_
395