// Copyright 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Implementations for the operations and builtin functions in the Emboss // expression language. #ifndef EMBOSS_RUNTIME_CPP_EMBOSS_ARITHMETIC_H_ #define EMBOSS_RUNTIME_CPP_EMBOSS_ARITHMETIC_H_ #include #include #include "runtime/cpp/emboss_bit_util.h" #include "runtime/cpp/emboss_maybe.h" namespace emboss { namespace support { // Arithmetic operations // // Emboss arithmetic is performed by special-purpose functions, not (directly) // using C++ operators. This allows Emboss to handle the minor differences // between the ways that Emboss operations are defined and the way that C++ // operations are defined, and provides a convenient way to handle arithmetic on // values that might not be readable. // // The biggest differences are: // // Emboss's And and Or are defined to return false or true, respectively, if at // least one operand is false or true, respectively, even if the other operand // is not Known(). This is similar to C/C++ shortcut evaluation, except that it // is symmetric. // // Emboss's expression type system uses (notionally) infinite-size integers, but // it is an error in Emboss if the full range of any subexpression cannot fit in // either [-(2**63), 2**63 - 1] or [0, 2**64 - 1]. Additionally, either all // arguments to and the return type of an operation, if integers, must fit in // int64_t, or they must all fit in uin64_t. This means that C++ integer types // can be used directly for each operation, but casting may be required in // between operations. // AllKnown(...) returns true if all of its arguments are Known(). The base // case is no arguments. inline constexpr bool AllKnown() { return true; } // The rest of AllKnown() could be: // // template // inline constexpr bool AllKnown(T v, RestT... rest) { // return v.Known() && AllKnown(rest...); // } // // ... unfortunately, some compilers do not optimize this well, and it ends // up using linear stack space instead of constant stack space; for complex // structs on systems with limited stack (such as typical microcontrollers), // this can cause methods like Ok() to blow the stack. // // The C++14 solution would be to use a std::initializer_list and iterate over // the arguments. Unfortunately, C++11 std::initializer_list is not // constexpr, and C++11 constexpr does not allow iteration. // // Instead, for "small" numbers of arguments (up to 64, at time of writing, // controlled by OVERLOADS in generators/all_known.py), we have generated // overloads of the form: // // template // inline constexpr bool AllKnown(T0 v0, ... TN vN) { // return v0.Known() && ... && vN.Known(); // } // // This reduces stack frames by ~64x. #include "emboss_arithmetic_all_known_generated.h" // MaybeDo implements the logic of checking for known values, unwrapping the // known values, passing the unwrapped values to OperatorT, and then rewrapping // the result. template inline constexpr Maybe MaybeDo(Maybe... args) { return AllKnown(args...) ? Maybe(static_cast(OperatorT::template Do<>( static_cast(args.ValueOrDefault())...))) : Maybe(); } //// Operations intended to be passed to MaybeDo: struct SumOperation { template static inline constexpr T Do(T l, T r) { return l + r; } }; struct DifferenceOperation { template static inline constexpr T Do(T l, T r) { return l - r; } }; struct ProductOperation { template static inline constexpr T Do(T l, T r) { return l * r; } }; // Assertions for the template types of comparisons. template inline constexpr bool AssertComparisonInPartsTypes() { static_assert(::std::is_same::value, "EMBOSS BUG: Comparisons must return bool."); static_assert( ::std::is_signed::value || ::std::is_signed::value, "EMBOSS BUG: Comparisons in parts expect one side to be signed."); static_assert( ::std::is_unsigned::value || ::std::is_unsigned::value, "EMBOSS BUG: Comparisons in parts expect one side to be unsigned."); return true; // A literal return type is required for a constexpr function. } struct EqualOperation { template static inline constexpr bool Do(T l, T r) { return l == r; } }; struct NotEqualOperation { template static inline constexpr bool Do(T l, T r) { return l != r; } }; struct LessThanOperation { template static inline constexpr bool Do(T l, T r) { return l < r; } }; struct LessThanOrEqualOperation { template static inline constexpr bool Do(T l, T r) { return l <= r; } }; struct GreaterThanOperation { template static inline constexpr bool Do(T l, T r) { return l > r; } }; struct GreaterThanOrEqualOperation { template static inline constexpr bool Do(T l, T r) { return l >= r; } }; // MaximumOperation is a bit more complex, in order to handle the variable // number of parameters. struct MaximumOperation { // Maximum of 1 element is just itself. template static inline constexpr T Do(T arg) { return arg; } // The rest of MaximumOperation::Do could be: // // template // static inline constexpr T Do(T v0, T v1, RestT... rest) { // return Do(v0 < v1 ? v1 : v0, rest...); // } // // ... unfortunately, some compilers do not optimize this well, and it ends // up using linear stack space instead of constant stack space; for complex // structs on systems with limited stack (such as typical microcontrollers), // this can cause methods like Ok() to blow the stack. // // The C++14 solution would be to use a std::initializer_list and iterate over // the arguments. Unfortunately, C++11 std::initializer_list is not // constexpr, and C++11 constexpr does not allow iteration. // // Instead, we have a small number of hand-written overloads and a large // number (59, at time of writing, controlled by OVERLOADS in // generators/maximum_operation_do.py) of generated overloads, which use // O(lg(N)) stack for "small" numbers of arguments (128 or fewer, at time of // writing), and O(N) stack for more arguments, but with a much, much smaller // constant multiplier: one additional stack frame per 64 arguments, instead // of one per argument. // Maximum of 2-4 elements are special-cased. template static inline constexpr T Do(T v0, T v1) { // C++11 std::max is not constexpr, so we can't just call it. return v0 < v1 ? v1 : v0; } template static inline constexpr T Do(T v0, T v1, T v2) { return Do(v0 < v1 ? v1 : v0, v2); } template static inline constexpr T Do(T v0, T v1, T v2, T v3) { return Do(v0 < v1 ? v1 : v0, v2 < v3 ? v3 : v2); } // The remaining overloads (5+ arguments) are generated by a script and // #included, so that they do not clutter the hand-written code. // // They are of the form: // // template // static inline constexpr Do(T v0, ... T vN, T vN_plus_1, ... T v2N) { // return Do(Do(v0, ... vN), Do(vN_plus_1, ... v2N)); // } // // In each case, they cut their argument lists in half, calling Do(Do(first // half), Do(second half)). // // Note that, if there are enough arguments, this still falls back onto // linear-stack-space recursion. #include "emboss_arithmetic_maximum_operation_generated.h" }; //// Special operations, where either un-Known() operands do not always result //// in un-Known() results, or where Known() operands do not always result in //// Known() results. // Assertions for And and Or. template inline constexpr bool AssertBooleanOperationTypes() { // And and Or are templates so that the Emboss code generator // doesn't have to special case AND, but they should only be instantiated with // . This pushes a bit of extra work onto the C++ compiler. static_assert(::std::is_same::value, "EMBOSS BUG: Boolean operations must have bool IntermediateT."); static_assert(::std::is_same::value, "EMBOSS BUG: Boolean operations must return bool."); static_assert(::std::is_same::value, "EMBOSS BUG: Boolean operations require boolean operands."); static_assert(::std::is_same::value, "EMBOSS BUG: Boolean operations require boolean operands."); return true; // A literal return type is required for a constexpr function. } template inline constexpr Maybe And(Maybe l, Maybe r) { // If either value is false, the result is false, even if the other value is // unknown. Otherwise, if either value is unknown, the result is unknown. // Otherwise, both values are true, and the result is true. return AssertBooleanOperationTypes(), !l.ValueOr(true) || !r.ValueOr(true) ? Maybe(false) : (!l.Known() || !r.Known() ? Maybe() : Maybe(true)); } template inline constexpr Maybe Or(Maybe l, Maybe r) { // If either value is true, the result is true, even if the other value is // unknown. Otherwise, if either value is unknown, the result is unknown. // Otherwise, both values are false, and the result is false. return AssertBooleanOperationTypes(), l.ValueOr(false) || r.ValueOr(false) ? Maybe(true) : (!l.Known() || !r.Known() ? Maybe() : Maybe(false)); } template inline constexpr Maybe MaybeStaticCast(Maybe value) { return value.Known() ? Maybe(static_cast(value.ValueOrDefault())) : Maybe(); } template inline constexpr Maybe Choice(Maybe condition, Maybe if_true, Maybe if_false) { // Since the result of a condition could be any value from either if_true or // if_false, it should be the same type as IntermediateT. static_assert(::std::is_same::value, "Choice's IntermediateT should be the same as ResultT."); static_assert(::std::is_same::value, "Choice operation requires a boolean condition."); // If the condition is un-Known(), then the result is un-Known(). Otherwise, // the result is if_true if condition, or if_false if not condition. For // integral types, ResultT may differ from TrueT or FalseT, so Known() results // must be unwrapped, cast to ResultT, and re-wrapped in Maybe. For // non-integral TrueT/FalseT/ResultT, the cast is unnecessary, but safe. return condition.Known() ? condition.ValueOrDefault() ? MaybeStaticCast(if_true) : MaybeStaticCast(if_false) : Maybe(); } //// From here down: boilerplate instantiations of the various operations, which //// only forward to MaybeDo: template inline constexpr Maybe Sum(Maybe l, Maybe r) { return MaybeDo(l, r); } template inline constexpr Maybe Difference(Maybe l, Maybe r) { return MaybeDo(l, r); } template inline constexpr Maybe Product(Maybe l, Maybe r) { return MaybeDo(l, r); } template inline constexpr Maybe Equal(Maybe l, Maybe r) { return MaybeDo(l, r); } template inline constexpr Maybe NotEqual(Maybe l, Maybe r) { return MaybeDo(l, r); } template inline constexpr Maybe LessThan(Maybe l, Maybe r) { return MaybeDo(l, r); } template inline constexpr Maybe LessThanOrEqual(Maybe l, Maybe r) { return MaybeDo(l, r); } template inline constexpr Maybe GreaterThan(Maybe l, Maybe r) { return MaybeDo( l, r); } template inline constexpr Maybe GreaterThanOrEqual(Maybe l, Maybe r) { return MaybeDo(l, r); } template inline constexpr Maybe Maximum(Maybe... args) { return MaybeDo(args...); } } // namespace support } // namespace emboss #endif // EMBOSS_RUNTIME_CPP_EMBOSS_ARITHMETIC_H_