From dbde35dd546860e1d6bed730f157ec81143c9e25 Mon Sep 17 00:00:00 2001 From: Aaron Webster Date: Wed, 3 Dec 2025 13:27:06 -0800 Subject: [PATCH 1/3] Add optional 128-bit integer support to C++ runtime --- runtime/cpp/emboss_bit_util.h | 8 +++ runtime/cpp/emboss_cpp_types.h | 16 +++++ runtime/cpp/emboss_defines.h | 10 +++ runtime/cpp/emboss_memory_util.h | 5 ++ runtime/cpp/test/emboss_bit_util_test.cc | 39 ++++++++++ runtime/cpp/test/emboss_cpp_types_test.cc | 18 +++++ runtime/cpp/test/emboss_prelude_test.cc | 30 ++++++++ testdata/BUILD | 8 +++ testdata/int128_sizes.emb | 86 +++++++++++++++++++++++ 9 files changed, 220 insertions(+) create mode 100644 testdata/int128_sizes.emb diff --git a/runtime/cpp/emboss_bit_util.h b/runtime/cpp/emboss_bit_util.h index 3e93f96..09aa317 100644 --- a/runtime/cpp/emboss_bit_util.h +++ b/runtime/cpp/emboss_bit_util.h @@ -56,6 +56,14 @@ inline constexpr ::std::uint64_t ByteSwap(::std::uint64_t x) { #endif } +#if EMBOSS_HAS_INT128 +inline constexpr __uint128_t ByteSwap(__uint128_t x) { + return (static_cast<__uint128_t>(ByteSwap(static_cast<::std::uint64_t>(x))) + << 64) | + ByteSwap(static_cast<::std::uint64_t>(x >> 64)); +} +#endif // EMBOSS_HAS_INT128 + // Masks the given value to the given number of bits. template inline constexpr T MaskToNBits(T value, unsigned bits) { diff --git a/runtime/cpp/emboss_cpp_types.h b/runtime/cpp/emboss_cpp_types.h index 9a1c211..6625737 100644 --- a/runtime/cpp/emboss_cpp_types.h +++ b/runtime/cpp/emboss_cpp_types.h @@ -21,6 +21,8 @@ #include #include +#include "runtime/cpp/emboss_defines.h" + namespace emboss { namespace support { @@ -51,12 +53,26 @@ struct FloatType<32> final { // LeastWidthInteger::Unsigned is the smallest uintNN_t type that can // hold n_bits or more. LeastWidthInteger::Signed is the corresponding // signed type. +#if EMBOSS_HAS_INT128 +template +struct LeastWidthInteger final { + static_assert(kBits <= 128, "Only bit sizes up to 128 are supported."); + using Unsigned = typename LeastWidthInteger::Unsigned; + using Signed = typename LeastWidthInteger::Signed; +}; +template <> +struct LeastWidthInteger<128> final { + using Unsigned = __uint128_t; + using Signed = __int128_t; +}; +#else template struct LeastWidthInteger final { static_assert(kBits <= 64, "Only bit sizes up to 64 are supported."); using Unsigned = typename LeastWidthInteger::Unsigned; using Signed = typename LeastWidthInteger::Signed; }; +#endif // EMBOSS_HAS_INT128 template <> struct LeastWidthInteger<64> final { using Unsigned = ::std::uint64_t; diff --git a/runtime/cpp/emboss_defines.h b/runtime/cpp/emboss_defines.h index 24b5af0..e76d231 100644 --- a/runtime/cpp/emboss_defines.h +++ b/runtime/cpp/emboss_defines.h @@ -311,4 +311,14 @@ #define EMBOSS_SYSTEM_IS_TWOS_COMPLEMENT 0 #endif // !defined(EMBOSS_SYSTEM_IS_TWOS_COMPLEMENT) +// Detect 128-bit integer support. +// __uint128_t and __int128_t are available on GCC and Clang on 64-bit targets. +#if !defined(EMBOSS_HAS_INT128) +#if defined(__SIZEOF_INT128__) +#define EMBOSS_HAS_INT128 1 +#else +#define EMBOSS_HAS_INT128 0 +#endif // defined(__SIZEOF_INT128__) +#endif // !defined(EMBOSS_HAS_INT128) + #endif // EMBOSS_RUNTIME_CPP_EMBOSS_DEFINES_H_ diff --git a/runtime/cpp/emboss_memory_util.h b/runtime/cpp/emboss_memory_util.h index fca71c9..77e6cb5 100644 --- a/runtime/cpp/emboss_memory_util.h +++ b/runtime/cpp/emboss_memory_util.h @@ -991,8 +991,13 @@ template class BitBlock final { static_assert(kBufferSizeInBits % 8 == 0, "BitBlock can only operate on byte buffers."); +#if EMBOSS_HAS_INT128 + static_assert(kBufferSizeInBits <= 128, + "BitBlock can only operate on buffers up to 128 bits."); +#else static_assert(kBufferSizeInBits <= 64, "BitBlock can only operate on small buffers."); +#endif // EMBOSS_HAS_INT128 public: using ValueType = typename LeastWidthInteger::Unsigned; diff --git a/runtime/cpp/test/emboss_bit_util_test.cc b/runtime/cpp/test/emboss_bit_util_test.cc index 52bff49..83db86c 100644 --- a/runtime/cpp/test/emboss_bit_util_test.cc +++ b/runtime/cpp/test/emboss_bit_util_test.cc @@ -26,6 +26,17 @@ TEST(ByteSwap, ByteSwap) { EXPECT_EQ(0x01020304U, ByteSwap(::std::uint32_t{0x04030201})); EXPECT_EQ(0x0102030405060708UL, ByteSwap(::std::uint64_t{0x0807060504030201UL})); +#if EMBOSS_HAS_INT128 + // Create 128-bit value 0x0f0e0d0c0b0a09080706050403020100 + __uint128_t val128 = + (static_cast<__uint128_t>(0x0807060504030201UL) << 64) | + static_cast<__uint128_t>(0x100f0e0d0c0b0a09UL); + // Expected result after swap: 0x090a0b0c0d0e0f10_0102030405060708 + __uint128_t expected128 = + (static_cast<__uint128_t>(0x090a0b0c0d0e0f10UL) << 64) | + static_cast<__uint128_t>(0x0102030405060708UL); + EXPECT_EQ(expected128, ByteSwap(val128)); +#endif // EMBOSS_HAS_INT128 } TEST(MaskToNBits, MaskToNBits) { @@ -36,6 +47,22 @@ TEST(MaskToNBits, MaskToNBits) { EXPECT_EQ(0xffffffffU, MaskToNBits(0xffffffffU, 32)); EXPECT_EQ(0xffffffffffffffffU, MaskToNBits(0xffffffffffffffffU, 64)); EXPECT_EQ(0xfU, MaskToNBits(::std::uint8_t{0xff}, 4)); +#if EMBOSS_HAS_INT128 + // Create a 128-bit value with all bits set + __uint128_t all_ones_128 = + (static_cast<__uint128_t>(0xffffffffffffffffUL) << 64) | + static_cast<__uint128_t>(0xffffffffffffffffUL); + // Mask to 72 bits + __uint128_t expected_72 = + (static_cast<__uint128_t>(0xffUL) << 64) | + static_cast<__uint128_t>(0xffffffffffffffffUL); + EXPECT_EQ(expected_72, MaskToNBits(all_ones_128, 72)); + // Mask to 128 bits should return the original value + EXPECT_EQ(all_ones_128, MaskToNBits(all_ones_128, 128)); + // Mask to 64 bits should return just the lower 64 bits + __uint128_t expected_64 = static_cast<__uint128_t>(0xffffffffffffffffUL); + EXPECT_EQ(expected_64, MaskToNBits(all_ones_128, 64)); +#endif // EMBOSS_HAS_INT128 } TEST(IsPowerOfTwo, IsPowerOfTwo) { @@ -71,6 +98,18 @@ TEST(IsPowerOfTwo, IsPowerOfTwo) { EXPECT_FALSE(IsPowerOfTwo(::std::int8_t{-128})); EXPECT_FALSE(IsPowerOfTwo(::std::int8_t{65})); EXPECT_FALSE(IsPowerOfTwo(::std::int8_t{127})); +#if EMBOSS_HAS_INT128 + // Test powers of two for 128-bit values + __uint128_t one_128 = 1; + EXPECT_TRUE(IsPowerOfTwo(one_128)); + EXPECT_TRUE(IsPowerOfTwo(one_128 << 64)); + EXPECT_TRUE(IsPowerOfTwo(one_128 << 100)); + EXPECT_TRUE(IsPowerOfTwo(one_128 << 127)); + EXPECT_FALSE(IsPowerOfTwo(static_cast<__uint128_t>(0))); + EXPECT_FALSE(IsPowerOfTwo(static_cast<__uint128_t>(3))); + EXPECT_FALSE(IsPowerOfTwo((one_128 << 127) - 1)); + EXPECT_FALSE(IsPowerOfTwo((one_128 << 64) + 1)); +#endif // EMBOSS_HAS_INT128 } #if defined(EMBOSS_LITTLE_ENDIAN_TO_NATIVE) diff --git a/runtime/cpp/test/emboss_cpp_types_test.cc b/runtime/cpp/test/emboss_cpp_types_test.cc index e0d6308..c88c640 100644 --- a/runtime/cpp/test/emboss_cpp_types_test.cc +++ b/runtime/cpp/test/emboss_cpp_types_test.cc @@ -100,6 +100,24 @@ TEST(LeastWidthInteger, Types) { ::std::is_same::Unsigned, ::std::uint64_t>::value)); EXPECT_TRUE( (::std::is_same::Signed, ::std::int64_t>::value)); +#if EMBOSS_HAS_INT128 + EXPECT_TRUE(( + ::std::is_same::Unsigned, __uint128_t>::value)); + EXPECT_TRUE( + (::std::is_same::Signed, __int128_t>::value)); + EXPECT_TRUE(( + ::std::is_same::Unsigned, __uint128_t>::value)); + EXPECT_TRUE( + (::std::is_same::Signed, __int128_t>::value)); + EXPECT_TRUE(( + ::std::is_same::Unsigned, __uint128_t>::value)); + EXPECT_TRUE( + (::std::is_same::Signed, __int128_t>::value)); + EXPECT_TRUE(( + ::std::is_same::Unsigned, __uint128_t>::value)); + EXPECT_TRUE( + (::std::is_same::Signed, __int128_t>::value)); +#endif // EMBOSS_HAS_INT128 } TEST(IsAliasSafe, CharTypes) { diff --git a/runtime/cpp/test/emboss_prelude_test.cc b/runtime/cpp/test/emboss_prelude_test.cc index 889daae..fe92782 100644 --- a/runtime/cpp/test/emboss_prelude_test.cc +++ b/runtime/cpp/test/emboss_prelude_test.cc @@ -115,9 +115,15 @@ void CheckViewSizeInBits() { return; } +#if EMBOSS_HAS_INT128 +TEST(UIntView, SizeInBits) { CheckViewSizeInBits(); } + +TEST(IntView, SizeInBits) { CheckViewSizeInBits(); } +#else TEST(UIntView, SizeInBits) { CheckViewSizeInBits(); } TEST(IntView, SizeInBits) { CheckViewSizeInBits(); } +#endif // EMBOSS_HAS_INT128 TEST(BcdView, SizeInBits) { CheckViewSizeInBits(); } @@ -150,6 +156,18 @@ TEST(UIntView, ValueType) { EXPECT_TRUE((::std::is_same< /**/ ::std::uint64_t, UIntView, BitBlockType>::ValueType>::value)); +#if EMBOSS_HAS_INT128 + using BitBlockType128 = BitBlockN<128>; + EXPECT_TRUE((::std::is_same< + __uint128_t, + UIntView, BitBlockType128>::ValueType>::value)); + EXPECT_TRUE((::std::is_same< + __uint128_t, + UIntView, BitBlockType128>::ValueType>::value)); + EXPECT_TRUE((::std::is_same< + __uint128_t, + UIntView, BitBlockType128>::ValueType>::value)); +#endif // EMBOSS_HAS_INT128 } TEST(UIntView, CouldWriteValue) { @@ -346,6 +364,18 @@ TEST(IntView, ValueType) { EXPECT_TRUE((::std::is_same< /**/ ::std::int64_t, IntView, BitBlockType>::ValueType>::value)); +#if EMBOSS_HAS_INT128 + using BitBlockType128 = BitBlockN<128>; + EXPECT_TRUE((::std::is_same< + __int128_t, + IntView, BitBlockType128>::ValueType>::value)); + EXPECT_TRUE((::std::is_same< + __int128_t, + IntView, BitBlockType128>::ValueType>::value)); + EXPECT_TRUE((::std::is_same< + __int128_t, + IntView, BitBlockType128>::ValueType>::value)); +#endif // EMBOSS_HAS_INT128 } TEST(IntView, CouldWriteValue) { diff --git a/testdata/BUILD b/testdata/BUILD index 95716da..1587cb9 100644 --- a/testdata/BUILD +++ b/testdata/BUILD @@ -64,6 +64,7 @@ filegroup( "importer.emb", "importer2.emb", "int_sizes.emb", + "int128_sizes.emb", "nested_structure.emb", "next_keyword.emb", "no_cpp_namespace.emb", @@ -236,6 +237,13 @@ emboss_cc_library( ], ) +emboss_cc_library( + name = "int128_sizes_emboss", + srcs = [ + "int128_sizes.emb", + ], +) + emboss_cc_library( name = "dynamic_size_emboss", srcs = [ diff --git a/testdata/int128_sizes.emb b/testdata/int128_sizes.emb new file mode 100644 index 0000000..120ef7a --- /dev/null +++ b/testdata/int128_sizes.emb @@ -0,0 +1,86 @@ +# Copyright 2024 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. + +-- Test struct for 65-128 bit Ints and UInts. +-- This file is only usable on systems with 128-bit integer support. + +[$default byte_order: "LittleEndian"] +[(cpp) namespace: "emboss::test"] + + +struct UInt128Sizes: + 0 [+9] UInt nine_byte + 9 [+10] UInt ten_byte + 19 [+11] UInt eleven_byte + 30 [+12] UInt twelve_byte + 42 [+13] UInt thirteen_byte + 55 [+14] UInt fourteen_byte + 69 [+15] UInt fifteen_byte + 84 [+16] UInt sixteen_byte + + +struct Int128Sizes: + 0 [+9] Int nine_byte + 9 [+10] Int ten_byte + 19 [+11] Int eleven_byte + 30 [+12] Int twelve_byte + 42 [+13] Int thirteen_byte + 55 [+14] Int fourteen_byte + 69 [+15] Int fifteen_byte + 84 [+16] Int sixteen_byte + + +struct BigEndianUInt128Sizes: + [$default byte_order: "BigEndian"] + 0 [+9] UInt nine_byte + 9 [+10] UInt ten_byte + 19 [+11] UInt eleven_byte + 30 [+12] UInt twelve_byte + 42 [+13] UInt thirteen_byte + 55 [+14] UInt fourteen_byte + 69 [+15] UInt fifteen_byte + 84 [+16] UInt sixteen_byte + + +struct BigEndianInt128Sizes: + [$default byte_order: "BigEndian"] + 0 [+9] Int nine_byte + 9 [+10] Int ten_byte + 19 [+11] Int eleven_byte + 30 [+12] Int twelve_byte + 42 [+13] Int thirteen_byte + 55 [+14] Int fourteen_byte + 69 [+15] Int fifteen_byte + 84 [+16] Int sixteen_byte + + +struct UInt128ArraySizes: + 0 [+18] UInt:72[2] nine_byte + 18 [+20] UInt:80[2] ten_byte + 38 [+22] UInt:88[2] eleven_byte + 60 [+24] UInt:96[2] twelve_byte + 84 [+26] UInt:104[2] thirteen_byte + 110 [+28] UInt:112[2] fourteen_byte + 138 [+30] UInt:120[2] fifteen_byte + 168 [+32] UInt:128[2] sixteen_byte + + +struct AnonymousBits128: + 0 [+16] bits: + 0 [+65] UInt lower_65_bits + 65 [+63] UInt upper_63_bits + + 16 [+16] bits: + 0 [+64] UInt lower_64_bits + 64 [+64] UInt upper_64_bits From 384ffceb47c52f50ff5c6261644615501189d957 Mon Sep 17 00:00:00 2001 From: Aaron Webster Date: Wed, 3 Dec 2025 13:51:33 -0800 Subject: [PATCH 2/3] Refactor: Apply consistent formatting and fix include order --- compiler/util/parser_types.py | 2 +- runtime/cpp/emboss_defines.h | 2 +- runtime/cpp/test/emboss_cpp_types_test.cc | 20 +++--- runtime/cpp/test/emboss_prelude_test.cc | 81 ++++++++++++----------- testdata/__init__.py | 13 ---- testdata/int128_sizes.emb | 74 ++++++++++----------- 6 files changed, 91 insertions(+), 101 deletions(-) diff --git a/compiler/util/parser_types.py b/compiler/util/parser_types.py index a3ef34d..89cb186 100644 --- a/compiler/util/parser_types.py +++ b/compiler/util/parser_types.py @@ -15,7 +15,7 @@ """Types related to the LR(1) parser. This module contains types used by the LR(1) parser, which are also used in -other parts of the compiler: +other parts of the compiler: SourcePosition: a position (zero-width) within a source file. SourceLocation: a span within a source file. diff --git a/runtime/cpp/emboss_defines.h b/runtime/cpp/emboss_defines.h index e76d231..14b578b 100644 --- a/runtime/cpp/emboss_defines.h +++ b/runtime/cpp/emboss_defines.h @@ -245,7 +245,7 @@ // GCC's attribute syntax disallows parentheses in that particular position. #if !defined(EMBOSS_ALIAS_SAFE_POINTER_CAST) #define EMBOSS_ALIAS_SAFE_POINTER_CAST(t, x) \ - reinterpret_cast((x)) + reinterpret_cast((x)) #endif // !defined(EMBOSS_LITTLE_ENDIAN_TO_NATIVE) #endif // !defined(__INTEL_COMPILER) diff --git a/runtime/cpp/test/emboss_cpp_types_test.cc b/runtime/cpp/test/emboss_cpp_types_test.cc index c88c640..41306b9 100644 --- a/runtime/cpp/test/emboss_cpp_types_test.cc +++ b/runtime/cpp/test/emboss_cpp_types_test.cc @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include - #include "runtime/cpp/emboss_cpp_types.h" +#include + #include "gtest/gtest.h" namespace emboss { @@ -101,20 +101,20 @@ TEST(LeastWidthInteger, Types) { EXPECT_TRUE( (::std::is_same::Signed, ::std::int64_t>::value)); #if EMBOSS_HAS_INT128 - EXPECT_TRUE(( - ::std::is_same::Unsigned, __uint128_t>::value)); + EXPECT_TRUE( + (::std::is_same::Unsigned, __uint128_t>::value)); EXPECT_TRUE( (::std::is_same::Signed, __int128_t>::value)); - EXPECT_TRUE(( - ::std::is_same::Unsigned, __uint128_t>::value)); + EXPECT_TRUE( + (::std::is_same::Unsigned, __uint128_t>::value)); EXPECT_TRUE( (::std::is_same::Signed, __int128_t>::value)); - EXPECT_TRUE(( - ::std::is_same::Unsigned, __uint128_t>::value)); + EXPECT_TRUE( + (::std::is_same::Unsigned, __uint128_t>::value)); EXPECT_TRUE( (::std::is_same::Signed, __int128_t>::value)); - EXPECT_TRUE(( - ::std::is_same::Unsigned, __uint128_t>::value)); + EXPECT_TRUE( + (::std::is_same::Unsigned, __uint128_t>::value)); EXPECT_TRUE( (::std::is_same::Signed, __int128_t>::value)); #endif // EMBOSS_HAS_INT128 diff --git a/runtime/cpp/test/emboss_prelude_test.cc b/runtime/cpp/test/emboss_prelude_test.cc index fe92782..a46617a 100644 --- a/runtime/cpp/test/emboss_prelude_test.cc +++ b/runtime/cpp/test/emboss_prelude_test.cc @@ -38,9 +38,9 @@ using ViewParameters = ::emboss::support::FixedSizeViewParameters< TEST(FlagView, Methods) { ::std::uint8_t byte = 0; - auto flag_view = - FlagView, OffsetBitBlock>>{BitBlockN<8>{ - ReadWriteContiguousBuffer{&byte, 1}}.GetOffsetStorage<1, 0>(0, 1)}; + auto flag_view = FlagView, OffsetBitBlock>>{ + BitBlockN<8>{ReadWriteContiguousBuffer{&byte, 1}}.GetOffsetStorage<1, 0>( + 0, 1)}; EXPECT_FALSE(flag_view.Read()); byte = 0xfe; EXPECT_FALSE(flag_view.Read()); @@ -60,8 +60,9 @@ TEST(FlagView, Methods) { TEST(FlagView, TextDecode) { ::std::uint8_t byte = 0; const auto flag_view = - FlagView, OffsetBitBlock>>{BitBlockN<8>{ - ReadWriteContiguousBuffer{&byte, 1}}.GetOffsetStorage<1, 0>(0, 1)}; + FlagView, OffsetBitBlock>>{ + BitBlockN<8>{ReadWriteContiguousBuffer{&byte, 1}} + .GetOffsetStorage<1, 0>(0, 1)}; EXPECT_FALSE(UpdateFromText(flag_view, "")); EXPECT_FALSE(UpdateFromText(flag_view, "FALSE")); EXPECT_FALSE(UpdateFromText(flag_view, "TRUE")); @@ -85,8 +86,9 @@ TEST(FlagView, TextDecode) { TEST(FlagView, TextEncode) { ::std::uint8_t byte = 0; const auto flag_view = - FlagView, OffsetBitBlock>>{BitBlockN<8>{ - ReadWriteContiguousBuffer{&byte, 1}}.GetOffsetStorage<1, 0>(0, 1)}; + FlagView, OffsetBitBlock>>{ + BitBlockN<8>{ReadWriteContiguousBuffer{&byte, 1}} + .GetOffsetStorage<1, 0>(0, 1)}; EXPECT_EQ("false", WriteToString(flag_view)); byte = 1; EXPECT_EQ("true", WriteToString(flag_view)); @@ -158,15 +160,18 @@ TEST(UIntView, ValueType) { UIntView, BitBlockType>::ValueType>::value)); #if EMBOSS_HAS_INT128 using BitBlockType128 = BitBlockN<128>; - EXPECT_TRUE((::std::is_same< - __uint128_t, - UIntView, BitBlockType128>::ValueType>::value)); - EXPECT_TRUE((::std::is_same< - __uint128_t, - UIntView, BitBlockType128>::ValueType>::value)); - EXPECT_TRUE((::std::is_same< - __uint128_t, - UIntView, BitBlockType128>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same< + __uint128_t, + UIntView, BitBlockType128>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same< + __uint128_t, + UIntView, BitBlockType128>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same< + __uint128_t, + UIntView, BitBlockType128>::ValueType>::value)); #endif // EMBOSS_HAS_INT128 } @@ -296,9 +301,9 @@ TEST(UIntView, NonPowerOfTwoSizeInsufficientBuffer) { TEST(UIntView, NonByteSize) { ::std::vector bytes = {{0x00, 0x00, 0x80, 0x80}}; auto uint23_view = - UIntView, OffsetBitBlock>>{BitBlockN<24>{ - ReadWriteContiguousBuffer{bytes.data(), - 3}}.GetOffsetStorage<1, 0>(0, 23)}; + UIntView, OffsetBitBlock>>{ + BitBlockN<24>{ReadWriteContiguousBuffer{bytes.data(), 3}} + .GetOffsetStorage<1, 0>(0, 23)}; EXPECT_EQ(0x0U, uint23_view.Read()); EXPECT_FALSE(uint23_view.CouldWriteValue(0x800f0e)); EXPECT_FALSE(uint23_view.CouldWriteValue(0x800000)); @@ -366,15 +371,15 @@ TEST(IntView, ValueType) { IntView, BitBlockType>::ValueType>::value)); #if EMBOSS_HAS_INT128 using BitBlockType128 = BitBlockN<128>; - EXPECT_TRUE((::std::is_same< - __int128_t, - IntView, BitBlockType128>::ValueType>::value)); - EXPECT_TRUE((::std::is_same< - __int128_t, - IntView, BitBlockType128>::ValueType>::value)); - EXPECT_TRUE((::std::is_same< - __int128_t, - IntView, BitBlockType128>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<__int128_t, IntView, + BitBlockType128>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<__int128_t, IntView, + BitBlockType128>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<__int128_t, IntView, + BitBlockType128>::ValueType>::value)); #endif // EMBOSS_HAS_INT128 } @@ -509,10 +514,9 @@ TEST(IntView, NonPowerOfTwoSizeInsufficientBuffer) { TEST(IntView, NonByteSize) { ::std::vector bytes = {{0x00, 0x00, 0x80, 0x80}}; - auto int23_view = - IntView, OffsetBitBlock>>{BitBlockN<24>{ - ReadWriteContiguousBuffer{bytes.data(), - 3}}.GetOffsetStorage<1, 0>(0, 23)}; + auto int23_view = IntView, OffsetBitBlock>>{ + BitBlockN<24>{ReadWriteContiguousBuffer{bytes.data(), 3}} + .GetOffsetStorage<1, 0>(0, 23)}; EXPECT_EQ(0x0, int23_view.Read()); EXPECT_FALSE(int23_view.CouldWriteValue(0x400f0e)); #if EMBOSS_CHECK_ABORTS @@ -533,9 +537,9 @@ TEST(IntView, NonByteSize) { TEST(IntView, OneBit) { ::std::uint8_t bytes[] = {0xfe}; - auto int1_view = - IntView, OffsetBitBlock>>{BitBlockN<8>{ - ReadWriteContiguousBuffer{bytes, 1}}.GetOffsetStorage<1, 0>(0, 1)}; + auto int1_view = IntView, OffsetBitBlock>>{ + BitBlockN<8>{ReadWriteContiguousBuffer{bytes, 1}}.GetOffsetStorage<1, 0>( + 0, 1)}; EXPECT_TRUE(int1_view.Ok()); EXPECT_TRUE(int1_view.IsComplete()); EXPECT_EQ(0, int1_view.Read()); @@ -780,10 +784,9 @@ TEST(BcdView, NonPowerOfTwoSizeInsufficientBuffer) { TEST(BcdView, NonByteSize) { ::std::vector bytes = {{0x00, 0x00, 0x80, 0x80}}; - auto bcd23_view = - BcdView, OffsetBitBlock>>{BitBlockN<24>{ - ReadWriteContiguousBuffer{bytes.data(), - 3}}.GetOffsetStorage<1, 0>(0, 23)}; + auto bcd23_view = BcdView, OffsetBitBlock>>{ + BitBlockN<24>{ReadWriteContiguousBuffer{bytes.data(), 3}} + .GetOffsetStorage<1, 0>(0, 23)}; EXPECT_EQ(0x0U, bcd23_view.Read()); EXPECT_FALSE(bcd23_view.CouldWriteValue(800000)); EXPECT_TRUE(bcd23_view.CouldWriteValue(799999)); diff --git a/testdata/__init__.py b/testdata/__init__.py index 086a24e..e69de29 100644 --- a/testdata/__init__.py +++ b/testdata/__init__.py @@ -1,13 +0,0 @@ -# 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. diff --git a/testdata/int128_sizes.emb b/testdata/int128_sizes.emb index 120ef7a..1e988c2 100644 --- a/testdata/int128_sizes.emb +++ b/testdata/int128_sizes.emb @@ -20,49 +20,49 @@ struct UInt128Sizes: - 0 [+9] UInt nine_byte - 9 [+10] UInt ten_byte - 19 [+11] UInt eleven_byte - 30 [+12] UInt twelve_byte - 42 [+13] UInt thirteen_byte - 55 [+14] UInt fourteen_byte - 69 [+15] UInt fifteen_byte - 84 [+16] UInt sixteen_byte + 0 [+9] UInt nine_byte + 9 [+10] UInt ten_byte + 19 [+11] UInt eleven_byte + 30 [+12] UInt twelve_byte + 42 [+13] UInt thirteen_byte + 55 [+14] UInt fourteen_byte + 69 [+15] UInt fifteen_byte + 84 [+16] UInt sixteen_byte struct Int128Sizes: - 0 [+9] Int nine_byte - 9 [+10] Int ten_byte - 19 [+11] Int eleven_byte - 30 [+12] Int twelve_byte - 42 [+13] Int thirteen_byte - 55 [+14] Int fourteen_byte - 69 [+15] Int fifteen_byte - 84 [+16] Int sixteen_byte + 0 [+9] Int nine_byte + 9 [+10] Int ten_byte + 19 [+11] Int eleven_byte + 30 [+12] Int twelve_byte + 42 [+13] Int thirteen_byte + 55 [+14] Int fourteen_byte + 69 [+15] Int fifteen_byte + 84 [+16] Int sixteen_byte struct BigEndianUInt128Sizes: [$default byte_order: "BigEndian"] - 0 [+9] UInt nine_byte - 9 [+10] UInt ten_byte - 19 [+11] UInt eleven_byte - 30 [+12] UInt twelve_byte - 42 [+13] UInt thirteen_byte - 55 [+14] UInt fourteen_byte - 69 [+15] UInt fifteen_byte - 84 [+16] UInt sixteen_byte + 0 [+9] UInt nine_byte + 9 [+10] UInt ten_byte + 19 [+11] UInt eleven_byte + 30 [+12] UInt twelve_byte + 42 [+13] UInt thirteen_byte + 55 [+14] UInt fourteen_byte + 69 [+15] UInt fifteen_byte + 84 [+16] UInt sixteen_byte struct BigEndianInt128Sizes: [$default byte_order: "BigEndian"] - 0 [+9] Int nine_byte - 9 [+10] Int ten_byte - 19 [+11] Int eleven_byte - 30 [+12] Int twelve_byte - 42 [+13] Int thirteen_byte - 55 [+14] Int fourteen_byte - 69 [+15] Int fifteen_byte - 84 [+16] Int sixteen_byte + 0 [+9] Int nine_byte + 9 [+10] Int ten_byte + 19 [+11] Int eleven_byte + 30 [+12] Int twelve_byte + 42 [+13] Int thirteen_byte + 55 [+14] Int fourteen_byte + 69 [+15] Int fifteen_byte + 84 [+16] Int sixteen_byte struct UInt128ArraySizes: @@ -77,10 +77,10 @@ struct UInt128ArraySizes: struct AnonymousBits128: - 0 [+16] bits: - 0 [+65] UInt lower_65_bits - 65 [+63] UInt upper_63_bits + 0 [+16] bits: + 0 [+65] UInt lower_65_bits + 65 [+63] UInt upper_63_bits 16 [+16] bits: - 0 [+64] UInt lower_64_bits - 64 [+64] UInt upper_64_bits + 0 [+64] UInt lower_64_bits + 64 [+64] UInt upper_64_bits From c801de5ebac0e3363570c23f3857848011d8d4e3 Mon Sep 17 00:00:00 2001 From: Aaron Webster Date: Thu, 29 Jan 2026 14:34:52 -0800 Subject: [PATCH 3/3] Update prelude to support 128-bit integers and enhance tests - Update prelude.emb to allow UInt and Int sizes up to 128 bits (previously limited to 64 bits). Added documentation noting that 128-bit support requires platform support (__uint128_t/__int128_t). - Update constraints_test.py to test with sizes > 128 bits now that 128-bit fields are valid. - Add comprehensive unit tests for 128-bit integer support: - emboss_bit_util_test.cc: ByteSwap, MaskToNBits, IsPowerOfTwo tests - emboss_memory_util_test.cc: MemoryAccessor, BitBlock tests for 128-bit values including non-full-width (72, 96 bits) and cross-64-bit-boundary operations - emboss_prelude_test.cc: UIntView and IntView tests for 128-bit read/write, sign extension, and CouldWriteValue - Add int128_sizes_test.cc integration test for generated code with 128-bit integer fields (UInt128Sizes, Int128Sizes, big-endian variants, and arrays). - Update int128_sizes.emb documentation and remove AnonymousBits128 struct (128-bit bits types require separate implementation work). --- compiler/back_end/cpp/BUILD | 12 + .../cpp/testcode/int128_sizes_test.cc | 342 ++++++++++++++++++ compiler/front_end/constraints_test.py | 7 +- compiler/front_end/prelude.emb | 14 +- runtime/cpp/test/emboss_bit_util_test.cc | 78 ++++ runtime/cpp/test/emboss_memory_util_test.cc | 262 ++++++++++++++ runtime/cpp/test/emboss_prelude_test.cc | 189 +++++++++- testdata/int128_sizes.emb | 17 +- 8 files changed, 907 insertions(+), 14 deletions(-) create mode 100644 compiler/back_end/cpp/testcode/int128_sizes_test.cc diff --git a/compiler/back_end/cpp/BUILD b/compiler/back_end/cpp/BUILD index bdd6e61..e7f6d22 100644 --- a/compiler/back_end/cpp/BUILD +++ b/compiler/back_end/cpp/BUILD @@ -191,6 +191,18 @@ emboss_cc_test( ], ) +emboss_cc_test( + name = "int128_sizes_test", + srcs = [ + "testcode/int128_sizes_test.cc", + ], + deps = [ + "//runtime/cpp:cpp_utils", + "//testdata:int128_sizes_emboss", + "@com_google_googletest//:gtest_main", + ], +) + emboss_cc_test( name = "float_test", srcs = [ diff --git a/compiler/back_end/cpp/testcode/int128_sizes_test.cc b/compiler/back_end/cpp/testcode/int128_sizes_test.cc new file mode 100644 index 0000000..5d2f7b6 --- /dev/null +++ b/compiler/back_end/cpp/testcode/int128_sizes_test.cc @@ -0,0 +1,342 @@ +// Copyright 2024 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. + +// Tests for 128-bit integer support in generated Emboss code. +// This file tests structures defined in int128_sizes.emb with UInt and Int +// fields larger than 64 bits. + +#include + +#include +#include + +#include "gtest/gtest.h" +#include "runtime/cpp/emboss_defines.h" + +#if EMBOSS_HAS_INT128 +#include "testdata/int128_sizes.emb.h" + +namespace emboss { +namespace test { +namespace { + +// Test data for UInt128Sizes (100 bytes total) +alignas(16) static const ::std::uint8_t kUInt128Sizes[100] = { + // 0:9 nine_byte (72 bits) = 0x090807060504030201 + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + // 9:19 ten_byte (80 bits) + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, + // 19:30 eleven_byte (88 bits) + 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, + // 30:42 twelve_byte (96 bits) + 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, + // 42:55 thirteen_byte (104 bits) + 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, + 0x37, + // 55:69 fourteen_byte (112 bits) + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, + 0x44, 0x45, + // 69:84 fifteen_byte (120 bits) + 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, + 0x52, 0x53, 0x54, + // 84:100 sixteen_byte (128 bits) + 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, + 0x61, 0x62, 0x63, 0x64, +}; + +TEST(UInt128SizesView, CanReadSizes) { + auto view = MakeUInt128SizesView(kUInt128Sizes, sizeof kUInt128Sizes); + EXPECT_TRUE(view.Ok()); + + // Nine byte (72 bits) - little endian + __uint128_t expected_nine = + (static_cast<__uint128_t>(0x09UL) << 64) | + static_cast<__uint128_t>(0x0807060504030201UL); + EXPECT_EQ(expected_nine, view.nine_byte().Read()); + EXPECT_EQ(16U, sizeof(view.nine_byte().Read())); + + // Ten byte (80 bits) + __uint128_t expected_ten = + (static_cast<__uint128_t>(0x1312UL) << 64) | + static_cast<__uint128_t>(0x11100f0e0d0c0b0aUL); + EXPECT_EQ(expected_ten, view.ten_byte().Read()); + + // Sixteen byte (128 bits) + __uint128_t expected_sixteen = + (static_cast<__uint128_t>(0x6463626160'5f5e5dUL) << 64) | + static_cast<__uint128_t>(0x5c5b5a5958'575655UL); + EXPECT_EQ(expected_sixteen, view.sixteen_byte().Read()); +} + +TEST(UInt128SizesWriter, CanWriteSizes) { + ::std::uint8_t buffer[sizeof kUInt128Sizes] = {}; + auto writer = UInt128SizesWriter(buffer, sizeof buffer); + + // Write nine byte value + __uint128_t nine_value = + (static_cast<__uint128_t>(0xffUL) << 64) | + static_cast<__uint128_t>(0xfedcba9876543210UL); + writer.nine_byte().Write(nine_value); + + // Verify written bytes (little-endian) + EXPECT_EQ(0x10, buffer[0]); + EXPECT_EQ(0x32, buffer[1]); + EXPECT_EQ(0x54, buffer[2]); + EXPECT_EQ(0x76, buffer[3]); + EXPECT_EQ(0x98, buffer[4]); + EXPECT_EQ(0xba, buffer[5]); + EXPECT_EQ(0xdc, buffer[6]); + EXPECT_EQ(0xfe, buffer[7]); + EXPECT_EQ(0xff, buffer[8]); + + // Read back and verify + auto view = MakeUInt128SizesView(buffer, sizeof buffer); + EXPECT_EQ(nine_value, view.nine_byte().Read()); +} + +TEST(UInt128SizesView, CanReadMaxValues) { + ::std::uint8_t buffer[100]; + auto writer = UInt128SizesWriter(buffer, sizeof buffer); + + // Max 72-bit value + __uint128_t max_72 = (static_cast<__uint128_t>(1) << 72) - 1; + writer.nine_byte().Write(max_72); + EXPECT_EQ(max_72, + MakeUInt128SizesView(buffer, sizeof buffer).nine_byte().Read()); + + // Max 128-bit value + __uint128_t max_128 = + (static_cast<__uint128_t>(0xffffffffffffffffUL) << 64) | + static_cast<__uint128_t>(0xffffffffffffffffUL); + writer.sixteen_byte().Write(max_128); + EXPECT_EQ(max_128, + MakeUInt128SizesView(buffer, sizeof buffer).sixteen_byte().Read()); +} + +// Int128 tests with negative values +alignas(16) static const ::std::uint8_t kInt128SizesNegativeOne[100] = { + // All bytes are 0xff, representing -1 in two's complement for all sizes + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // nine_byte + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // ten_byte + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, // eleven_byte + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, // twelve_byte + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, // thirteen_byte + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, // fourteen_byte + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, // fifteen_byte + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, // sixteen_byte +}; + +TEST(Int128SizesView, CanReadNegativeOne) { + auto view = MakeInt128SizesView(kInt128SizesNegativeOne, + sizeof kInt128SizesNegativeOne); + EXPECT_TRUE(view.Ok()); + + EXPECT_EQ(static_cast<__int128_t>(-1), view.nine_byte().Read()); + EXPECT_EQ(static_cast<__int128_t>(-1), view.ten_byte().Read()); + EXPECT_EQ(static_cast<__int128_t>(-1), view.eleven_byte().Read()); + EXPECT_EQ(static_cast<__int128_t>(-1), view.twelve_byte().Read()); + EXPECT_EQ(static_cast<__int128_t>(-1), view.thirteen_byte().Read()); + EXPECT_EQ(static_cast<__int128_t>(-1), view.fourteen_byte().Read()); + EXPECT_EQ(static_cast<__int128_t>(-1), view.fifteen_byte().Read()); + EXPECT_EQ(static_cast<__int128_t>(-1), view.sixteen_byte().Read()); +} + +TEST(Int128SizesWriter, CanWriteNegativeOne) { + ::std::uint8_t buffer[sizeof kInt128SizesNegativeOne]; + auto writer = Int128SizesWriter(buffer, sizeof buffer); + + writer.nine_byte().Write(static_cast<__int128_t>(-1)); + writer.ten_byte().Write(static_cast<__int128_t>(-1)); + writer.eleven_byte().Write(static_cast<__int128_t>(-1)); + writer.twelve_byte().Write(static_cast<__int128_t>(-1)); + writer.thirteen_byte().Write(static_cast<__int128_t>(-1)); + writer.fourteen_byte().Write(static_cast<__int128_t>(-1)); + writer.fifteen_byte().Write(static_cast<__int128_t>(-1)); + writer.sixteen_byte().Write(static_cast<__int128_t>(-1)); + + EXPECT_EQ(::std::vector( + kInt128SizesNegativeOne, + kInt128SizesNegativeOne + sizeof kInt128SizesNegativeOne), + ::std::vector(buffer, buffer + sizeof buffer)); +} + +TEST(Int128SizesView, CanReadMinMaxValues) { + ::std::uint8_t buffer[100]; + auto writer = Int128SizesWriter(buffer, sizeof buffer); + + // Test 72-bit signed min/max + __int128_t max_72 = (static_cast<__int128_t>(1) << 71) - 1; + __int128_t min_72 = -(static_cast<__int128_t>(1) << 71); + writer.nine_byte().Write(max_72); + EXPECT_EQ(max_72, + MakeInt128SizesView(buffer, sizeof buffer).nine_byte().Read()); + writer.nine_byte().Write(min_72); + EXPECT_EQ(min_72, + MakeInt128SizesView(buffer, sizeof buffer).nine_byte().Read()); + + // Test 128-bit signed min/max + __int128_t max_128 = static_cast<__int128_t>( + (static_cast<__uint128_t>(0x7fffffffffffffffUL) << 64) | + static_cast<__uint128_t>(0xffffffffffffffffUL)); + __int128_t min_128 = static_cast<__int128_t>(1) << 127; + + writer.sixteen_byte().Write(max_128); + EXPECT_EQ( + max_128, + MakeInt128SizesView(buffer, sizeof buffer).sixteen_byte().Read()); + writer.sixteen_byte().Write(min_128); + EXPECT_EQ( + min_128, + MakeInt128SizesView(buffer, sizeof buffer).sixteen_byte().Read()); +} + +// Test big-endian 128-bit structures +TEST(BigEndianUInt128SizesView, CanReadAndWrite) { + ::std::uint8_t buffer[100] = {}; + auto writer = BigEndianUInt128SizesWriter(buffer, sizeof buffer); + + // Write a recognizable value to nine_byte (72 bits) + __uint128_t nine_value = + (static_cast<__uint128_t>(0x01UL) << 64) | + static_cast<__uint128_t>(0x0203040506070809UL); + writer.nine_byte().Write(nine_value); + + // The bytes should be in big-endian order + EXPECT_EQ(0x01, buffer[0]); + EXPECT_EQ(0x02, buffer[1]); + EXPECT_EQ(0x09, buffer[8]); + + // Read back and verify + auto view = MakeBigEndianUInt128SizesView(buffer, sizeof buffer); + EXPECT_EQ(nine_value, view.nine_byte().Read()); +} + +TEST(BigEndianInt128SizesView, CanReadNegativeOne) { + ::std::uint8_t buffer[100]; + ::std::memset(buffer, 0xff, sizeof buffer); + + auto view = MakeBigEndianInt128SizesView(buffer, sizeof buffer); + EXPECT_EQ(static_cast<__int128_t>(-1), view.nine_byte().Read()); + EXPECT_EQ(static_cast<__int128_t>(-1), view.sixteen_byte().Read()); +} + +// Test arrays of 128-bit values +TEST(UInt128ArraySizesView, CanReadAndWriteArrays) { + ::std::uint8_t buffer[200] = {}; + auto writer = UInt128ArraySizesWriter(buffer, sizeof buffer); + + // Write to first element of nine_byte array (72-bit elements) + __uint128_t value_0 = + (static_cast<__uint128_t>(0xffUL) << 64) | + static_cast<__uint128_t>(0x0102030405060708UL); + writer.nine_byte()[0].Write(value_0); + + __uint128_t value_1 = + (static_cast<__uint128_t>(0xeeUL) << 64) | + static_cast<__uint128_t>(0x1112131415161718UL); + writer.nine_byte()[1].Write(value_1); + + auto view = MakeUInt128ArraySizesView(buffer, sizeof buffer); + EXPECT_EQ(value_0, view.nine_byte()[0].Read()); + EXPECT_EQ(value_1, view.nine_byte()[1].Read()); + + // Test sixteen_byte array (128-bit elements) + __uint128_t sixteen_0 = + (static_cast<__uint128_t>(0xfedcba9876543210UL) << 64) | + static_cast<__uint128_t>(0x0123456789abcdefUL); + __uint128_t sixteen_1 = + (static_cast<__uint128_t>(0x0123456789abcdefUL) << 64) | + static_cast<__uint128_t>(0xfedcba9876543210UL); + writer.sixteen_byte()[0].Write(sixteen_0); + writer.sixteen_byte()[1].Write(sixteen_1); + + EXPECT_EQ(sixteen_0, view.sixteen_byte()[0].Read()); + EXPECT_EQ(sixteen_1, view.sixteen_byte()[1].Read()); +} + +TEST(UInt128SizesView, CopyFrom) { + ::std::uint8_t buf_x[sizeof kUInt128Sizes] = {}; + ::std::uint8_t buf_y[sizeof kUInt128Sizes] = {}; + + auto x = UInt128SizesWriter(buf_x, sizeof buf_x); + auto y = UInt128SizesWriter(buf_y, sizeof buf_y); + + __uint128_t value = + (static_cast<__uint128_t>(0xfedcba9876543210UL) << 64) | + static_cast<__uint128_t>(0x0123456789abcdefUL); + x.sixteen_byte().Write(value); + EXPECT_NE(x.sixteen_byte().Read(), y.sixteen_byte().Read()); + y.sixteen_byte().CopyFrom(x.sixteen_byte()); + EXPECT_EQ(x.sixteen_byte().Read(), y.sixteen_byte().Read()); +} + +TEST(Int128SizesView, CopyFrom) { + ::std::uint8_t buf_x[sizeof kInt128SizesNegativeOne] = {}; + ::std::uint8_t buf_y[sizeof kInt128SizesNegativeOne] = {}; + + auto x = Int128SizesWriter(buf_x, sizeof buf_x); + auto y = Int128SizesWriter(buf_y, sizeof buf_y); + + // Use a value that fits in int64_t to avoid literal issues + __int128_t large_negative = static_cast<__int128_t>(-1234567890123456789LL); + x.sixteen_byte().Write(large_negative); + EXPECT_NE(x.sixteen_byte().Read(), y.sixteen_byte().Read()); + y.sixteen_byte().CopyFrom(x.sixteen_byte()); + EXPECT_EQ(x.sixteen_byte().Read(), y.sixteen_byte().Read()); +} + +TEST(UInt128SizesView, TryToCopyFrom) { + ::std::uint8_t buf_x[sizeof kUInt128Sizes] = {}; + ::std::uint8_t buf_y[sizeof kUInt128Sizes] = {}; + + auto x = UInt128SizesWriter(buf_x, sizeof buf_x); + auto y = UInt128SizesWriter(buf_y, sizeof buf_y); + + __uint128_t value = + (static_cast<__uint128_t>(0xabcdef0123456789UL) << 64) | + static_cast<__uint128_t>(0x9876543210fedcbaUL); + x.sixteen_byte().Write(value); + EXPECT_NE(x.sixteen_byte().Read(), y.sixteen_byte().Read()); + EXPECT_TRUE(y.sixteen_byte().TryToCopyFrom(x.sixteen_byte())); + EXPECT_EQ(x.sixteen_byte().Read(), y.sixteen_byte().Read()); +} + +} // namespace +} // namespace test +} // namespace emboss + +#else // !EMBOSS_HAS_INT128 + +// Provide a placeholder test when 128-bit integers are not available +namespace emboss { +namespace test { +namespace { + +TEST(Int128Support, NotAvailable) { + // This test exists just to indicate that 128-bit tests were skipped + // because the platform doesn't support __int128_t/__uint128_t. + GTEST_SKIP() << "128-bit integer support is not available on this platform"; +} + +} // namespace +} // namespace test +} // namespace emboss + +#endif // EMBOSS_HAS_INT128 diff --git a/compiler/front_end/constraints_test.py b/compiler/front_end/constraints_test.py index fb790a9..eaba409 100644 --- a/compiler/front_end/constraints_test.py +++ b/compiler/front_end/constraints_test.py @@ -235,9 +235,10 @@ def test_struct_field_too_big_for_type(self): ) def test_bits_field_too_big_for_type(self): + # UInt now supports up to 128 bits, so test with 129 bits (17 bytes) ir = _make_ir_from_emb( "struct Foo:\n" - " 0 [+9] UInt uint72\n" + " 0 [+17] UInt uint136\n" ' [byte_order: "LittleEndian"]\n' ) error_field = ir.module[0].type[0].structure.field[0] @@ -467,9 +468,11 @@ def test_explicit_size_too_small_for_field(self): ) def test_explicit_size_too_big(self): + # UInt now supports up to 128 bits, so test with 256 bits (32 bytes) + # to ensure we get the "Requirements of UInt not met" error ir = _make_ir_from_emb( "struct Foo:\n" - " 0 [+16] UInt:128 one_twenty_eight_bit\n" + " 0 [+32] UInt:256 two_fifty_six_bit\n" ' [byte_order: "LittleEndian"]\n' ) error_field = ir.module[0].type[0].structure.field[0] diff --git a/compiler/front_end/prelude.emb b/compiler/front_end/prelude.emb index 5a54251..53c83fe 100644 --- a/compiler/front_end/prelude.emb +++ b/compiler/front_end/prelude.emb @@ -25,14 +25,24 @@ external UInt: -- UInt is an automatically-sized unsigned integer. - [static_requirements: $is_statically_sized && 1 <= $static_size_in_bits <= 64] + -- + -- Note: Sizes above 64 bits (up to 128 bits) require platform support for + -- 128-bit integers (__uint128_t). The C++ backend will use EMBOSS_HAS_INT128 + -- to detect availability. On platforms without 128-bit integer support, + -- fields larger than 64 bits will fail to compile. + [static_requirements: $is_statically_sized && 1 <= $static_size_in_bits <= 128] [is_integer: true] [addressable_unit_size: 1] external Int: -- Int is an automatically-sized signed 2's-complement integer. - [static_requirements: $is_statically_sized && 1 <= $static_size_in_bits <= 64] + -- + -- Note: Sizes above 64 bits (up to 128 bits) require platform support for + -- 128-bit integers (__int128_t). The C++ backend will use EMBOSS_HAS_INT128 + -- to detect availability. On platforms without 128-bit integer support, + -- fields larger than 64 bits will fail to compile. + [static_requirements: $is_statically_sized && 1 <= $static_size_in_bits <= 128] [is_integer: true] [addressable_unit_size: 1] diff --git a/runtime/cpp/test/emboss_bit_util_test.cc b/runtime/cpp/test/emboss_bit_util_test.cc index 83db86c..0592bf3 100644 --- a/runtime/cpp/test/emboss_bit_util_test.cc +++ b/runtime/cpp/test/emboss_bit_util_test.cc @@ -109,6 +109,19 @@ TEST(IsPowerOfTwo, IsPowerOfTwo) { EXPECT_FALSE(IsPowerOfTwo(static_cast<__uint128_t>(3))); EXPECT_FALSE(IsPowerOfTwo((one_128 << 127) - 1)); EXPECT_FALSE(IsPowerOfTwo((one_128 << 64) + 1)); + + // Test signed 128-bit values + __int128_t signed_one_128 = 1; + EXPECT_TRUE(IsPowerOfTwo(signed_one_128)); + EXPECT_TRUE(IsPowerOfTwo(signed_one_128 << 64)); + EXPECT_TRUE(IsPowerOfTwo(signed_one_128 << 100)); + EXPECT_TRUE(IsPowerOfTwo(signed_one_128 << 126)); // Max positive power of 2 + EXPECT_FALSE(IsPowerOfTwo(static_cast<__int128_t>(0))); + EXPECT_FALSE(IsPowerOfTwo(static_cast<__int128_t>(-1))); + EXPECT_FALSE(IsPowerOfTwo(static_cast<__int128_t>(-2))); + // Min int128 is negative, so should not be a power of two + __int128_t min_int128 = static_cast<__int128_t>(1) << 127; + EXPECT_FALSE(IsPowerOfTwo(min_int128)); #endif // EMBOSS_HAS_INT128 } @@ -220,6 +233,71 @@ TEST(EndianConversion, NativeToBigEndian) { } #endif // defined(EMBOSS_NATIVE_TO_BIG_ENDIAN) +#if EMBOSS_HAS_INT128 +TEST(ByteSwap, ByteSwap128Comprehensive) { + // Test identity: swapping twice should return the original value + __uint128_t original = + (static_cast<__uint128_t>(0x0102030405060708UL) << 64) | + static_cast<__uint128_t>(0x090a0b0c0d0e0f10UL); + EXPECT_EQ(original, ByteSwap(ByteSwap(original))); + + // Test with value where high and low 64-bit halves are different + __uint128_t asymmetric = + (static_cast<__uint128_t>(0xFFFFFFFFFFFFFFFFUL) << 64) | + static_cast<__uint128_t>(0x0000000000000000UL); + __uint128_t asymmetric_swapped = + (static_cast<__uint128_t>(0x0000000000000000UL) << 64) | + static_cast<__uint128_t>(0xFFFFFFFFFFFFFFFFUL); + EXPECT_EQ(asymmetric_swapped, ByteSwap(asymmetric)); + + // Test with single byte set at position 0 + __uint128_t byte0 = static_cast<__uint128_t>(0x42UL); + __uint128_t byte0_swapped = static_cast<__uint128_t>(0x42UL) << 120; + EXPECT_EQ(byte0_swapped, ByteSwap(byte0)); + + // Test with single byte set at position 15 (highest byte) + __uint128_t byte15 = static_cast<__uint128_t>(0x42UL) << 120; + __uint128_t byte15_swapped = static_cast<__uint128_t>(0x42UL); + EXPECT_EQ(byte15_swapped, ByteSwap(byte15)); + + // Test with value where each byte is unique + __uint128_t unique_bytes = + (static_cast<__uint128_t>(0x0102030405060708UL) << 64) | + static_cast<__uint128_t>(0x090a0b0c0d0e0f10UL); + __uint128_t unique_bytes_swapped = + (static_cast<__uint128_t>(0x100f0e0d0c0b0a09UL) << 64) | + static_cast<__uint128_t>(0x0807060504030201UL); + EXPECT_EQ(unique_bytes_swapped, ByteSwap(unique_bytes)); +} + +TEST(MaskToNBits, MaskToNBits128EdgeCases) { + __uint128_t all_ones_128 = + (static_cast<__uint128_t>(0xffffffffffffffffUL) << 64) | + static_cast<__uint128_t>(0xffffffffffffffffUL); + + // Mask to 1 bit + EXPECT_EQ(static_cast<__uint128_t>(1), MaskToNBits(all_ones_128, 1)); + + // Mask to 65 bits (just over 64) + __uint128_t expected_65 = + (static_cast<__uint128_t>(0x1UL) << 64) | + static_cast<__uint128_t>(0xffffffffffffffffUL); + EXPECT_EQ(expected_65, MaskToNBits(all_ones_128, 65)); + + // Mask to 127 bits + __uint128_t expected_127 = + (static_cast<__uint128_t>(0x7fffffffffffffffUL) << 64) | + static_cast<__uint128_t>(0xffffffffffffffffUL); + EXPECT_EQ(expected_127, MaskToNBits(all_ones_128, 127)); + + // Mask value that already fits + __uint128_t small_value = static_cast<__uint128_t>(0x1234UL); + EXPECT_EQ(small_value, MaskToNBits(small_value, 128)); + EXPECT_EQ(small_value, MaskToNBits(small_value, 64)); + EXPECT_EQ(static_cast<__uint128_t>(0x234UL), MaskToNBits(small_value, 12)); +} +#endif // EMBOSS_HAS_INT128 + } // namespace test } // namespace support } // namespace emboss diff --git a/runtime/cpp/test/emboss_memory_util_test.cc b/runtime/cpp/test/emboss_memory_util_test.cc index 1831085..b67dc70 100644 --- a/runtime/cpp/test/emboss_memory_util_test.cc +++ b/runtime/cpp/test/emboss_memory_util_test.cc @@ -150,6 +150,118 @@ TEST(MemoryAccessor, LittleEndianReads) { TestMemoryAccessor(); } +#if EMBOSS_HAS_INT128 +// Test 128-bit MemoryAccessor operations +TEST(MemoryAccessor, Int128LittleEndianReadWrite) { + alignas(16) unsigned char bytes[16] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, + 0x0d, 0x0e, 0x0f, 0x10}; + __uint128_t expected_le = + (static_cast<__uint128_t>(0x100f0e0d0c0b0a09UL) << 64) | + static_cast<__uint128_t>(0x0807060504030201UL); + EXPECT_EQ(expected_le, + (MemoryAccessor::ReadLittleEndianUInt( + bytes))); + + // Write and verify + __uint128_t write_value = + (static_cast<__uint128_t>(0xfedcba9876543210UL) << 64) | + static_cast<__uint128_t>(0x0123456789abcdefUL); + MemoryAccessor::WriteLittleEndianUInt(bytes, + write_value); + EXPECT_EQ(0xef, bytes[0]); + EXPECT_EQ(0xcd, bytes[1]); + EXPECT_EQ(0xab, bytes[2]); + EXPECT_EQ(0x89, bytes[3]); + EXPECT_EQ(0x67, bytes[4]); + EXPECT_EQ(0x45, bytes[5]); + EXPECT_EQ(0x23, bytes[6]); + EXPECT_EQ(0x01, bytes[7]); + EXPECT_EQ(0x10, bytes[8]); + EXPECT_EQ(0x32, bytes[9]); + EXPECT_EQ(0x54, bytes[10]); + EXPECT_EQ(0x76, bytes[11]); + EXPECT_EQ(0x98, bytes[12]); + EXPECT_EQ(0xba, bytes[13]); + EXPECT_EQ(0xdc, bytes[14]); + EXPECT_EQ(0xfe, bytes[15]); +} + +TEST(MemoryAccessor, Int128BigEndianReadWrite) { + alignas(16) unsigned char bytes[16] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, + 0x0d, 0x0e, 0x0f, 0x10}; + __uint128_t expected_be = + (static_cast<__uint128_t>(0x0102030405060708UL) << 64) | + static_cast<__uint128_t>(0x090a0b0c0d0e0f10UL); + EXPECT_EQ( + expected_be, + (MemoryAccessor::ReadBigEndianUInt(bytes))); + + // Write and verify + __uint128_t write_value = + (static_cast<__uint128_t>(0xfedcba9876543210UL) << 64) | + static_cast<__uint128_t>(0x0123456789abcdefUL); + MemoryAccessor::WriteBigEndianUInt(bytes, + write_value); + EXPECT_EQ(0xfe, bytes[0]); + EXPECT_EQ(0xdc, bytes[1]); + EXPECT_EQ(0xba, bytes[2]); + EXPECT_EQ(0x98, bytes[3]); + EXPECT_EQ(0x76, bytes[4]); + EXPECT_EQ(0x54, bytes[5]); + EXPECT_EQ(0x32, bytes[6]); + EXPECT_EQ(0x10, bytes[7]); + EXPECT_EQ(0x01, bytes[8]); + EXPECT_EQ(0x23, bytes[9]); + EXPECT_EQ(0x45, bytes[10]); + EXPECT_EQ(0x67, bytes[11]); + EXPECT_EQ(0x89, bytes[12]); + EXPECT_EQ(0xab, bytes[13]); + EXPECT_EQ(0xcd, bytes[14]); + EXPECT_EQ(0xef, bytes[15]); +} + +TEST(MemoryAccessor, Int128NonFullWidth) { + // Test reading/writing values less than 128 bits but more than 64 + alignas(16) unsigned char bytes[16] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, + 0x0d, 0x0e, 0x0f, 0x10}; + + // Read 72 bits (9 bytes) little-endian + __uint128_t expected_72_le = + (static_cast<__uint128_t>(0x09UL) << 64) | + static_cast<__uint128_t>(0x0807060504030201UL); + EXPECT_EQ(expected_72_le, + (MemoryAccessor::ReadLittleEndianUInt( + bytes))); + + // Read 72 bits (9 bytes) big-endian + __uint128_t expected_72_be = + (static_cast<__uint128_t>(0x01UL) << 64) | + static_cast<__uint128_t>(0x0203040506070809UL); + EXPECT_EQ( + expected_72_be, + (MemoryAccessor::ReadBigEndianUInt(bytes))); + + // Read 96 bits (12 bytes) little-endian + __uint128_t expected_96_le = + (static_cast<__uint128_t>(0x0c0b0a09UL) << 64) | + static_cast<__uint128_t>(0x0807060504030201UL); + EXPECT_EQ(expected_96_le, + (MemoryAccessor::ReadLittleEndianUInt( + bytes))); + + // Read 96 bits (12 bytes) big-endian + __uint128_t expected_96_be = + (static_cast<__uint128_t>(0x01020304UL) << 64) | + static_cast<__uint128_t>(0x05060708090a0b0cUL); + EXPECT_EQ( + expected_96_be, + (MemoryAccessor::ReadBigEndianUInt(bytes))); +} +#endif // EMBOSS_HAS_INT128 + TEST(ContiguousBuffer, OffsetStorageType) { EXPECT_TRUE((::std::is_same< ContiguousBuffer, @@ -675,6 +787,156 @@ TEST(BitBlock, GetOffsetStorage) { .SizeInBits())); } +#if EMBOSS_HAS_INT128 +template +using BigEndianBitBlockN128 = + BitBlock, kBits>; + +template +using LittleEndianBitBlockN128 = + BitBlock, kBits>; + +TEST(BitBlock, Int128BigEndianMethods) { + ::std::uint8_t bytes[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}; + const auto big_endian = + BigEndianBitBlockN128<128>{ReadWriteContiguousBuffer{bytes, 16}}; + EXPECT_EQ(128U, big_endian.SizeInBits()); + EXPECT_TRUE(big_endian.Ok()); + + __uint128_t expected = + (static_cast<__uint128_t>(0x0102030405060708UL) << 64) | + static_cast<__uint128_t>(0x090a0b0c0d0e0f10UL); + EXPECT_EQ(expected, big_endian.ReadUInt()); + EXPECT_EQ(expected, big_endian.UncheckedReadUInt()); + EXPECT_FALSE(BigEndianBitBlockN128<128>().Ok()); + EXPECT_EQ(128U, BigEndianBitBlockN128<128>().SizeInBits()); +} + +TEST(BitBlock, Int128LittleEndianMethods) { + ::std::uint8_t bytes[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}; + const auto little_endian = + LittleEndianBitBlockN128<128>{ReadWriteContiguousBuffer{bytes, 16}}; + EXPECT_EQ(128U, little_endian.SizeInBits()); + EXPECT_TRUE(little_endian.Ok()); + + __uint128_t expected = + (static_cast<__uint128_t>(0x100f0e0d0c0b0a09UL) << 64) | + static_cast<__uint128_t>(0x0807060504030201UL); + EXPECT_EQ(expected, little_endian.ReadUInt()); + EXPECT_EQ(expected, little_endian.UncheckedReadUInt()); + EXPECT_FALSE(LittleEndianBitBlockN128<128>().Ok()); + EXPECT_EQ(128U, LittleEndianBitBlockN128<128>().SizeInBits()); +} + +TEST(BitBlock, Int128WriteOperations) { + ::std::uint8_t bytes[16] = {}; + auto little_endian = + LittleEndianBitBlockN128<128>{ReadWriteContiguousBuffer{bytes, 16}}; + + __uint128_t write_value = + (static_cast<__uint128_t>(0xfedcba9876543210UL) << 64) | + static_cast<__uint128_t>(0x0123456789abcdefUL); + little_endian.WriteUInt(write_value); + + // Verify little-endian byte order + EXPECT_EQ(0xef, bytes[0]); + EXPECT_EQ(0xcd, bytes[1]); + EXPECT_EQ(0xab, bytes[2]); + EXPECT_EQ(0x89, bytes[3]); + EXPECT_EQ(0x67, bytes[4]); + EXPECT_EQ(0x45, bytes[5]); + EXPECT_EQ(0x23, bytes[6]); + EXPECT_EQ(0x01, bytes[7]); + EXPECT_EQ(0x10, bytes[8]); + EXPECT_EQ(0x32, bytes[9]); + EXPECT_EQ(0x54, bytes[10]); + EXPECT_EQ(0x76, bytes[11]); + EXPECT_EQ(0x98, bytes[12]); + EXPECT_EQ(0xba, bytes[13]); + EXPECT_EQ(0xdc, bytes[14]); + EXPECT_EQ(0xfe, bytes[15]); + + EXPECT_EQ(write_value, little_endian.ReadUInt()); +} + +TEST(BitBlock, Int128NonFullWidthSizes) { + // Test 72-bit (9-byte), 96-bit (12-byte), 104-bit (13-byte) values + ::std::uint8_t bytes[16] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}; + + // Test 72-bit little-endian + const auto le_72 = + LittleEndianBitBlockN128<72>{ReadWriteContiguousBuffer{bytes, 9}}; + EXPECT_TRUE(le_72.Ok()); + __uint128_t expected_72_le = + (static_cast<__uint128_t>(0x09UL) << 64) | + static_cast<__uint128_t>(0x0807060504030201UL); + EXPECT_EQ(expected_72_le, le_72.ReadUInt()); + + // Test 96-bit big-endian + const auto be_96 = + BigEndianBitBlockN128<96>{ReadWriteContiguousBuffer{bytes, 12}}; + EXPECT_TRUE(be_96.Ok()); + __uint128_t expected_96_be = + (static_cast<__uint128_t>(0x01020304UL) << 64) | + static_cast<__uint128_t>(0x05060708090a0b0cUL); + EXPECT_EQ(expected_96_be, be_96.ReadUInt()); +} + +TEST(BitBlock, Int128GetOffsetStorage) { + ::std::uint8_t bytes[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}; + const auto bit_block = + LittleEndianBitBlockN128<128>{ReadWriteContiguousBuffer{bytes, 16}}; + + // Get offset storage for bits 64-127 (upper 64 bits) + const auto offset_block = bit_block.GetOffsetStorage<1, 0>(64, 64); + EXPECT_EQ(64U, offset_block.SizeInBits()); + EXPECT_TRUE(offset_block.Ok()); + EXPECT_EQ(0x100f0e0d0c0b0a09UL, offset_block.ReadUInt()); + + // Get offset storage for bits 0-63 (lower 64 bits) + const auto lower_block = bit_block.GetOffsetStorage<1, 0>(0, 64); + EXPECT_EQ(64U, lower_block.SizeInBits()); + EXPECT_TRUE(lower_block.Ok()); + EXPECT_EQ(0x0807060504030201UL, lower_block.ReadUInt()); + + // Get offset storage that spans the 64-bit boundary + const auto span_block = bit_block.GetOffsetStorage<1, 0>(32, 64); + EXPECT_EQ(64U, span_block.SizeInBits()); + EXPECT_TRUE(span_block.Ok()); + // Bits 32-95 from original value + __uint128_t full_value = + (static_cast<__uint128_t>(0x100f0e0d0c0b0a09UL) << 64) | + static_cast<__uint128_t>(0x0807060504030201UL); + EXPECT_EQ(static_cast<::std::uint64_t>((full_value >> 32) & 0xFFFFFFFFFFFFFFFFUL), + span_block.ReadUInt()); +} + +TEST(ContiguousBuffer, Int128ReturnType) { + const auto buffer = ContiguousBuffer(); + +#if EMBOSS_HAS_INT128 + EXPECT_TRUE((::std::is_same()), + __uint128_t>::value)); + EXPECT_TRUE((::std::is_same()), + __uint128_t>::value)); + EXPECT_TRUE((::std::is_same()), + __uint128_t>::value)); + EXPECT_TRUE((::std::is_same()), + __uint128_t>::value)); + EXPECT_TRUE( + (::std::is_same()), + __uint128_t>::value)); + EXPECT_TRUE( + (::std::is_same()), + __uint128_t>::value)); +#endif // EMBOSS_HAS_INT128 +} +#endif // EMBOSS_HAS_INT128 + TEST(OffsetBitBlock, Methods) { ::std::vector bytes = { {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09}}; diff --git a/runtime/cpp/test/emboss_prelude_test.cc b/runtime/cpp/test/emboss_prelude_test.cc index a46617a..a313977 100644 --- a/runtime/cpp/test/emboss_prelude_test.cc +++ b/runtime/cpp/test/emboss_prelude_test.cc @@ -94,10 +94,23 @@ TEST(FlagView, TextEncode) { EXPECT_EQ("true", WriteToString(flag_view)); } +// Helper to select appropriate BitBlock size for a given view size +template +struct BitBlockSelector { +#if EMBOSS_HAS_INT128 + // Use 128-bit BitBlock for sizes > 64 + using Type = typename ::std::conditional<(kBits > 64), BitBlockN<128>, + BitBlockN<64>>::type; +#else + using Type = BitBlockN<64>; +#endif +}; + template