cannam@49: // Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
cannam@49: // Licensed under the MIT License:
cannam@49: //
cannam@49: // Permission is hereby granted, free of charge, to any person obtaining a copy
cannam@49: // of this software and associated documentation files (the "Software"), to deal
cannam@49: // in the Software without restriction, including without limitation the rights
cannam@49: // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
cannam@49: // copies of the Software, and to permit persons to whom the Software is
cannam@49: // furnished to do so, subject to the following conditions:
cannam@49: //
cannam@49: // The above copyright notice and this permission notice shall be included in
cannam@49: // all copies or substantial portions of the Software.
cannam@49: //
cannam@49: // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
cannam@49: // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
cannam@49: // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
cannam@49: // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
cannam@49: // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
cannam@49: // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
cannam@49: // THE SOFTWARE.
cannam@49: 
cannam@49: #ifndef CAPNP_ENDIAN_H_
cannam@49: #define CAPNP_ENDIAN_H_
cannam@49: 
cannam@49: #if defined(__GNUC__) && !defined(CAPNP_HEADER_WARNINGS)
cannam@49: #pragma GCC system_header
cannam@49: #endif
cannam@49: 
cannam@49: #include "common.h"
cannam@49: #include <inttypes.h>
cannam@49: #include <string.h>  // memcpy
cannam@49: 
cannam@49: namespace capnp {
cannam@49: namespace _ {  // private
cannam@49: 
cannam@49: // WireValue
cannam@49: //
cannam@49: // Wraps a primitive value as it appears on the wire.  Namely, values are little-endian on the
cannam@49: // wire, because little-endian is the most common endianness in modern CPUs.
cannam@49: //
cannam@49: // Note:  In general, code that depends cares about byte ordering is bad.  See:
cannam@49: //     http://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html
cannam@49: //   Cap'n Proto is special because it is essentially doing compiler-like things, fussing over
cannam@49: //   allocation and layout of memory, in order to squeeze out every last drop of performance.
cannam@49: 
cannam@49: #if _MSC_VER
cannam@49: // Assume Windows is little-endian.
cannam@49: //
cannam@49: // TODO(msvc): This is ugly. Maybe refactor later checks to be based on CAPNP_BYTE_ORDER or
cannam@49: //   CAPNP_SWAP_BYTES or something, and define that in turn based on _MSC_VER or the GCC
cannam@49: //   intrinsics.
cannam@49: 
cannam@49: #ifndef __ORDER_BIG_ENDIAN__
cannam@49: #define __ORDER_BIG_ENDIAN__ 4321
cannam@49: #endif
cannam@49: #ifndef __ORDER_LITTLE_ENDIAN__
cannam@49: #define __ORDER_LITTLE_ENDIAN__ 1234
cannam@49: #endif
cannam@49: #ifndef __BYTE_ORDER__
cannam@49: #define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__
cannam@49: #endif
cannam@49: #endif
cannam@49: 
cannam@49: #if CAPNP_REVERSE_ENDIAN
cannam@49: #define CAPNP_WIRE_BYTE_ORDER __ORDER_BIG_ENDIAN__
cannam@49: #define CAPNP_OPPOSITE_OF_WIRE_BYTE_ORDER __ORDER_LITTLE_ENDIAN__
cannam@49: #else
cannam@49: #define CAPNP_WIRE_BYTE_ORDER __ORDER_LITTLE_ENDIAN__
cannam@49: #define CAPNP_OPPOSITE_OF_WIRE_BYTE_ORDER __ORDER_BIG_ENDIAN__
cannam@49: #endif
cannam@49: 
cannam@49: #if defined(__BYTE_ORDER__) && \
cannam@49:     __BYTE_ORDER__ == CAPNP_WIRE_BYTE_ORDER && \
cannam@49:     !CAPNP_DISABLE_ENDIAN_DETECTION
cannam@49: // CPU is little-endian.  We can just read/write the memory directly.
cannam@49: 
cannam@49: template <typename T>
cannam@49: class DirectWireValue {
cannam@49: public:
cannam@49:   KJ_ALWAYS_INLINE(T get() const) { return value; }
cannam@49:   KJ_ALWAYS_INLINE(void set(T newValue)) { value = newValue; }
cannam@49: 
cannam@49: private:
cannam@49:   T value;
cannam@49: };
cannam@49: 
cannam@49: template <typename T>
cannam@49: using WireValue = DirectWireValue<T>;
cannam@49: // To prevent ODR problems when endian-test, endian-reverse-test, and endian-fallback-test are
cannam@49: // linked together, we define each implementation with a different name and define an alias to the
cannam@49: // one we want to use.
cannam@49: 
cannam@49: #elif defined(__BYTE_ORDER__) && \
cannam@49:       __BYTE_ORDER__ == CAPNP_OPPOSITE_OF_WIRE_BYTE_ORDER && \
cannam@49:       defined(__GNUC__) && !CAPNP_DISABLE_ENDIAN_DETECTION
cannam@49: // Big-endian, but GCC's __builtin_bswap() is available.
cannam@49: 
cannam@49: // TODO(perf):  Use dedicated instructions to read little-endian data on big-endian CPUs that have
cannam@49: //   them.
cannam@49: 
cannam@49: // TODO(perf):  Verify that this code optimizes reasonably.  In particular, ensure that the
cannam@49: //   compiler optimizes away the memcpy()s and keeps everything in registers.
cannam@49: 
cannam@49: template <typename T, size_t size = sizeof(T)>
cannam@49: class SwappingWireValue;
cannam@49: 
cannam@49: template <typename T>
cannam@49: class SwappingWireValue<T, 1> {
cannam@49: public:
cannam@49:   KJ_ALWAYS_INLINE(T get() const) { return value; }
cannam@49:   KJ_ALWAYS_INLINE(void set(T newValue)) { value = newValue; }
cannam@49: 
cannam@49: private:
cannam@49:   T value;
cannam@49: };
cannam@49: 
cannam@49: template <typename T>
cannam@49: class SwappingWireValue<T, 2> {
cannam@49: public:
cannam@49:   KJ_ALWAYS_INLINE(T get() const) {
cannam@49:     // Not all platforms have __builtin_bswap16() for some reason.  In particular, it is missing
cannam@49:     // on gcc-4.7.3-cygwin32 (but present on gcc-4.8.1-cygwin64).
cannam@49:     uint16_t swapped = (value << 8) | (value >> 8);
cannam@49:     T result;
cannam@49:     memcpy(&result, &swapped, sizeof(T));
cannam@49:     return result;
cannam@49:   }
cannam@49:   KJ_ALWAYS_INLINE(void set(T newValue)) {
cannam@49:     uint16_t raw;
cannam@49:     memcpy(&raw, &newValue, sizeof(T));
cannam@49:     // Not all platforms have __builtin_bswap16() for some reason.  In particular, it is missing
cannam@49:     // on gcc-4.7.3-cygwin32 (but present on gcc-4.8.1-cygwin64).
cannam@49:     value = (raw << 8) | (raw >> 8);
cannam@49:   }
cannam@49: 
cannam@49: private:
cannam@49:   uint16_t value;
cannam@49: };
cannam@49: 
cannam@49: template <typename T>
cannam@49: class SwappingWireValue<T, 4> {
cannam@49: public:
cannam@49:   KJ_ALWAYS_INLINE(T get() const) {
cannam@49:     uint32_t swapped = __builtin_bswap32(value);
cannam@49:     T result;
cannam@49:     memcpy(&result, &swapped, sizeof(T));
cannam@49:     return result;
cannam@49:   }
cannam@49:   KJ_ALWAYS_INLINE(void set(T newValue)) {
cannam@49:     uint32_t raw;
cannam@49:     memcpy(&raw, &newValue, sizeof(T));
cannam@49:     value = __builtin_bswap32(raw);
cannam@49:   }
cannam@49: 
cannam@49: private:
cannam@49:   uint32_t value;
cannam@49: };
cannam@49: 
cannam@49: template <typename T>
cannam@49: class SwappingWireValue<T, 8> {
cannam@49: public:
cannam@49:   KJ_ALWAYS_INLINE(T get() const) {
cannam@49:     uint64_t swapped = __builtin_bswap64(value);
cannam@49:     T result;
cannam@49:     memcpy(&result, &swapped, sizeof(T));
cannam@49:     return result;
cannam@49:   }
cannam@49:   KJ_ALWAYS_INLINE(void set(T newValue)) {
cannam@49:     uint64_t raw;
cannam@49:     memcpy(&raw, &newValue, sizeof(T));
cannam@49:     value = __builtin_bswap64(raw);
cannam@49:   }
cannam@49: 
cannam@49: private:
cannam@49:   uint64_t value;
cannam@49: };
cannam@49: 
cannam@49: template <typename T>
cannam@49: using WireValue = SwappingWireValue<T>;
cannam@49: // To prevent ODR problems when endian-test, endian-reverse-test, and endian-fallback-test are
cannam@49: // linked together, we define each implementation with a different name and define an alias to the
cannam@49: // one we want to use.
cannam@49: 
cannam@49: #else
cannam@49: // Unknown endianness.  Fall back to bit shifts.
cannam@49: 
cannam@49: #if !CAPNP_DISABLE_ENDIAN_DETECTION
cannam@49: #if _MSC_VER
cannam@49: #pragma message("Couldn't detect endianness of your platform.  Using unoptimized fallback implementation.")
cannam@49: #pragma message("Consider changing this code to detect your platform and send us a patch!")
cannam@49: #else
cannam@49: #warning "Couldn't detect endianness of your platform.  Using unoptimized fallback implementation."
cannam@49: #warning "Consider changing this code to detect your platform and send us a patch!"
cannam@49: #endif
cannam@49: #endif  // !CAPNP_DISABLE_ENDIAN_DETECTION
cannam@49: 
cannam@49: template <typename T, size_t size = sizeof(T)>
cannam@49: class ShiftingWireValue;
cannam@49: 
cannam@49: template <typename T>
cannam@49: class ShiftingWireValue<T, 1> {
cannam@49: public:
cannam@49:   KJ_ALWAYS_INLINE(T get() const) { return value; }
cannam@49:   KJ_ALWAYS_INLINE(void set(T newValue)) { value = newValue; }
cannam@49: 
cannam@49: private:
cannam@49:   T value;
cannam@49: };
cannam@49: 
cannam@49: template <typename T>
cannam@49: class ShiftingWireValue<T, 2> {
cannam@49: public:
cannam@49:   KJ_ALWAYS_INLINE(T get() const) {
cannam@49:     uint16_t raw = (static_cast<uint16_t>(bytes[0])     ) |
cannam@49:                    (static_cast<uint16_t>(bytes[1]) << 8);
cannam@49:     T result;
cannam@49:     memcpy(&result, &raw, sizeof(T));
cannam@49:     return result;
cannam@49:   }
cannam@49:   KJ_ALWAYS_INLINE(void set(T newValue)) {
cannam@49:     uint16_t raw;
cannam@49:     memcpy(&raw, &newValue, sizeof(T));
cannam@49:     bytes[0] = raw;
cannam@49:     bytes[1] = raw >> 8;
cannam@49:   }
cannam@49: 
cannam@49: private:
cannam@49:   union {
cannam@49:     byte bytes[2];
cannam@49:     uint16_t align;
cannam@49:   };
cannam@49: };
cannam@49: 
cannam@49: template <typename T>
cannam@49: class ShiftingWireValue<T, 4> {
cannam@49: public:
cannam@49:   KJ_ALWAYS_INLINE(T get() const) {
cannam@49:     uint32_t raw = (static_cast<uint32_t>(bytes[0])      ) |
cannam@49:                    (static_cast<uint32_t>(bytes[1]) <<  8) |
cannam@49:                    (static_cast<uint32_t>(bytes[2]) << 16) |
cannam@49:                    (static_cast<uint32_t>(bytes[3]) << 24);
cannam@49:     T result;
cannam@49:     memcpy(&result, &raw, sizeof(T));
cannam@49:     return result;
cannam@49:   }
cannam@49:   KJ_ALWAYS_INLINE(void set(T newValue)) {
cannam@49:     uint32_t raw;
cannam@49:     memcpy(&raw, &newValue, sizeof(T));
cannam@49:     bytes[0] = raw;
cannam@49:     bytes[1] = raw >> 8;
cannam@49:     bytes[2] = raw >> 16;
cannam@49:     bytes[3] = raw >> 24;
cannam@49:   }
cannam@49: 
cannam@49: private:
cannam@49:   union {
cannam@49:     byte bytes[4];
cannam@49:     uint32_t align;
cannam@49:   };
cannam@49: };
cannam@49: 
cannam@49: template <typename T>
cannam@49: class ShiftingWireValue<T, 8> {
cannam@49: public:
cannam@49:   KJ_ALWAYS_INLINE(T get() const) {
cannam@49:     uint64_t raw = (static_cast<uint64_t>(bytes[0])      ) |
cannam@49:                    (static_cast<uint64_t>(bytes[1]) <<  8) |
cannam@49:                    (static_cast<uint64_t>(bytes[2]) << 16) |
cannam@49:                    (static_cast<uint64_t>(bytes[3]) << 24) |
cannam@49:                    (static_cast<uint64_t>(bytes[4]) << 32) |
cannam@49:                    (static_cast<uint64_t>(bytes[5]) << 40) |
cannam@49:                    (static_cast<uint64_t>(bytes[6]) << 48) |
cannam@49:                    (static_cast<uint64_t>(bytes[7]) << 56);
cannam@49:     T result;
cannam@49:     memcpy(&result, &raw, sizeof(T));
cannam@49:     return result;
cannam@49:   }
cannam@49:   KJ_ALWAYS_INLINE(void set(T newValue)) {
cannam@49:     uint64_t raw;
cannam@49:     memcpy(&raw, &newValue, sizeof(T));
cannam@49:     bytes[0] = raw;
cannam@49:     bytes[1] = raw >> 8;
cannam@49:     bytes[2] = raw >> 16;
cannam@49:     bytes[3] = raw >> 24;
cannam@49:     bytes[4] = raw >> 32;
cannam@49:     bytes[5] = raw >> 40;
cannam@49:     bytes[6] = raw >> 48;
cannam@49:     bytes[7] = raw >> 56;
cannam@49:   }
cannam@49: 
cannam@49: private:
cannam@49:   union {
cannam@49:     byte bytes[8];
cannam@49:     uint64_t align;
cannam@49:   };
cannam@49: };
cannam@49: 
cannam@49: template <typename T>
cannam@49: using WireValue = ShiftingWireValue<T>;
cannam@49: // To prevent ODR problems when endian-test, endian-reverse-test, and endian-fallback-test are
cannam@49: // linked together, we define each implementation with a different name and define an alias to the
cannam@49: // one we want to use.
cannam@49: 
cannam@49: #endif
cannam@49: 
cannam@49: }  // namespace _ (private)
cannam@49: }  // namespace capnp
cannam@49: 
cannam@49: #endif  // CAPNP_ENDIAN_H_