Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,15 @@ jobs:
-fstack-protector-strong -fstack-clash-protection -fcf-protection=full
-Wformat -Wformat-security -Werror=format-security"
-Dcpp_link_args="-Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack"

# A tagged pointer packed into a byte array is exactly the design that a different
# word size can break, and test/meson.build has carried a -m32 accommodation for
# years that no job ever built. It had rotted into not configuring at all.
- name: linux-32bit
os: ubuntu-latest
cxx: c++
multilib: true
setup_args: -Dcpp_args=-m32 -Dcpp_link_args=-m32
steps:
- uses: actions/checkout@v7
- uses: hendrikmuhs/ccache-action@v1.2
Expand All @@ -129,6 +138,10 @@ jobs:
path: ${{ env.MESON_PACKAGE_CACHE }}
key: wraps-${{ hashFiles('subprojects/*.wrap') }}
- run: pip install -r .github/workflows/requirements.txt
# The runner images carry a 64 bit only toolchain, so -m32 needs the 32 bit libstdc++.
- name: 32 bit toolchain
if: matrix.multilib
run: sudo apt-get update && sudo apt-get install -y g++-multilib
# ccache does not support MSVC, sccache does. Without this the windows job had no
# compiler cache at all and was by far the slowest.
- uses: ilammy/msvc-dev-cmd@v1
Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,13 @@ meson test
`meson test` runs the unit tests — 108 cases and ~630k assertions, much of it comparing against `std::vector`
operation by operation — and replays a 1651 entry fuzzing corpus. CI additionally builds on Linux, macOS and
Windows, at C++20 as well as the default C++17 and at C++23 on Linux, under address+undefined sanitizers, and
with a distribution's hardening flags including `_GLIBCXX_ASSERTIONS`. It also compiles a small consumer
project against `CMakeLists.txt`, which is the only thing that exercises the CMake path.
with a distribution's hardening flags including `_GLIBCXX_ASSERTIONS`, and as a 32 bit build. It also compiles
a small consumer project against `CMakeLists.txt`, which is the only thing that exercises the CMake path.

The one thing a build has to provide is a **little endian** target, because the direct/indirect flag shares a
byte with the low end of the indirect pointer. That is checked with a `static_assert` rather than left to be
discovered. Both 32 and 64 bit are fine: `svector<uint8_t, 1>` is 8 bytes holding 7 where a pointer is 8 bytes,
and 4 holding 3 where it is 4.

Benchmarks are separate and want a release build. There are two of them:

Expand Down
15 changes: 14 additions & 1 deletion include/ankerl/svector.h
Original file line number Diff line number Diff line change
Expand Up @@ -593,10 +593,23 @@ class svector : private detail::allocator_holder<Allocator> {
* Then 0-X bytes unused (padding), and then the actual inline T data.
* indirect:
* m_data[0] & 1: lowest bit is 0 for indirect mode
* m_data[0..7]: stores an uintptr_t, which points to the indirect data.
* m_data[0..sizeof(void*)-1]: stores an uintptr_t, which points to the indirect data.
*
* The mode flag and the pointer share m_data[0], which only holds on a little endian target.
* There the pointer's least significant byte lands in m_data[0] and alignment guarantees its
* low bit is 0, so the flag has a byte to live in. On a big endian target m_data[0] would be
* the pointer's most significant byte instead and the flag would read something unrelated.
*/
alignas(detail::alignment_of_svector<T>()) std::array<uint8_t, detail::size_of_svector<T>(MinInlineCapacity)> m_data;

// Nothing in CI can catch this: every runner is little endian. MSVC does not define
// __BYTE_ORDER__ but targets nothing big endian either, so check where a check is possible.
#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__)
static_assert(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__,
"svector packs its direct/indirect flag into the low byte of the indirect pointer, "
"which requires a little endian target");
#endif

/**
* @brief Whether the whole inline buffer can stand in for the elements it holds.
*
Expand Down
17 changes: 11 additions & 6 deletions test/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,16 @@ if compiler.has_argument('-Wno-stringop-overflow')
cpp_args += '-Wno-stringop-overflow' # g++ error in fmtlib
endif

fmt_method = 'auto'
# fmt is only used by the test harness. A system fmt is fine normally, but it is built for the
# host word size, so a -m32 build cannot link against it and has to build fmt from the wrap.
#
# method: 'builtin' used to say that and silently stopped meaning it: restricting the method
# keeps meson from consulting the wrap's override_dependency, so the fallback came back NO and
# a 32 bit setup failed outright rather than falling back. Ask for the subproject directly.
if get_option('cpp_args').contains('-m32')
# use builtin so we can compile it for 32bit.
# Can't use it as a default or sanitizer doesn't work...
fmt_method = 'builtin'
fmt_dep = subproject('fmt').get_variable('fmt_dep')
else
fmt_dep = dependency('fmt')
endif

# boost and abseil are only used to compare against in the benchmarks and in
Expand All @@ -81,7 +86,7 @@ test_exe = executable(

# see what's in the [provide] sections for the dependency names
dependency('doctest'),
dependency('fmt', method: fmt_method),
fmt_dep,
],
)

Expand Down Expand Up @@ -120,7 +125,7 @@ if compiler.get_id() == 'clang'
fuzz_deps = [
dependency('doctest'),
dependency('threads'),
dependency('fmt', method: fmt_method),
fmt_dep,
]
fuzz_cpp_args = [
'-DFUZZ',
Expand Down
Loading