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).
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
@@ -192,6 +192,18 @@
)
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 = [
"testcode/float_test.cc",
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 <stdint.h>
+
+#include <cstring>
+#include <vector>
+
+#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</**/ ::std::uint8_t>(
+ kInt128SizesNegativeOne,
+ kInt128SizesNegativeOne + sizeof kInt128SizesNegativeOne),
+ ::std::vector</**/ ::std::uint8_t>(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_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_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 @@
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 @@
}
#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 @@
TestMemoryAccessor<unsigned char, 8, 0, 64>();
}
+#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<unsigned char, 1, 0, 128>::ReadLittleEndianUInt(
+ bytes)));
+
+ // Write and verify
+ __uint128_t write_value =
+ (static_cast<__uint128_t>(0xfedcba9876543210UL) << 64) |
+ static_cast<__uint128_t>(0x0123456789abcdefUL);
+ MemoryAccessor<unsigned char, 1, 0, 128>::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<unsigned char, 1, 0, 128>::ReadBigEndianUInt(bytes)));
+
+ // Write and verify
+ __uint128_t write_value =
+ (static_cast<__uint128_t>(0xfedcba9876543210UL) << 64) |
+ static_cast<__uint128_t>(0x0123456789abcdefUL);
+ MemoryAccessor<unsigned char, 1, 0, 128>::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<unsigned char, 1, 0, 72>::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<unsigned char, 1, 0, 72>::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<unsigned char, 1, 0, 96>::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<unsigned char, 1, 0, 96>::ReadBigEndianUInt(bytes)));
+}
+#endif // EMBOSS_HAS_INT128
+
TEST(ContiguousBuffer, OffsetStorageType) {
EXPECT_TRUE((::std::is_same<
ContiguousBuffer<char, 2, 0>,
@@ -675,6 +787,156 @@
.SizeInBits()));
}
+#if EMBOSS_HAS_INT128
+template </**/ ::std::size_t kBits>
+using BigEndianBitBlockN128 =
+ BitBlock<BigEndianByteOrderer<ReadWriteContiguousBuffer>, kBits>;
+
+template </**/ ::std::size_t kBits>
+using LittleEndianBitBlockN128 =
+ BitBlock<LittleEndianByteOrderer<ReadWriteContiguousBuffer>, 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<char, 1, 0>();
+
+#if EMBOSS_HAS_INT128
+ EXPECT_TRUE((::std::is_same<decltype(buffer.ReadBigEndianUInt<128>()),
+ __uint128_t>::value));
+ EXPECT_TRUE((::std::is_same<decltype(buffer.ReadBigEndianUInt<72>()),
+ __uint128_t>::value));
+ EXPECT_TRUE((::std::is_same<decltype(buffer.ReadLittleEndianUInt<128>()),
+ __uint128_t>::value));
+ EXPECT_TRUE((::std::is_same<decltype(buffer.ReadLittleEndianUInt<96>()),
+ __uint128_t>::value));
+ EXPECT_TRUE(
+ (::std::is_same<decltype(buffer.UncheckedReadBigEndianUInt<128>()),
+ __uint128_t>::value));
+ EXPECT_TRUE(
+ (::std::is_same<decltype(buffer.UncheckedReadLittleEndianUInt<128>()),
+ __uint128_t>::value));
+#endif // EMBOSS_HAS_INT128
+}
+#endif // EMBOSS_HAS_INT128
+
TEST(OffsetBitBlock, Methods) {
::std::vector</**/ ::std::uint8_t> 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 @@
EXPECT_EQ("true", WriteToString(flag_view));
}
+// Helper to select appropriate BitBlock size for a given view size
+template <int kBits>
+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 <template <typename, typename> class ViewType, int kMaxBits>
void CheckViewSizeInBits() {
+ using BlockType = typename BitBlockSelector<kMaxBits>::Type;
const int size_in_bits =
- ViewType<ViewParameters<kMaxBits>, BitBlockN<64>>::SizeInBits();
+ ViewType<ViewParameters<kMaxBits>, BlockType>::SizeInBits();
EXPECT_EQ(size_in_bits, kMaxBits);
return CheckViewSizeInBits<ViewType, kMaxBits - 1>();
}
@@ -200,6 +213,81 @@
EXPECT_FALSE(UIntViewN<64>::CouldWriteValue(-1));
}
+#if EMBOSS_HAS_INT128
+TEST(UIntView, Int128ReadAndWriteWithSufficientBuffer) {
+ ::std::vector</**/ ::std::uint8_t> bytes = {
+ {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0xff}};
+ auto uint128_view = UIntView<ViewParameters<128>, BitBlockN<128>>{
+ BitBlockN<128>{ReadWriteContiguousBuffer{bytes.data(), 16}}};
+ __uint128_t expected =
+ (static_cast<__uint128_t>(0x100f0e0d0c0b0a09UL) << 64) |
+ static_cast<__uint128_t>(0x0807060504030201UL);
+ EXPECT_EQ(expected, uint128_view.Read());
+ EXPECT_EQ(expected, uint128_view.UncheckedRead());
+
+ __uint128_t write_value =
+ (static_cast<__uint128_t>(0xfedcba9876543210UL) << 64) |
+ static_cast<__uint128_t>(0x0123456789abcdefUL);
+ uint128_view.Write(write_value);
+ EXPECT_EQ(write_value, uint128_view.Read());
+
+ // Verify the buffer was written in little-endian order
+ EXPECT_EQ(0xef, bytes[0]);
+ EXPECT_EQ(0xcd, bytes[1]);
+ EXPECT_EQ(0x10, bytes[8]);
+ EXPECT_EQ(0xfe, bytes[15]);
+ // Last byte should be untouched
+ EXPECT_EQ(0xff, bytes[16]);
+
+ EXPECT_TRUE(uint128_view.Ok());
+ EXPECT_TRUE(uint128_view.IsComplete());
+}
+
+TEST(UIntView, Int128NonFullWidth) {
+ // Test 72-bit (9 byte) unsigned integer - BitBlock size must match view size
+ ::std::vector</**/ ::std::uint8_t> bytes = {
+ {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xff}};
+ auto uint72_view = UIntView<ViewParameters<72>, BitBlockN<72>>{
+ BitBlockN<72>{ReadWriteContiguousBuffer{bytes.data(), 9}}};
+
+ __uint128_t expected_72 =
+ (static_cast<__uint128_t>(0x09UL) << 64) |
+ static_cast<__uint128_t>(0x0807060504030201UL);
+ EXPECT_EQ(expected_72, uint72_view.Read());
+ EXPECT_TRUE(uint72_view.Ok());
+
+ // Write a value that fits in 72 bits
+ __uint128_t write_72 =
+ (static_cast<__uint128_t>(0xffUL) << 64) |
+ static_cast<__uint128_t>(0xffffffffffffffffUL);
+ uint72_view.Write(write_72);
+ EXPECT_EQ(write_72, uint72_view.Read());
+ // Last byte should be untouched
+ EXPECT_EQ(0xff, bytes[9]);
+}
+
+TEST(UIntView, Int128CouldWriteValue) {
+ using UIntView128 = UIntView<ViewParameters<128>, BitBlockN<128>>;
+
+ // 128-bit view should accept full 128-bit values
+ __uint128_t max_128 =
+ (static_cast<__uint128_t>(0xffffffffffffffffUL) << 64) |
+ static_cast<__uint128_t>(0xffffffffffffffffUL);
+ EXPECT_TRUE(UIntView128::CouldWriteValue(max_128));
+ EXPECT_TRUE(UIntView128::CouldWriteValue(static_cast<__uint128_t>(0)));
+
+ // Test that values in the valid range work
+ __uint128_t mid_value =
+ (static_cast<__uint128_t>(0x8000000000000000UL) << 64) |
+ static_cast<__uint128_t>(0x0000000000000000UL);
+ EXPECT_TRUE(UIntView128::CouldWriteValue(mid_value));
+
+ // Test that negative values (which convert to large unsigned) fail
+ EXPECT_FALSE(UIntView128::CouldWriteValue(static_cast<__int128_t>(-1)));
+}
+#endif // EMBOSS_HAS_INT128
+
TEST(UIntView, CouldWriteValueNarrowing) {
auto narrowing_could_write = [](int value) {
return UIntViewN<8>::CouldWriteValue(value);
@@ -408,6 +496,105 @@
EXPECT_FALSE(IntViewN<64>::CouldWriteValue(0x8000000000000000UL));
}
+#if EMBOSS_HAS_INT128
+TEST(IntView, Int128ReadAndWriteWithSufficientBuffer) {
+ ::std::vector</**/ ::std::uint8_t> bytes = {
+ {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0xff}};
+ auto int128_view = IntView<ViewParameters<128>, BitBlockN<128>>{
+ BitBlockN<128>{ReadWriteContiguousBuffer{bytes.data(), 16}}};
+ __int128_t expected =
+ static_cast<__int128_t>(
+ (static_cast<__uint128_t>(0x100f0e0d0c0b0a09UL) << 64) |
+ static_cast<__uint128_t>(0x0807060504030201UL));
+ EXPECT_EQ(expected, int128_view.Read());
+ EXPECT_EQ(expected, int128_view.UncheckedRead());
+
+ // Write a negative value
+ __int128_t write_value = -1;
+ int128_view.Write(write_value);
+ EXPECT_EQ(write_value, int128_view.Read());
+ // All bytes should be 0xff
+ for (int i = 0; i < 16; ++i) {
+ EXPECT_EQ(0xff, bytes[i]);
+ }
+ // Last byte should be untouched
+ EXPECT_EQ(0xff, bytes[16]);
+
+ EXPECT_TRUE(int128_view.Ok());
+ EXPECT_TRUE(int128_view.IsComplete());
+}
+
+TEST(IntView, Int128SignExtension) {
+ // Test that signed 128-bit values with high bit set are properly sign-extended
+ // Min int128 in little-endian: 0x00...00 with MSB set = 0x80 in the last byte
+ ::std::vector</**/ ::std::uint8_t> bytes = {
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80}}; // -2^127 in LE
+ auto int128_view = IntView<ViewParameters<128>, BitBlockN<128>>{
+ BitBlockN<128>{ReadWriteContiguousBuffer{bytes.data(), 16}}};
+
+ // The MSB is set, so this should be the minimum 128-bit signed integer
+ __int128_t min_int128 = static_cast<__int128_t>(1) << 127;
+ // This is negative due to two's complement
+ EXPECT_TRUE(int128_view.Read() < 0);
+ EXPECT_EQ(min_int128, int128_view.Read());
+
+ // Write max positive value
+ __int128_t max_positive = ~min_int128; // 2^127 - 1
+ int128_view.Write(max_positive);
+ EXPECT_EQ(max_positive, int128_view.Read());
+ EXPECT_TRUE(int128_view.Read() > 0);
+
+ // Verify the bytes are correct for max positive
+ // Max positive = 0x7fffffffffffffff_ffffffffffffffff in LE
+ EXPECT_EQ(0xff, bytes[0]);
+ EXPECT_EQ(0xff, bytes[7]);
+ EXPECT_EQ(0xff, bytes[14]);
+ EXPECT_EQ(0x7f, bytes[15]); // The MSB should be 0
+}
+
+TEST(IntView, Int128NonFullWidth) {
+ // Test 72-bit (9 byte) signed integer with negative value
+ ::std::vector</**/ ::std::uint8_t> bytes = {
+ {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00}};
+ auto int72_view = IntView<ViewParameters<72>, BitBlockN<72>>{
+ BitBlockN<72>{ReadWriteContiguousBuffer{bytes.data(), 9}}};
+
+ // 72 bits all set to 1 should be -1 in two's complement
+ EXPECT_EQ(static_cast<__int128_t>(-1), int72_view.Read());
+ EXPECT_TRUE(int72_view.Ok());
+
+ // Write a small negative value
+ int72_view.Write(static_cast<__int128_t>(-100));
+ EXPECT_EQ(static_cast<__int128_t>(-100), int72_view.Read());
+ // Last byte should be untouched (but may have been modified by writes above)
+}
+
+TEST(IntView, Int128CouldWriteValue) {
+ using IntView128 = IntView<ViewParameters<128>, BitBlockN<128>>;
+
+ // Max and min 128-bit signed values
+ __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;
+
+ EXPECT_TRUE(IntView128::CouldWriteValue(max_128));
+ EXPECT_TRUE(IntView128::CouldWriteValue(min_128));
+ EXPECT_TRUE(IntView128::CouldWriteValue(static_cast<__int128_t>(0)));
+ EXPECT_TRUE(IntView128::CouldWriteValue(static_cast<__int128_t>(-1)));
+ EXPECT_TRUE(IntView128::CouldWriteValue(static_cast<__int128_t>(1)));
+
+ // Test various values in the middle of the range
+ EXPECT_TRUE(IntView128::CouldWriteValue(
+ static_cast<__int128_t>(0x7fffffffffffffffLL)));
+ EXPECT_TRUE(IntView128::CouldWriteValue(
+ static_cast<__int128_t>(-0x7fffffffffffffffLL)));
+}
+#endif // EMBOSS_HAS_INT128
+
TEST(IntView, CouldWriteValueNarrowing) {
auto narrowing_could_write = [](int value) {
return IntViewN<8>::CouldWriteValue(value);
diff --git a/testdata/int128_sizes.emb b/testdata/int128_sizes.emb
index 1e988c2..2fec3a8 100644
--- a/testdata/int128_sizes.emb
+++ b/testdata/int128_sizes.emb
@@ -13,7 +13,11 @@
# 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.
+--
+-- This file requires platform support for 128-bit integers (__uint128_t and
+-- __int128_t). The C++ backend uses EMBOSS_HAS_INT128 to detect availability.
+-- On platforms without 128-bit integer support (e.g., 32-bit systems, MSVC),
+-- code using these types will fail to compile.
[$default byte_order: "LittleEndian"]
[(cpp) namespace: "emboss::test"]
@@ -76,11 +80,6 @@
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
+# Note: Anonymous `bits` types are still constrained to 64 bits maximum.
+# Supporting 128-bit `bits` types would require additional implementation
+# work in the OffsetBitBlock class and the constraints checker.