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
62 changes: 60 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,62 @@ jobs:
run: |
docker run --rm liblpm-go:ci

# Python bindings test
test-python-bindings:
name: Test Python Bindings
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ['3.10', '3.11', '3.12']

steps:
- name: Checkout code
uses: actions/checkout@v6
with:
submodules: recursive

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential cmake
python -m pip install --upgrade pip
python -m pip install cython pytest pytest-cov scikit-build-core

- name: Build liblpm
run: |
mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DLPM_TS_RESOLVERS=ON
make -j$(nproc)
sudo make install
sudo ldconfig

- name: Build Python bindings
working-directory: bindings/python
run: |
pip install -e ".[dev]"

- name: Run Python tests
working-directory: bindings/python
env:
# Workaround for IFUNC/dlopen incompatibility
LD_PRELOAD: /usr/local/lib/liblpm.so.1
run: |
pytest tests/ -v --cov=liblpm --cov-report=xml

- name: Upload coverage
if: matrix.python-version == '3.11'
uses: codecov/codecov-action@v4
with:
file: bindings/python/coverage.xml
flags: python
fail_ci_if_error: false

# Code quality checks
code-quality:
name: Code Quality
Expand Down Expand Up @@ -167,7 +223,7 @@ jobs:
ci-summary:
name: CI Summary
runs-on: ubuntu-latest
needs: [build-and-test, test-cpp-bindings, test-go-bindings, code-quality]
needs: [build-and-test, test-cpp-bindings, test-go-bindings, test-python-bindings, code-quality]
if: always()

steps:
Expand All @@ -177,12 +233,14 @@ jobs:
echo "Build and test: ${{ needs.build-and-test.result }}"
echo "C++ bindings: ${{ needs.test-cpp-bindings.result }}"
echo "Go bindings: ${{ needs.test-go-bindings.result }}"
echo "Python bindings: ${{ needs.test-python-bindings.result }}"
echo "Code quality: ${{ needs.code-quality.result }}"

# Fail if any required job failed
if [[ "${{ needs.build-and-test.result }}" == "failure" ]] || \
[[ "${{ needs.test-cpp-bindings.result }}" == "failure" ]] || \
[[ "${{ needs.test-go-bindings.result }}" == "failure" ]]; then
[[ "${{ needs.test-go-bindings.result }}" == "failure" ]] || \
[[ "${{ needs.test-python-bindings.result }}" == "failure" ]]; then
echo "One or more required jobs failed"
exit 1
fi
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ build_clang/
build_gcc/
build_*/
build_afl
build-*
run_fuzz.sh
fuzz_input
fuzz_output
Expand Down
39 changes: 36 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,22 @@ endif()
option(BUILD_TESTS "Build test programs" ON)
option(BUILD_BENCHMARKS "Build benchmark programs" OFF)
option(ENABLE_NATIVE_ARCH "Enable native architecture optimizations" OFF)
option(WITH_DPDK_BENCHMARK "Build DPDK comparison benchmark (requires DPDK)" OFF)
option(WITH_EXTERNAL_LPM_BENCHMARK "Build benchmarks with external LPM libraries (Docker only)" OFF)
option(WITH_DPDK_BENCHMARK "Build DPDK comparison benchmark" OFF)
option(WITH_EXTERNAL_LPM_BENCHMARK "Build benchmarks with external LPM libraries" OFF)
option(BUILD_GO_WRAPPER "Build Go wrapper and bindings" OFF)
option(BUILD_CPP_WRAPPER "Build C++ wrapper and bindings" OFF)
option(BUILD_PYTHON_WRAPPER "Build Python wrapper and bindings" OFF)
option(LPM_TS_RESOLVERS "Enable thread-safe resolvers (for dlopen contexts)" OFF)

# External LPM libraries directory (set by Docker builds)
# Thread-safe resolver configuration
if(LPM_TS_RESOLVERS)
add_compile_definitions(LPM_TS_RESOLVERS=1)
message(STATUS "Thread-safe resolvers: ENABLED (for dlopen contexts)")
else()
message(STATUS "Thread-safe resolvers: DISABLED (standard C library)")
endif()

# External LPM libraries directory
set(EXTERNAL_LPM_DIR "" CACHE PATH "Directory containing external LPM libraries")

# Check for external LPM libraries if requested
Expand Down Expand Up @@ -315,6 +325,24 @@ if(BUILD_CPP_WRAPPER)
add_subdirectory(bindings/cpp)
endif()

# Python wrapper
if(BUILD_PYTHON_WRAPPER)
find_package(Python COMPONENTS Interpreter Development.Module)
find_program(CYTHON_EXECUTABLE NAMES cython cython3)
if(Python_FOUND AND CYTHON_EXECUTABLE)
message(STATUS "Found Python: ${Python_EXECUTABLE} (${Python_VERSION})")
message(STATUS "Found Cython: ${CYTHON_EXECUTABLE}")
add_subdirectory(bindings/python)
else()
if(NOT Python_FOUND)
message(WARNING "Python not found, skipping Python bindings")
endif()
if(NOT CYTHON_EXECUTABLE)
message(WARNING "Cython not found, skipping Python bindings. Install with: pip install cython")
endif()
endif()
endif()

# Package configuration for find_package(liblpm)
include(CMakePackageConfigHelpers)

Expand Down Expand Up @@ -435,6 +463,11 @@ if(BUILD_CPP_WRAPPER)
else()
message(STATUS " Build C++ wrapper: OFF")
endif()
if(BUILD_PYTHON_WRAPPER AND Python_FOUND AND CYTHON_EXECUTABLE)
message(STATUS " Build Python wrapper: ON")
else()
message(STATUS " Build Python wrapper: OFF")
endif()

# ============================================================================
# CPack Configuration for .deb and .rpm packages
Expand Down
39 changes: 39 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,45 @@ Comprehensive man pages for the library API, accessible via `man liblpm`.

**Location:** `docs/man/man3/` (English), `docs/man/<lang>/man3/` (translations)

## Thread-Safe Resolver Improvements

**Status:** Implemented (January 2026)

**Description:**
Optional thread-safe resolver support for dlopen() contexts via CMake configuration.

**Implemented:**
- [x] CMake option `LPM_TS_RESOLVERS` (default OFF)
- [x] Conditional compilation macro `LPM_DETECT_SIMD()`
- [x] Automatic enablement for Python bindings
- [x] Documentation in README and Python IFUNC_WORKAROUND.md

**Future Enhancements:**
- [ ] Performance benchmarks comparing TS vs non-TS overhead
- [ ] Thread-safety stress tests for concurrent initialization
- [ ] Testing with different dynamic loaders (musl, bionic)
- [ ] Investigate IFUNC alternatives for better portability
- [ ] Go bindings: evaluate if thread-safe mode needed
- [ ] C++ bindings: evaluate if thread-safe mode needed
- [ ] Add runtime detection of dlopen() context (if feasible)

**Testing Needs:**
- [ ] Concurrent initialization stress test (pthread_create during IFUNC resolve)
- [ ] Memory ordering verification for atomic caching
- [ ] Python multiprocessing edge cases
- [ ] Plugin loader compatibility tests
- [ ] Valgrind/Helgrind analysis of atomic operations

**Documentation:**
- [ ] Add architecture diagram showing resolver flow in both modes
- [ ] Performance comparison table (TS vs non-TS)
- [ ] Best practices guide for binding authors

**Benefits:**
- Zero overhead for regular C programs (default mode)
- Safe for Python and other dlopen() contexts (opt-in)
- Flexible configuration per binding type

## Related Documentation

- [docs/DOCKER.md](docs/DOCKER.md) - Container usage guide
Expand Down
52 changes: 52 additions & 0 deletions bindings/php/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Build artifacts
*.lo
*.la
*.o
*.so
.libs/
.deps/
modules/

# Autotools generated files
autom4te.cache/
Makefile
Makefile.fragments
Makefile.objects
acinclude.m4
aclocal.m4
build/
config.guess
config.h
config.h.in~
config.log
config.nice
config.status
config.sub
configure
configure.ac~
install-sh
libtool
ltmain.sh
missing
mkinstalldirs
run-tests.php

# Dependency files
*.dep

# PHP extension build files
*.gcda
*.gcno

# Test results
tests/*.diff
tests/*.exp
tests/*.log
tests/*.out
tests/*.php
tests/*.sh

# Backup files
*~
*.bak
*.swp
121 changes: 121 additions & 0 deletions bindings/php/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# CMakeLists.txt for liblpm PHP extension
#
# This file enables building the PHP extension using CMake directly.
# For production use, prefer the phpize/configure/make workflow.
#
# Usage:
# mkdir build && cd build
# cmake -DBUILD_PHP_WRAPPER=ON ..
# make php_wrapper
# make php_test
# make php_install
#
# Or standalone (requires liblpm to be installed):
# cd bindings/php
# phpize
# ./configure --with-liblpm=/usr/local
# make
# make test
# sudo make install

cmake_minimum_required(VERSION 3.16)
project(liblpm_php LANGUAGES C)

# Find php-config
find_program(PHP_CONFIG_EXECUTABLE NAMES php-config php-config8.3 php-config8.2 php-config8.1)

if(NOT PHP_CONFIG_EXECUTABLE)
message(FATAL_ERROR "php-config not found. Install PHP development files.")
endif()

# Get PHP configuration
execute_process(
COMMAND ${PHP_CONFIG_EXECUTABLE} --version
OUTPUT_VARIABLE PHP_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
)

execute_process(
COMMAND ${PHP_CONFIG_EXECUTABLE} --includes
OUTPUT_VARIABLE PHP_INCLUDES
OUTPUT_STRIP_TRAILING_WHITESPACE
)

execute_process(
COMMAND ${PHP_CONFIG_EXECUTABLE} --extension-dir
OUTPUT_VARIABLE PHP_EXTENSION_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE
)

execute_process(
COMMAND ${PHP_CONFIG_EXECUTABLE} --php-binary
OUTPUT_VARIABLE PHP_BINARY
OUTPUT_STRIP_TRAILING_WHITESPACE
)

message(STATUS "PHP version: ${PHP_VERSION}")
message(STATUS "PHP extension dir: ${PHP_EXTENSION_DIR}")

# Parse include flags
separate_arguments(PHP_INCLUDE_FLAGS UNIX_COMMAND "${PHP_INCLUDES}")

# Find liblpm
find_library(LPM_LIBRARY NAMES lpm PATHS /usr/local/lib /usr/lib)
find_path(LPM_INCLUDE_DIR lpm/lpm.h PATHS /usr/local/include /usr/include)

if(NOT LPM_LIBRARY OR NOT LPM_INCLUDE_DIR)
# Try parent project
if(TARGET lpm)
set(LPM_LIBRARY lpm)
set(LPM_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/include)
else()
message(FATAL_ERROR "liblpm not found. Install liblpm or build from parent project.")
endif()
endif()

message(STATUS "liblpm library: ${LPM_LIBRARY}")
message(STATUS "liblpm include: ${LPM_INCLUDE_DIR}")

# Build the PHP extension as a shared library
add_library(liblpm_php MODULE
liblpm.c
)

# Set output name to liblpm (produces liblpm.so)
set_target_properties(liblpm_php PROPERTIES
OUTPUT_NAME liblpm
PREFIX ""
SUFFIX ".so"
)

# Add PHP include paths
target_include_directories(liblpm_php PRIVATE
${LPM_INCLUDE_DIR}
)

# Add PHP include flags
target_compile_options(liblpm_php PRIVATE
${PHP_INCLUDE_FLAGS}
-DCOMPILE_DL_LIBLPM
-DHAVE_CONFIG_H
)

# Link against liblpm
target_link_libraries(liblpm_php PRIVATE
${LPM_LIBRARY}
)

# Installation
install(TARGETS liblpm_php
LIBRARY DESTINATION ${PHP_EXTENSION_DIR}
)

# Test target
add_custom_target(test_php
COMMAND ${CMAKE_COMMAND} -E env "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}:$ENV{LD_LIBRARY_PATH}"
${PHP_BINARY} -d "extension=${CMAKE_CURRENT_BINARY_DIR}/liblpm.so"
${CMAKE_CURRENT_SOURCE_DIR}/tests/run_tests.php
DEPENDS liblpm_php
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Running PHP extension tests"
)
Loading
Loading