diff options
author | Chris Mihelich <cmihelic@google.com> | 2024-06-12 12:20:27 -0700 |
---|---|---|
committer | Copybara-Service <copybara-worker@google.com> | 2024-06-12 12:21:10 -0700 |
commit | fc7612086f68bf702b1f25309eadb00166d5d6f1 (patch) | |
tree | d31fda8466bb641034bc56bf5c43430ac690906f | |
parent | 17137c084a3d502277bea803b076894eef06af41 (diff) |
Sequence of 1-to-4 values with prefix sum to support Punycode decoding.
PiperOrigin-RevId: 642696557
Change-Id: Ia6b8e174ddb55e44bd082bf0d81d2f9c53c94016
-rw-r--r-- | CMake/AbseilDll.cmake | 1 | ||||
-rw-r--r-- | absl/debugging/BUILD.bazel | 15 | ||||
-rw-r--r-- | absl/debugging/CMakeLists.txt | 15 | ||||
-rw-r--r-- | absl/debugging/internal/bounded_utf8_length_sequence.h | 126 | ||||
-rw-r--r-- | absl/debugging/internal/bounded_utf8_length_sequence_test.cc | 126 |
5 files changed, 283 insertions, 0 deletions
diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index 3c161ec0..87904542 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake @@ -122,6 +122,7 @@ set(ABSL_INTERNAL_DLL_FILES "debugging/symbolize.h" "debugging/internal/address_is_readable.cc" "debugging/internal/address_is_readable.h" + "debugging/internal/bounded_utf8_length_sequence.h" "debugging/internal/demangle.cc" "debugging/internal/demangle.h" "debugging/internal/demangle_rust.cc" diff --git a/absl/debugging/BUILD.bazel b/absl/debugging/BUILD.bazel index 670f7764..0cd8015c 100644 --- a/absl/debugging/BUILD.bazel +++ b/absl/debugging/BUILD.bazel @@ -225,6 +225,7 @@ cc_library( "internal/utf8_for_code_point.cc", ], hdrs = [ + "internal/bounded_utf8_length_sequence.h", "internal/demangle.h", "internal/demangle_rust.h", "internal/utf8_for_code_point.h", @@ -239,6 +240,20 @@ cc_library( "//absl/base", "//absl/base:config", "//absl/base:core_headers", + "//absl/numeric:bits", + ], +) + +cc_test( + name = "bounded_utf8_length_sequence_test", + srcs = ["internal/bounded_utf8_length_sequence_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":demangle_internal", + "//absl/base:config", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/debugging/CMakeLists.txt b/absl/debugging/CMakeLists.txt index 8379b5b9..738ae4e3 100644 --- a/absl/debugging/CMakeLists.txt +++ b/absl/debugging/CMakeLists.txt @@ -195,6 +195,7 @@ absl_cc_library( NAME demangle_internal HDRS + "internal/bounded_utf8_length_sequence.h" "internal/demangle.h" "internal/demangle_rust.h" "internal/utf8_for_code_point.h" @@ -206,12 +207,26 @@ absl_cc_library( ${ABSL_DEFAULT_COPTS} DEPS absl::base + absl::bits absl::core_headers PUBLIC ) absl_cc_test( NAME + bounded_utf8_length_sequence_test + SRCS + "internal/bounded_utf8_length_sequence_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::demangle_internal + absl::config + GTest::gmock_main +) + +absl_cc_test( + NAME demangle_rust_test SRCS "internal/demangle_rust_test.cc" diff --git a/absl/debugging/internal/bounded_utf8_length_sequence.h b/absl/debugging/internal/bounded_utf8_length_sequence.h new file mode 100644 index 00000000..188e06c4 --- /dev/null +++ b/absl/debugging/internal/bounded_utf8_length_sequence.h @@ -0,0 +1,126 @@ +// Copyright 2024 The Abseil Authors +// +// 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. + +#ifndef ABSL_DEBUGGING_INTERNAL_BOUNDED_UTF8_LENGTH_SEQUENCE_H_ +#define ABSL_DEBUGGING_INTERNAL_BOUNDED_UTF8_LENGTH_SEQUENCE_H_ + +#include <cstdint> + +#include "absl/base/config.h" +#include "absl/numeric/bits.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace debugging_internal { + +// A sequence of up to max_elements integers between 1 and 4 inclusive, whose +// insertion operation computes the sum of all the elements before the insertion +// point. This is useful in decoding Punycode, where one needs to know where in +// a UTF-8 byte stream the n-th code point begins. +// +// BoundedUtf8LengthSequence is async-signal-safe and suitable for use in +// symbolizing stack traces in a signal handler, provided max_elements is not +// improvidently large. For inputs of lengths accepted by the Rust demangler, +// up to a couple hundred code points, InsertAndReturnSumOfPredecessors should +// run in a few dozen clock cycles, on par with the other arithmetic required +// for Punycode decoding. +template <uint32_t max_elements> +class BoundedUtf8LengthSequence { + public: + // Constructs an empty sequence. + BoundedUtf8LengthSequence() = default; + + // Inserts `utf_length` at position `index`, shifting any existing elements at + // or beyond `index` one position to the right. If the sequence is already + // full, the rightmost element is discarded. + // + // Returns the sum of the elements at positions 0 to `index - 1` inclusive. + // If `index` is greater than the number of elements already inserted, the + // excess positions in the range count 1 apiece. + // + // REQUIRES: index < max_elements and 1 <= utf8_length <= 4. + uint32_t InsertAndReturnSumOfPredecessors( + uint32_t index, uint32_t utf8_length) { + // The caller shouldn't pass out-of-bounds inputs, but if it does happen, + // clamp the values and try to continue. If we're being called from a + // signal handler, the last thing we want to do is crash. Emitting + // malformed UTF-8 is a lesser evil. + if (index >= max_elements) index = max_elements - 1; + if (utf8_length == 0 || utf8_length > 4) utf8_length = 1; + + const uint32_t word_index = index/32; + const uint32_t bit_index = 2 * (index % 32); + const uint64_t ones_bit = uint64_t{1} << bit_index; + + // Compute the sum of predecessors. + // - Each value from 1 to 4 is represented by a bit field with value from + // 0 to 3, so the desired sum is index plus the sum of the + // representations actually stored. + // - For each bit field, a set low bit should contribute 1 to the sum, and + // a set high bit should contribute 2. + // - Another way to say the same thing is that each set bit contributes 1, + // and each set high bit contributes an additional 1. + // - So the sum we want is index + popcount(everything) + popcount(bits in + // odd positions). + const uint64_t odd_bits_mask = 0xaaaaaaaaaaaaaaaa; + const uint64_t lower_seminibbles_mask = ones_bit - 1; + const uint64_t higher_seminibbles_mask = ~lower_seminibbles_mask; + const uint64_t same_word_bits_below_insertion = + rep_[word_index] & lower_seminibbles_mask; + int full_popcount = absl::popcount(same_word_bits_below_insertion); + int odd_popcount = + absl::popcount(same_word_bits_below_insertion & odd_bits_mask); + for (uint32_t j = word_index; j > 0; --j) { + const uint64_t word_below_insertion = rep_[j - 1]; + full_popcount += absl::popcount(word_below_insertion); + odd_popcount += absl::popcount(word_below_insertion & odd_bits_mask); + } + const uint32_t sum_of_predecessors = + index + static_cast<uint32_t>(full_popcount + odd_popcount); + + // Now insert utf8_length's representation, shifting successors up one + // place. + for (uint32_t j = max_elements/32 - 1; j > word_index; --j) { + rep_[j] = (rep_[j] << 2) | (rep_[j - 1] >> 62); + } + rep_[word_index] = + (rep_[word_index] & lower_seminibbles_mask) | + (uint64_t{utf8_length - 1} << bit_index) | + ((rep_[word_index] & higher_seminibbles_mask) << 2); + + return sum_of_predecessors; + } + + private: + // If the (32 * i + j)-th element of the represented sequence has the value k + // (0 <= j < 32, 1 <= k <= 4), then bits 2 * j and 2 * j + 1 of rep_[i] + // contain the seminibble (k - 1). + // + // In particular, the zero-initialization of rep_ makes positions not holding + // any inserted element count as 1 in InsertAndReturnSumOfPredecessors. + // + // Example: rep_ = {0xb1, ... the rest zeroes ...} represents the sequence + // (2, 1, 4, 3, ... the rest 1's ...). Constructing the sequence of Unicode + // code points "Àa🂻中" = {U+00C0, U+0061, U+1F0BB, U+4E2D} (among many + // other examples) would yield this value of rep_. + static_assert(max_elements > 0 && max_elements % 32 == 0, + "max_elements must be a positive multiple of 32"); + uint64_t rep_[max_elements/32] = {}; +}; + +} // namespace debugging_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_DEBUGGING_INTERNAL_BOUNDED_UTF8_LENGTH_SEQUENCE_H_ diff --git a/absl/debugging/internal/bounded_utf8_length_sequence_test.cc b/absl/debugging/internal/bounded_utf8_length_sequence_test.cc new file mode 100644 index 00000000..17a24fd1 --- /dev/null +++ b/absl/debugging/internal/bounded_utf8_length_sequence_test.cc @@ -0,0 +1,126 @@ +// Copyright 2024 The Abseil Authors +// +// 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. + +#include "absl/debugging/internal/bounded_utf8_length_sequence.h" + +#include <cstdint> + +#include "gtest/gtest.h" +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace debugging_internal { +namespace { + +TEST(BoundedUtf8LengthSequenceTest, RemembersAValueOfOneCorrectly) { + BoundedUtf8LengthSequence<32> seq; + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(0, 1), 0); + EXPECT_EQ(seq.InsertAndReturnSumOfPredecessors(1, 1), 1); +} + +TEST(BoundedUtf8LengthSequenceTest, RemembersAValueOfTwoCorrectly) { + BoundedUtf8LengthSequence<32> seq; + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(0, 2), 0); + EXPECT_EQ(seq.InsertAndReturnSumOfPredecessors(1, 1), 2); +} + +TEST(BoundedUtf8LengthSequenceTest, RemembersAValueOfThreeCorrectly) { + BoundedUtf8LengthSequence<32> seq; + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(0, 3), 0); + EXPECT_EQ(seq.InsertAndReturnSumOfPredecessors(1, 1), 3); +} + +TEST(BoundedUtf8LengthSequenceTest, RemembersAValueOfFourCorrectly) { + BoundedUtf8LengthSequence<32> seq; + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(0, 4), 0); + EXPECT_EQ(seq.InsertAndReturnSumOfPredecessors(1, 1), 4); +} + +TEST(BoundedUtf8LengthSequenceTest, RemembersSeveralAppendedValues) { + BoundedUtf8LengthSequence<32> seq; + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(0, 1), 0); + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(1, 4), 1); + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(2, 2), 5); + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(3, 3), 7); + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(4, 1), 10); +} + +TEST(BoundedUtf8LengthSequenceTest, RemembersSeveralPrependedValues) { + BoundedUtf8LengthSequence<32> seq; + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(0, 4), 0); + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(0, 3), 0); + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(0, 2), 0); + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(0, 1), 0); + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(4, 1), 10); + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(3, 1), 6); + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(2, 1), 3); + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(1, 1), 1); +} + +TEST(BoundedUtf8LengthSequenceTest, RepeatedInsertsShiftValuesOutTheRightEnd) { + BoundedUtf8LengthSequence<32> seq; + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(0, 2), 0); + for (uint32_t i = 1; i < 31; ++i) { + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(0, 1), 0) + << "while moving the 2 into position " << i; + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(31, 1), 32) + << "after moving the 2 into position " << i; + } + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(0, 1), 0) + << "while moving the 2 into position 31"; + EXPECT_EQ(seq.InsertAndReturnSumOfPredecessors(31, 1), 31) + << "after moving the 2 into position 31"; +} + +TEST(BoundedUtf8LengthSequenceTest, InsertsIntoWord1LeaveWord0Untouched) { + BoundedUtf8LengthSequence<64> seq; + for (uint32_t i = 0; i < 32; ++i) { + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(i, 2), 2 * i) + << "at index " << i; + } + EXPECT_EQ(seq.InsertAndReturnSumOfPredecessors(32, 1), 64); + EXPECT_EQ(seq.InsertAndReturnSumOfPredecessors(32, 1), 64); +} + +TEST(BoundedUtf8LengthSequenceTest, InsertsIntoWord0ShiftValuesIntoWord1) { + BoundedUtf8LengthSequence<64> seq; + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(29, 2), 29); + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(30, 3), 31); + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(31, 4), 34); + + // Pushing two 1's on the front moves the 3 and 4 into the high word. + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(0, 1), 0); + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(0, 1), 0); + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(34, 1), 31 + 2 + 3 + 4); + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(32, 1), 31 + 2); +} + +TEST(BoundedUtf8LengthSequenceTest, ValuesAreShiftedCorrectlyAmongThreeWords) { + BoundedUtf8LengthSequence<96> seq; + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(31, 3), 31); + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(63, 4), 62 + 3); + + // This insertion moves both the 3 and the 4 up a word. + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(0, 1), 0); + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(65, 1), 63 + 3 + 4); + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(64, 1), 63 + 3); + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(33, 1), 32 + 3); + ASSERT_EQ(seq.InsertAndReturnSumOfPredecessors(32, 1), 32); +} + +} // namespace +} // namespace debugging_internal +ABSL_NAMESPACE_END +} // namespace absl |