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
51 changes: 51 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,57 @@ jobs:
export LD_LIBRARY_PATH="$(dirname "$(find "$PWD/prefix" -name 'libspill.so.0.*')")"
./consumer/c_use && ./consumer/cxx_use && ./consumer/f_use

# Windows. Psi4 and OpenMolcas both ship Windows builds, so this is not a
# courtesy target: an adopter replacing its own I/O layer cannot carry a
# dependency that drops a platform it already supports. MSVC and clang-cl
# both, because clang-cl is what Psi4's Windows CI actually uses and the two
# disagree often enough to be worth separating.
windows:
name: windows (${{ matrix.toolset }})
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
include:
- toolset: msvc
flags: ""
- toolset: clang-cl
flags: "-T ClangCL"
steps:
- uses: actions/checkout@v4
- name: Configure
run: >
cmake -S . -B build ${{ matrix.flags }}
-DLIBSPILL_BUILD_TESTS=ON
-DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/prefix
- name: Build
run: cmake --build build --config Release -j
- name: Test
run: ctest --test-dir build -C Release --output-on-failure
- name: Install and consume
shell: bash
run: |
cmake --install build --config Release
cmake -S tests/consumer -B consumer \
-Dlibspill_DIR="$(dirname "$(find "$PWD/prefix" -name libspillConfig.cmake)")"
cmake --build consumer --config Release -j

# The Windows code path, run on Linux. Windows has no mmap, so LS_MAPPED is
# refused there and ls_map/ls_unmap take a different branch; LS_NO_MMAP
# compiles and runs exactly those branches here, where the whole suite can
# exercise them, instead of leaving them to be discovered on Windows.
no-mmap:
name: no mapping (the Windows path, on Linux)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: sudo apt-get update -qq && sudo apt-get install -y cmake
- name: Configure, build and test
run: |
cmake -S . -B build -DLIBSPILL_BUILD_TESTS=ON -DCMAKE_C_FLAGS=-DLS_NO_MMAP
cmake --build build -j
ctest --test-dir build --output-on-failure

# The suite must also pass with every optional piece absent -- that is what a
# code taking only the POSIX core would build.
minimal:
Expand Down
33 changes: 31 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,33 @@ add_library(spill
src/async.c)
add_library(libspill::spill ALIAS spill)

# The platform floor: every system call the library makes goes through os.h, so
# that exactly one of these two files is built and nothing else is conditional.
if(WIN32)
target_sources(spill PRIVATE src/os_win32.c)
else()
target_sources(spill PRIVATE src/os_posix.c)
endif()

target_include_directories(spill
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)

target_compile_features(spill PRIVATE c_std_99)

# Windows exports nothing from a DLL unless it is marked, so LS_API has to
# become __declspec(dllexport) while this library is being built and
# __declspec(dllimport) in a consumer -- see the LS_API block in libspill.h.
# LIBSPILL_STATIC is PUBLIC because a consumer of a static build must see it
# too, or it will look for imports that are not there.
if(WIN32)
target_compile_definitions(spill PRIVATE LIBSPILL_BUILD)
if(NOT BUILD_SHARED_LIBS)
target_compile_definitions(spill PUBLIC LIBSPILL_STATIC)
endif()
endif()

# The optional HDF5 backend. Off by default: §7b keeps POSIX the default and the
# only backend that can deliver asynchrony, and HDF5 is here for codes that
# already link it and want scratch files h5ls can read. A library built without
Expand All @@ -64,8 +84,13 @@ endif()
# because a consumer of the public header needs none of them.
target_compile_definitions(spill PRIVATE _GNU_SOURCE)

find_package(Threads REQUIRED)
target_link_libraries(spill PUBLIC Threads::Threads)
# Windows threading is SRWLOCK and CONDITION_VARIABLE out of kernel32 -- there
# is nothing to find and nothing to link, and asking for Threads::Threads there
# would fail the configure over a dependency that does not exist.
if(NOT WIN32)
find_package(Threads REQUIRED)
target_link_libraries(spill PUBLIC Threads::Threads)
endif()

set_target_properties(spill PROPERTIES
VERSION ${PROJECT_VERSION}
Expand Down Expand Up @@ -183,6 +208,10 @@ if(LIBSPILL_BUILD_TESTS)
add_test(NAME ${t} COMMAND ${t})
endforeach()

# test_mapped is entirely about LS_MAPPED, so where the platform has no
# mapping it skips rather than fails. Nothing else in the suite is optional.
set_tests_properties(test_mapped PROPERTIES SKIP_RETURN_CODE 77)

# Built at both Fortran integer widths; a mismatch is invisible to the
# toolchain, so it has to be caught here.
foreach(w 0 1)
Expand Down
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ FCFLAGS += -std=f2008 -Wall -Jfortran -Ifortran
DEPFLAGS = -MMD -MP
LDLIBS += -lpthread

SRC := src/error.c src/toc.c src/alloc.c src/store.c src/open.c src/async.c
SRC := src/error.c src/toc.c src/alloc.c src/store.c src/open.c src/async.c \
src/os_posix.c

# The optional HDF5 backend (§7b). Enabled when the headers are present; a
# library built without them still accepts LS_HDF5 at compile time and refuses
Expand Down Expand Up @@ -136,7 +137,7 @@ check-install:
LD_LIBRARY_PATH=$$(dirname $$(find $(INSTALL_TEST_DIR)/prefix -name 'libspill.so.0.*')) \
sh -c '$(INSTALL_TEST_DIR)/consumer/c_use && \
$(INSTALL_TEST_DIR)/consumer/cxx_use && \
$(INSTALL_TEST_DIR)/consumer/f_use' && \
{ [ ! -x $(INSTALL_TEST_DIR)/consumer/f_use ] || $(INSTALL_TEST_DIR)/consumer/f_use; }' && \
rm -rf $(INSTALL_TEST_DIR)

check-clean:
Expand Down
15 changes: 13 additions & 2 deletions include/libspill.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,19 @@ extern "C" {
* not the same as encapsulating them: without this, ls_rw, ls_toc_find,
* ls_pool_start and thirty others would be linkable -- and therefore, in
* practice, part of the ABI we promised to keep. */
/* On Windows the same intent needs the opposite default: nothing is exported
* from a DLL unless it is marked, so LS_API has to be the mark. LIBSPILL_BUILD
* is defined by this library's own build (see CMakeLists.txt) and by nothing
* else, which is what makes the same declaration mean "export" here and
* "import" in a consumer. A static build defines neither and wants neither. */
#if defined(_WIN32) || defined(__CYGWIN__)
# define LS_API
# if defined(LIBSPILL_STATIC)
# define LS_API
# elif defined(LIBSPILL_BUILD)
# define LS_API __declspec(dllexport)
# else
# define LS_API __declspec(dllimport)
# endif
#elif defined(__GNUC__) && (__GNUC__ >= 4)
# define LS_API __attribute__((visibility("default")))
#else
Expand Down Expand Up @@ -182,7 +193,7 @@ typedef struct {
* header keeps working against a newer library. Always obtain an ls_opts from
* ls_opts_default rather than declaring one and filling it in. */

LS_API /* Fills o with defaults. Call it through the ls_opts_default macro below, which
/* Fills o with defaults. Call it through the ls_opts_default macro below, which
* passes the version YOUR header declares -- the library then writes only the
* fields that version defines, so linking a newer libspill against a struct
* compiled from an older header cannot write past its end. */
Expand Down
23 changes: 11 additions & 12 deletions src/alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
*/
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include "internal.h"
Expand Down Expand Up @@ -71,7 +70,7 @@ static int fl_insert(ls_store *s, uint64_t foff, uint64_t len)
if (s->nfl && s->fl[s->nfl - 1].foff + s->fl[s->nfl - 1].len == s->file_end) {
s->file_end = s->fl[s->nfl - 1].foff;
s->nfl--;
if (ftruncate(s->fd, (off_t)s->file_end) != 0)
if (ls_os_ftruncate(s->fd, s->file_end) != 0)
ls_report(s, -errno, NULL, s->file_end, 0, "truncating freed tail");
}
return LS_OK;
Expand All @@ -90,7 +89,7 @@ int ls_alloc_best(ls_store *s, uint64_t need, ls_extent *out)
need = round_up(need);
if (need == 0) return 0;

pthread_mutex_lock(&s->alloc_lk);
ls_mutex_lock(&s->alloc_lk);
for (i = 0; i < s->nfl; i++)
if (s->fl[i].len >= need &&
(best == (size_t)-1 || s->fl[i].len < s->fl[best].len))
Expand All @@ -107,7 +106,7 @@ int ls_alloc_best(ls_store *s, uint64_t need, ls_extent *out)
s->fl[best].len -= need;
}
}
pthread_mutex_unlock(&s->alloc_lk);
ls_mutex_unlock(&s->alloc_lk);
return best != (size_t)-1;
}

Expand All @@ -116,7 +115,7 @@ int ls_alloc_largest(ls_store *s, uint64_t least, ls_extent *out)
{
size_t i, big = (size_t)-1;

pthread_mutex_lock(&s->alloc_lk);
ls_mutex_lock(&s->alloc_lk);
for (i = 0; i < s->nfl; i++)
if (big == (size_t)-1 || s->fl[i].len > s->fl[big].len) big = i;
if (big != (size_t)-1 && s->fl[big].len >= round_up(least)) {
Expand All @@ -128,19 +127,19 @@ int ls_alloc_largest(ls_store *s, uint64_t least, ls_extent *out)
} else {
big = (size_t)-1;
}
pthread_mutex_unlock(&s->alloc_lk);
ls_mutex_unlock(&s->alloc_lk);
return big != (size_t)-1;
}

/* Fresh space past the end of the file. */
void ls_alloc_tail(ls_store *s, uint64_t need, ls_extent *out)
{
need = round_up(need);
pthread_mutex_lock(&s->alloc_lk);
ls_mutex_lock(&s->alloc_lk);
out->foff = s->file_end;
out->len = need;
s->file_end += need;
pthread_mutex_unlock(&s->alloc_lk);
ls_mutex_unlock(&s->alloc_lk);
}

void ls_free_extents(ls_store *s, ls_extent *e, size_t n)
Expand All @@ -151,18 +150,18 @@ void ls_free_extents(ls_store *s, ls_extent *e, size_t n)
* already been written; recycling their extents would shrink the free list
* back over the table of contents we just persisted. */
if (s->teardown) return;
pthread_mutex_lock(&s->alloc_lk);
ls_mutex_lock(&s->alloc_lk);
for (i = 0; i < n; i++)
(void)fl_insert(s, e[i].foff, e[i].len);
pthread_mutex_unlock(&s->alloc_lk);
ls_mutex_unlock(&s->alloc_lk);
}

uint64_t ls_alloc_live(ls_store *s)
{
uint64_t hole = 0, i;
pthread_mutex_lock(&s->alloc_lk);
ls_mutex_lock(&s->alloc_lk);
for (i = 0; i < s->nfl; i++) hole += s->fl[i].len;
i = s->file_end - LS_SUPER_SIZE - hole;
pthread_mutex_unlock(&s->alloc_lk);
ls_mutex_unlock(&s->alloc_lk);
return i;
}
Loading
Loading