diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 3c3f79b0d8..95d91f4fac 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -11,7 +11,12 @@ on: - 'CMakeLists.txt' - 'config/**' - 'configure.ac' + - 'include/meson.build' + - 'include/flint/meson.build' - 'Makefile.in' + - 'meson.build' + - 'meson.options' + - 'update_meson.py' push: paths: - '.github/workflows/CI.yml' @@ -22,7 +27,12 @@ on: - 'CMakeLists.txt' - 'config/**' - 'configure.ac' + - 'include/meson.build' + - 'include/flint/meson.build' - 'Makefile.in' + - 'meson.build' + - 'meson.options' + - 'update_meson.py' env: GLOBAL_MULTIPLIER: 1 @@ -34,6 +44,31 @@ concurrency: cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} jobs: + ubuntu-meson: + name: Meson, GCC (x0.5) + runs-on: ubuntu-24.04 + env: + CC: "gcc" + TESTCOEFF: "0.5" + steps: + - name: "Rescale multiplier" + run: echo "FLINT_TEST_MULTIPLIER=$(echo "${TESTCOEFF} * ${GLOBAL_MULTIPLIER}" | bc)" >> $GITHUB_ENV + - uses: actions/checkout@v5 + - name: "Setup" + run: | + sudo apt-get install -y libgmp-dev libmpfr-dev meson ninja-build pkg-config + $CC --version + meson --version + ninja --version + - name: "Generate Meson files" + run: python3 update_meson.py + - name: "Configure" + run: meson setup build + - name: "Compile" + run: meson compile -C build + - name: "Check" + run: echo "FLINT_TEST_MULTIPLIER = $FLINT_TEST_MULTIPLIER" && meson test -C build --print-errorlogs + assert-ntl-msolve-nemo: name: NTL, msolve and Nemo.jl runs-on: ubuntu-24.04 @@ -44,21 +79,27 @@ jobs: - uses: actions/checkout@v5 - name: "Setup" run: | - sudo apt-get install -y libgmp-dev libmpfr-dev libntl-dev autoconf libtool-bin + sudo apt-get install -y libgmp-dev libmpfr-dev libntl-dev autoconf libtool-bin meson ninja-build pkg-config $CC --version make --version autoconf --version libtool --version + meson --version + ninja --version julia --version echo "MAKE=make -j$(expr $(nproc) + 1) --output-sync=target" >> $GITHUB_ENV + - name: "Generate Meson files" + run: python3 update_meson.py - name: "Build FLINT for msolve" run: | - ./bootstrap.sh && ./configure CC=${CC} --with-ntl --enable-assert - $MAKE && sudo make install && sudo ldconfig + meson setup build -Dntl=enabled -Dassert=enabled + meson compile -C build + sudo meson install -C build + sudo ldconfig - name: "Test FLINT for msolve" run: | - $MAKE tests - $MAKE check + echo "FLINT_TEST_MULTIPLIER = $FLINT_TEST_MULTIPLIER" + meson test -C build --print-errorlogs - name: "Test msolve" run: | git clone --depth=1 https://github.com/algebraic-solving/msolve @@ -68,14 +109,14 @@ jobs: gmp_path=$(julia -e 'include("dev/find_gmp_mpfr.jl"); print(gmp_artifact_dir())') mpfr_path=$(julia -e 'include("dev/find_gmp_mpfr.jl"); print(mpfr_artifact_dir())') echo "Path to GMP and MPFR: ${gmp_path}, ${mpfr_path}" - ./bootstrap.sh && ./configure CC=${CC} --with-gmp=$gmp_path --with-mpfr=$mpfr_path && $MAKE - mkdir lib - cp libflint.so* lib/ + PKG_CONFIG_PATH="${gmp_path}/lib/pkgconfig:${mpfr_path}/lib/pkgconfig" meson setup build-nemo --prefix="$(pwd)/nemo-flint" -Dlibdir=lib + meson compile -C build-nemo + meson install -C build-nemo - name: "Test Nemo.jl" run: | git clone --depth 1 https://github.com/Nemocas/Nemo.jl.git mkdir -p ~/.julia/artifacts - echo -e "[e134572f-a0d5-539d-bddf-3cad8db41a82]\nFLINT = \"$(pwd)\"" > ~/.julia/artifacts/Overrides.toml + echo -e "[e134572f-a0d5-539d-bddf-3cad8db41a82]\nFLINT = \"$(pwd)/nemo-flint\"" > ~/.julia/artifacts/Overrides.toml julia -e "import Pkg; Pkg.develop(path=\"./Nemo.jl\"); Pkg.test(\"Nemo\")" ubuntu-codecov: @@ -90,31 +131,30 @@ jobs: - uses: actions/checkout@v5 - name: "Setup" run: | - sudo apt-get install -y libgmp-dev libmpfr-dev autoconf libtool-bin lcov + sudo apt-get install -y libgmp-dev libmpfr-dev meson ninja-build pkg-config lcov gcovr $CC --version - make --version - autoconf --version - libtool --version + meson --version + ninja --version lcov --version + gcovr --version python3 --version - echo "MAKE=make -j$(expr $(nproc) + 1) --output-sync=target" >> $GITHUB_ENV + - name: "Generate Meson files" + run: python3 update_meson.py - name: "Configure" - run: ./bootstrap.sh && ./configure CC=${CC} --enable-avx2 --enable-coverage - - name: "Compile library" - run: $MAKE && ldd libflint.so - - name: "Compile tests" - run: $MAKE tests + run: meson setup build -Davx2=enabled -Db_coverage=true + - name: "Compile" + run: meson compile -C build && ldd build/libflint.so - name: "Check" - run: echo "FLINT_TEST_MULTIPLIER = $FLINT_TEST_MULTIPLIER" && $MAKE check + run: echo "FLINT_TEST_MULTIPLIER = $FLINT_TEST_MULTIPLIER" && meson test -C build --print-errorlogs - name: "Check Python" - run: $MAKE check PYTHON=1 + run: LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$(pwd)/build python3 src/python/flint_ctypes.py - name: "Gather coverage data" - run: $MAKE coverage + run: ninja -C build coverage - name: "Upload coverage data" uses: codecov/codecov-action@v5 if: github.repository == 'flintlib/flint' with: - files: build/coverage/coverage.info + files: build/meson-logs/coverage.info token: ${{ secrets.CODECOV_TOKEN }} regression-check: @@ -122,26 +162,23 @@ jobs: runs-on: ubuntu-24.04 env: CC: "gcc" - CFLAGS: "-march=native -std=c11 -Wall -Wextra -Werror" - MODULES: "build/thread_pool_merged.lo build/thread_support_merged.lo build/ulong_extras_merged.lo build/long_extras_merged.lo build/perm_merged.lo build/double_extras_merged.lo build/d_vec_merged.lo build/d_mat_merged.lo build/mpn_extras_merged.lo build/nmod_merged.lo build/nmod_vec_merged.lo build/nmod_mat_merged.lo build/nmod_poly_merged.lo build/mpn_mod_merged.lo build/fmpz_merged.lo build/fmpz_vec_merged.lo build/fmpz_mat_merged.lo build/fmpz_poly_merged.lo build/fmpz_mod_merged.lo build/fmpz_mod_vec_merged.lo build/fmpz_mod_mat_merged.lo build/fmpz_mod_poly_merged.lo build/fmpq_merged.lo build/fmpq_vec_merged.lo build/fmpq_mat_merged.lo build/fmpq_poly_merged.lo build/fq_merged.lo build/fq_vec_merged.lo build/fq_mat_merged.lo build/fq_poly_merged.lo build/fq_nmod_merged.lo build/fq_nmod_vec_merged.lo build/fq_nmod_mat_merged.lo build/fq_nmod_poly_merged.lo build/fq_zech_merged.lo build/fq_zech_mat_merged.lo build/fq_zech_poly_merged.lo build/fq_default_merged.lo build/fq_default_mat_merged.lo build/fq_default_poly_merged.lo build/fq_embed_merged.lo build/fq_nmod_embed_merged.lo build/fq_zech_embed_merged.lo build/padic_merged.lo build/padic_mat_merged.lo build/padic_poly_merged.lo build/qadic_merged.lo build/nmod_poly_factor_merged.lo build/fmpz_factor_merged.lo build/fmpz_poly_factor_merged.lo build/fmpz_mod_poly_factor_merged.lo build/fq_poly_factor_merged.lo build/fq_nmod_poly_factor_merged.lo build/fq_zech_poly_factor_merged.lo build/fq_default_poly_factor_merged.lo build/nmod_poly_mat_merged.lo build/fmpz_poly_mat_merged.lo build/mpoly_merged.lo build/nmod_mpoly_merged.lo build/fmpz_mpoly_merged.lo build/fmpz_mod_mpoly_merged.lo build/fmpq_mpoly_merged.lo build/fq_nmod_mpoly_merged.lo build/fq_zech_mpoly_merged.lo build/nmod_mpoly_factor_merged.lo build/fmpz_mpoly_factor_merged.lo build/fmpz_mod_mpoly_factor_merged.lo build/fmpq_mpoly_factor_merged.lo build/fq_nmod_mpoly_factor_merged.lo build/fq_zech_mpoly_factor_merged.lo build/fft_merged.lo build/fft_small_merged.lo" - # Everything until fmpz_poly_q steps: - uses: actions/checkout@v5 - name: "Setup" run: | - sudo apt-get install -y libgmp-dev libmpfr-dev autoconf libtool-bin + sudo apt-get install -y libgmp-dev libmpfr-dev meson ninja-build pkg-config $CC --version - make --version - autoconf --version - libtool --version - echo "MAKE=make -j$(expr $(nproc) + 1) --output-sync=target" >> $GITHUB_ENV + meson --version + ninja --version + - name: "Generate Meson files" + run: python3 update_meson.py - name: "Configure" - run: ./bootstrap.sh && ./configure CC=${CC} CFLAGS="${CFLAGS}" --disable-debug + run: meson setup build - name: "Check regression in a subset of modules" - run: $MAKE $MODULES + run: meson compile -C build regression-check ubuntu-arm: - name: ARM (x0.5) + name: Meson, ARM (x0.5) runs-on: ubuntu-24.04-arm env: CC: "gcc" @@ -152,23 +189,21 @@ jobs: - uses: actions/checkout@v5 - name: "Setup" run: | - sudo apt-get install -y libgmp-dev libmpfr-dev autoconf libtool-bin + sudo apt-get install -y libgmp-dev libmpfr-dev meson ninja-build pkg-config $CC --version - make --version - autoconf --version - libtool --version - echo "MAKE=make -j$(expr $(nproc) + 1) --output-sync=target" >> $GITHUB_ENV + meson --version + ninja --version + - name: "Generate Meson files" + run: python3 update_meson.py - name: "Configure" - run: ./bootstrap.sh && ./configure CC=${CC} --disable-debug - - name: "Compile library" - run: $MAKE && ldd libflint.so - - name: "Compile tests" - run: $MAKE tests + run: meson setup build + - name: "Compile" + run: meson compile -C build - name: "Check" - run: echo "FLINT_TEST_MULTIPLIER = $FLINT_TEST_MULTIPLIER" && $MAKE check + run: echo "FLINT_TEST_MULTIPLIER = $FLINT_TEST_MULTIPLIER" && meson test -C build --print-errorlogs macos-m1: - name: M1 (x0.5) + name: Meson, M1 (x0.5) runs-on: macos-26 env: TESTCOEFF: "0.5" @@ -178,21 +213,20 @@ jobs: - uses: actions/checkout@v5 - name: "Setup" run: | - brew install make autoconf libtool automake llvm + brew install gmp mpfr meson ninja pkg-config llvm + echo "CC=$(brew --prefix llvm)/bin/clang" >> $GITHUB_ENV + echo "PKG_CONFIG_PATH=$(brew --prefix gmp)/lib/pkgconfig:$(brew --prefix mpfr)/lib/pkgconfig" >> $GITHUB_ENV $(brew --prefix llvm)/bin/clang --version - gmake --version - autoconf --version - echo "MAKE=gmake -j$(expr $(nproc) + 1) -l 10 --output-sync=target" >> $GITHUB_ENV + meson --version + ninja --version + - name: "Generate Meson files" + run: python3 update_meson.py - name: "Configure" - run: ./bootstrap.sh && ./configure CC=$(brew --prefix llvm)/bin/clang --with-gmp=$(brew --prefix) --with-mpfr=$(brew --prefix) --disable-debug - - name: "Compile library" - run: $MAKE - - name: "Compile tests" - run: $MAKE tests - - name: "Compile profiles" - run: $MAKE profile + run: meson setup build + - name: "Compile" + run: meson compile -C build - name: "Check" - run: echo "FLINT_TEST_MULTIPLIER = $FLINT_TEST_MULTIPLIER" && $MAKE check + run: echo "FLINT_TEST_MULTIPLIER = $FLINT_TEST_MULTIPLIER" && meson test -C build --print-errorlogs ubuntu-clang: name: ASan, BLAS, examples (x1) @@ -206,27 +240,34 @@ jobs: - uses: actions/checkout@v5 - name: "Setup" run: | - sudo apt-get install -y libgmp-dev libmpfr-dev autoconf libtool-bin perl libopenblas-dev + sudo apt-get install -y libgmp-dev libmpfr-dev meson ninja-build pkg-config perl libopenblas-dev clang --version - make --version - autoconf --version - libtool --version - echo "MAKE=make -j$(expr $(nproc) + 1) --output-sync=target" >> $GITHUB_ENV + meson --version + ninja --version + - name: "Generate Meson files" + run: python3 update_meson.py - name: "Configure" - run: ./bootstrap.sh && ./configure CC=${CC} --with-blas --disable-debug --enable-asan - - name: "Compile library" - run: $MAKE && ldd libflint.so - - name: "Compile tests" - run: $MAKE tests + run: meson setup build -Dblas=enabled -Db_sanitize=address -Db_lundef=false + - name: "Compile" + run: meson compile -C build - name: "Check" - run: echo "FLINT_TEST_MULTIPLIER = $FLINT_TEST_MULTIPLIER" && $MAKE check - - name: "Compile examples" - run: $MAKE examples + env: + ASAN_OPTIONS: halt_on_error=1:abort_on_error=1:print_summary=1 + UBSAN_OPTIONS: halt_on_error=1:abort_on_error=1:print_summary=1:print_stacktrace=1 + run: echo "FLINT_TEST_MULTIPLIER = $FLINT_TEST_MULTIPLIER" && meson test -C build --print-errorlogs - name: "Check examples" - run: $MAKE checkexamples + env: + ASAN_OPTIONS: halt_on_error=1:abort_on_error=1:print_summary=1 + UBSAN_OPTIONS: halt_on_error=1:abort_on_error=1:print_summary=1:print_stacktrace=1 + run: | + meson compile -C build examples + for src in examples/*.c; do + example=$(basename "$src" .c) + ./dev/check_examples.sh "$example" build/examples + done mingw64-gcc: - name: MinGW (x0.5) + name: Meson, MinGW (x0.5) runs-on: windows-latest defaults: run: @@ -241,27 +282,25 @@ jobs: with: msystem: mingw64 update: true - install: bc mingw-w64-x86_64-gcc mingw-w64-x86_64-autotools + install: bc mingw-w64-x86_64-gcc mingw-w64-x86_64-gmp mingw-w64-x86_64-mpfr mingw-w64-x86_64-meson mingw-w64-x86_64-ninja mingw-w64-x86_64-pkgconf - name: "Rescale multiplier" run: echo "FLINT_TEST_MULTIPLIER=$(echo "${TESTCOEFF} * ${GLOBAL_MULTIPLIER}" | bc)" >> $GITHUB_ENV - name: "Setup" run: | gcc --version - make --version - autoconf --version - libtool --version - echo "MAKE=make -j$(expr $(nproc) + 1) --output-sync=target" >> $GITHUB_ENV + meson --version + ninja --version + - name: "Generate Meson files" + run: python update_meson.py - name: "Configure" - run: ./bootstrap.sh && ./configure CC=${CC} --enable-fast-build --disable-debug - - name: "Compile library" - run: $MAKE - - name: "Compile tests" - run: $MAKE tests + run: meson setup build + - name: "Compile" + run: meson compile -C build - name: "Check" - run: echo "FLINT_TEST_MULTIPLIER = $FLINT_TEST_MULTIPLIER" && $MAKE check + run: echo "FLINT_TEST_MULTIPLIER = $FLINT_TEST_MULTIPLIER" && meson test -C build --print-errorlogs msvc: - name: MSVC (x0.5) + name: Meson, MSVC (x0.5) runs-on: windows-latest env: TESTCOEFF: "0.5" @@ -279,31 +318,43 @@ jobs: restore-keys: | ${{ runner.os }}-vcpkg- - name: "Install vcpkg packages" - run: vcpkg install gmp mpfr pthreads --binarysource="clear;files,${{ runner.temp }}\vcpkg\archives,readwrite" + run: vcpkg install gmp mpfr pthreads pkgconf --binarysource="clear;files,${{ runner.temp }}\vcpkg\archives,readwrite" - name: "Setup MSVC" uses: ilammy/msvc-dev-cmd@v1.13.0 with: arch: x86_64 - - name: "Configure" + - name: "Setup Meson" run: | - mkdir build; cd build - cmake ` - -G "Ninja" ` - -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake ` - -DCMAKE_C_FLAGS="/wd4018 /wd4146 /wd4244 /wd4267 /wd4305 /wd4996" ` - -DBUILD_TESTING=ON ` - -DCMAKE_BUILD_TYPE=Release ` - .. - - name: "Build" - run: cd build; cmake --build . -j5 - - name: "Check" + python -m pip install meson ninja + $pkgConfigPath = "C:/vcpkg/installed/x64-windows/lib/pkgconfig" + echo "PKG_CONFIG=C:\vcpkg\installed\x64-windows\tools\pkgconf\pkgconf.exe" | Out-File -Append -FilePath $env:GITHUB_ENV + echo "PKG_CONFIG_PATH=$pkgConfigPath" | Out-File -Append -FilePath $env:GITHUB_ENV + echo "CMAKE_PREFIX_PATH=C:/vcpkg/installed/x64-windows" | Out-File -Append -FilePath $env:GITHUB_ENV + echo "INCLUDE=C:\vcpkg\installed\x64-windows\include;$env:INCLUDE" | Out-File -Append -FilePath $env:GITHUB_ENV + echo "LIB=C:\vcpkg\installed\x64-windows\lib;$env:LIB" | Out-File -Append -FilePath $env:GITHUB_ENV + echo "C:\vcpkg\installed\x64-windows\bin" | Out-File -Append -FilePath $env:GITHUB_PATH + echo "C:\vcpkg\installed\x64-windows\tools\pkgconf" | Out-File -Append -FilePath $env:GITHUB_PATH + cl 2>&1 | Select-Object -First 3 + meson --version + ninja --version + C:\vcpkg\installed\x64-windows\tools\pkgconf\pkgconf.exe --version + - name: "Inspect vcpkg pthreads" run: | - cd build - set "FLINT_TEST_MULTIPLIER=$env:FLINT_TEST_MULTIPLIER" - ctest -j5 --output-on-failure + Get-ChildItem C:\vcpkg\installed\x64-windows\include -Filter '*pthread*' | Select-Object FullName + Get-ChildItem C:\vcpkg\installed\x64-windows\lib -Filter '*pthread*' | Select-Object FullName + Get-ChildItem C:\vcpkg\installed\x64-windows\lib\pkgconfig -Filter '*pthread*' | Select-Object FullName + Get-ChildItem C:\vcpkg\installed\x64-windows\share -Recurse -Filter '*pthread*' | Select-Object FullName + - name: "Generate Meson files" + run: python update_meson.py + - name: "Configure" + run: meson setup build + - name: "Compile" + run: meson compile -C build + - name: "Check" + run: echo "FLINT_TEST_MULTIPLIER = $env:FLINT_TEST_MULTIPLIER"; meson test -C build --print-errorlogs alpine-32bit: - name: 32-bit, musl, assert (x2.5) + name: Meson, 32-bit, musl, assert (x2.5) runs-on: ubuntu-24.04 env: CC: "gcc" @@ -318,21 +369,20 @@ jobs: with: arch: x86 branch: edge - packages: gmp-dev mpfr-dev gcc musl-dev make autoconf automake libtool + packages: gmp-dev mpfr-dev gcc musl-dev meson ninja pkgconf python3 - name: "Rescale multiplier" run: echo "FLINT_TEST_MULTIPLIER=$(echo "${TESTCOEFF} * ${GLOBAL_MULTIPLIER}" | bc)" >> $GITHUB_ENV - name: "Setup" run: | gcc --version - make --version - autoconf --version - libtool --version - echo "MAKE=make -j$(expr $(nproc) + 1) --output-sync=target" >> $GITHUB_ENV + meson --version + ninja --version + python3 --version + - name: "Generate Meson files" + run: python3 update_meson.py - name: "Configure" - run: ./bootstrap.sh && ./configure CC=${CC} --enable-assert --disable-debug - - name: "Compile library" - run: $MAKE - - name: "Compile tests" - run: $MAKE tests + run: meson setup build -Dassert=enabled + - name: "Compile" + run: meson compile -C build - name: "Check" - run: echo "FLINT_TEST_MULTIPLIER = $FLINT_TEST_MULTIPLIER" && $MAKE check + run: echo "FLINT_TEST_MULTIPLIER = $FLINT_TEST_MULTIPLIER" && meson test -C build --print-errorlogs diff --git a/.gitignore b/.gitignore index ce3195baac..6298517296 100644 --- a/.gitignore +++ b/.gitignore @@ -21,10 +21,12 @@ config/* !config/config.sub !config/configfsf.guess !config/configfsf.sub +!config/check_assembly.py +!config/generate_msvc_def.py +!config/meson.build config.log confdefs.h conftest.* -/Makefile doc/build doc/latex/flint-manual.* !doc/latex/flint-manual.tex @@ -66,3 +68,5 @@ config.m4 src/flint-mparam.h .gdb_history vgcore.* +src/**/meson.build +examples/meson.build diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..ce03d7a754 --- /dev/null +++ b/Makefile @@ -0,0 +1,164 @@ +# Convenience wrapper for the Meson build. +# +# Override BUILD to use a different build directory, for example: +# make check BUILD=build-debug + +BUILD ?= build +MESON ?= $(if $(wildcard .venv/bin/meson),.venv/bin/meson,meson) +MESON_SETUP_ARGS ?= +MESON_COMPILE_ARGS ?= +MESON_TEST_ARGS ?= +GDB ?= gdb +VALGRIND ?= valgrind +VALGRIND_ARGS ?= --track-origins=yes --leak-check=full --show-reachable=yes +DESTDIR ?= + +.PHONY: all setup library shared static test tests check debug valgrind examples checkexamples profile tune bench coverage coverage_html install uninstall clean distclean print-% + +ifneq ($(NJOBS),) +number_generator = $(words $2) $(if $(word $1,$2),,$(call number_generator,$1,w $2)) +THREAD_LIST := $(call number_generator,$(NJOBS),w) +CHECK_THREAD_TARGETS := $(foreach mod,$(MOD),$(foreach num,$(THREAD_LIST),check-$(mod)-thread-$(num))) + +define check_mod_build_rule +.PHONY: check-$(1)-build +check-$(1)-build: setup + $$(MESON) compile -C $$(BUILD) src/$(1)/test/main $$(MESON_COMPILE_ARGS) +endef + +define check_thread_rule +.PHONY: check-$(1)-thread-$(2) +check-$(1)-thread-$(2): check-$(1)-build + @exe="$$(BUILD)/src/$(1)/test/main"; \ + if [ ! -f "$$$$exe" ]; then exe="$$$$exe.exe"; fi; \ + echo "$$$$exe --numthreads=$$(NJOBS) --thread=$(2)"; \ + LD_LIBRARY_PATH="$$(BUILD):$$$$LD_LIBRARY_PATH" DYLD_LIBRARY_PATH="$$(BUILD):$$$$DYLD_LIBRARY_PATH" "$$$$exe" --numthreads=$$(NJOBS) --thread=$(2) +endef + +$(foreach mod,$(MOD),$(eval $(call check_mod_build_rule,$(mod)))) +$(foreach mod,$(MOD),$(foreach num,$(THREAD_LIST),$(eval $(call check_thread_rule,$(mod),$(num))))) +endif + +all: library + +library: setup + $(MESON) compile -C $(BUILD) $(MESON_COMPILE_ARGS) + +shared: + $(MESON) setup "$(BUILD)" --reconfigure -Ddefault_library=shared $(MESON_SETUP_ARGS) + $(MESON) compile -C $(BUILD) $(MESON_COMPILE_ARGS) + +static: + $(MESON) setup "$(BUILD)" --reconfigure -Ddefault_library=static $(MESON_SETUP_ARGS) + $(MESON) compile -C $(BUILD) $(MESON_COMPILE_ARGS) + +setup: + @if [ ! -f "$(BUILD)/build.ninja" ]; then \ + $(MESON) setup "$(BUILD)" $(MESON_SETUP_ARGS); \ + fi + +test: check + +tests: setup + $(MESON) compile -C $(BUILD) tests $(MESON_COMPILE_ARGS) + @python3 dev/create_build_links.py "$(BUILD)" + +check: setup +ifneq ($(NJOBS),) +ifdef MOD +ifndef ARGS +check: $(CHECK_THREAD_TARGETS) +endif +endif +endif + +ifdef PYTHON + $(MESON) compile -C $(BUILD) $(MESON_COMPILE_ARGS) + @LD_LIBRARY_PATH="$(BUILD):$${LD_LIBRARY_PATH}" DYLD_LIBRARY_PATH="$(BUILD):$${DYLD_LIBRARY_PATH}" python3 src/python/flint_ctypes.py + @echo '' + @echo 'All Python tests passed.' +else +ifdef ARGS +ifneq ($(words $(sort $(MOD))),1) + $(error Can only check one module with arguments) +endif + $(MESON) test -C $(BUILD) $(MESON_TEST_ARGS) --test-args='$(ARGS)' $(MOD) +else +ifdef NJOBS +ifdef MOD + @echo '' + @echo 'All tests passed for $(sort $(MOD)).' +else + $(MESON) test -C $(BUILD) $(MESON_TEST_ARGS) +endif +else + $(MESON) test -C $(BUILD) $(MESON_TEST_ARGS) $(MOD) +endif +endif +endif + +debug: setup +ifndef MOD + $(error Use make debug MOD=module ARGS='test arguments') +endif +ifndef ARGS + $(error Use make debug MOD=module ARGS='test arguments') +endif + $(MESON) test -C $(BUILD) --gdb --gdb-path $(GDB) --interactive $(MESON_TEST_ARGS) --test-args='$(ARGS)' $(MOD) + +valgrind: setup + $(MESON) test -C $(BUILD) --wrapper '$(VALGRIND) $(VALGRIND_ARGS)' $(MESON_TEST_ARGS) $(MOD) + +examples: setup + $(MESON) compile -C $(BUILD) examples $(MESON_COMPILE_ARGS) + +checkexamples: setup + $(MESON) compile -C $(BUILD) examples $(MESON_COMPILE_ARGS) + @for src in examples/*.c; do \ + example=$$(basename "$$src" .c); \ + ./dev/check_examples.sh "$$example" "$(BUILD)/examples"; \ + done + +profile: setup +ifdef MOD + $(MESON) compile -C $(BUILD) $(MOD)-profile $(MESON_COMPILE_ARGS) +else + $(MESON) compile -C $(BUILD) profile $(MESON_COMPILE_ARGS) +endif + @python3 dev/create_build_links.py "$(BUILD)" + +tune: setup + $(MESON) compile -C $(BUILD) tune $(MESON_COMPILE_ARGS) + @python3 dev/create_build_links.py "$(BUILD)" + +bench: setup + $(MESON) compile -C $(BUILD) examples fmpz-profile fmpz_factor-profile fmpz_poly_factor-profile fmpz_mpoly_factor-profile nmod_mpoly_factor-profile $(MESON_COMPILE_ARGS) + @python3 dev/create_build_links.py "$(BUILD)" + @LD_LIBRARY_PATH="$(BUILD):$${LD_LIBRARY_PATH}" DYLD_LIBRARY_PATH="$(BUILD):$${DYLD_LIBRARY_PATH}" python3 dev/bench.py + +coverage: + $(MESON) setup "$(BUILD)" --reconfigure -Db_coverage=true $(MESON_SETUP_ARGS); \ + $(MESON) test -C $(BUILD) $(MESON_TEST_ARGS) $(MOD) + $(MESON) coverage -C $(BUILD) + +coverage_html: + $(MESON) setup "$(BUILD)" --reconfigure -Db_coverage=true $(MESON_SETUP_ARGS); \ + $(MESON) test -C $(BUILD) $(MESON_TEST_ARGS) $(MOD) + $(MESON) coverage -C $(BUILD) --html + +install: setup + $(MESON) install -C $(BUILD) $(if $(DESTDIR),--destdir $(DESTDIR),) + +uninstall: + $(MESON) compile -C $(BUILD) uninstall + +clean: + @if [ -f "$(BUILD)/build.ninja" ]; then \ + $(MESON) compile -C $(BUILD) --clean; \ + fi + +distclean: + rm -rf "$(BUILD)" + +print-%: + @echo "$*=$($*)" diff --git a/capture_autotools_commands.sh b/capture_autotools_commands.sh new file mode 100755 index 0000000000..1e992e5ff3 --- /dev/null +++ b/capture_autotools_commands.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# +# Run with any make arguments e.g.: +# ./capture_autotools_commands.sh -j10 + +set -eu + +make clean +./bootstrap.sh +./configure +echo :::::::::::::::::::: +echo :::::::::::::::::::: +echo :::::::::::::::::::: +make V=1 "$@" > autotools_commands.txt diff --git a/compare_build_commands.py b/compare_build_commands.py new file mode 100644 index 0000000000..a39e1dd884 --- /dev/null +++ b/compare_build_commands.py @@ -0,0 +1,375 @@ +#!/usr/bin/env python3 +import argparse +import json +import os +import shlex +import sys +from collections import Counter, defaultdict +from pathlib import Path + + +SOURCE_EXTS = ('.c', '.cc', '.cpp', '.cxx', '.S', '.s') + + +def canonical_generated_source(path): + parts = Path(path).parts + for i, part in enumerate(parts): + if part == 'mpn_extras' and i + 2 < len(parts): + rel = Path(*parts[i:]).as_posix() + if rel.endswith('.s'): + return 'generated/' + rel + return None + + +def split_command(command): + return shlex.split(command, posix=(os.name != 'nt')) + + +def normalise_source(path, base): + p = Path(path) + if not p.is_absolute(): + p = (base / p) + try: + source = p.resolve().relative_to(Path.cwd().resolve()).as_posix() + except ValueError: + source = p.resolve().as_posix() + + generated = canonical_generated_source(source) + if generated is not None: + return generated + return source + + +def source_from_argv(argv, base): + for i, arg in enumerate(argv): + if arg == '-c' and i + 1 < len(argv): + return normalise_source(argv[i + 1], base) + + for arg in argv: + if arg.endswith(SOURCE_EXTS): + return normalise_source(arg, base) + + return None + + +def source_is_assembly(source): + return source.endswith('.s') or source.endswith('.S') + + +def is_compile_command(argv): + return '-c' in argv and any(arg.endswith(SOURCE_EXTS) for arg in argv) + + +def normalise_path_arg(arg, base): + if arg.startswith('-I') and len(arg) > 2: + prefix, path = '-I', arg[2:] + else: + prefix, path = '', arg + + p = Path(path) + if not p.is_absolute(): + p = base / p + try: + path = p.resolve().relative_to(Path.cwd().resolve()).as_posix() + except ValueError: + path = p.resolve().as_posix() + + return prefix + path + + +def normalise_relative_path(path): + p = Path(path) + if not p.is_absolute(): + p = Path.cwd() / p + try: + return p.resolve().relative_to(Path.cwd().resolve()).as_posix() + except ValueError: + return p.resolve().as_posix() + + +def is_ignored_include(include, build_dir=None): + if not include.startswith('-I'): + return False + if build_dir is None: + return False + + path = include[2:] + parts = Path(path).parts + build_parts = Path(build_dir).parts + if len(parts) < len(build_parts) + 1: + return False + + # Meson adds a private include directory for each target's object files + # (for example build/libflint.24.dylib.p). These are implementation + # details of the target split and do not correspond to autotools flags. + after_build = parts[len(build_parts):] + if parts[:len(build_parts)] != build_parts: + return False + + if after_build[0].startswith('libflint') and after_build[0].endswith('.p'): + return True + + # The generated assembly subdirectory is added only to the main library + # target. It is not relevant to C source comparison, and otherwise splits + # the inlines.c objects into a separate difference group. + return len(after_build) >= 3 and after_build[0] == 'src' and after_build[1] == 'mpn_extras' + + +def classify_args(argv, base, build_dir=None): + includes = [] + defines = [] + flags = [] + + skip_next = False + compiler_seen = False + options_with_arg = {'-o', '-MF', '-MQ', '-MT', '-x'} + ignored_flags = { + '-c', + '-MMD', + '-MD', + '-MP', + '-fdiagnostics-color=always', + } + + for i, arg in enumerate(argv): + if skip_next: + skip_next = False + continue + + if not compiler_seen: + compiler_seen = True + continue + + if arg in options_with_arg: + skip_next = True + continue + + if i > 0 and argv[i - 1] == '-c': + continue + + if arg.endswith(SOURCE_EXTS): + continue + + if arg in ignored_flags: + continue + + if arg == '-I' and i + 1 < len(argv): + include = normalise_path_arg(argv[i + 1], base) + if not is_ignored_include(include, build_dir): + includes.append(include) + skip_next = True + continue + + if arg.startswith('-I'): + include = normalise_path_arg(arg, base) + if not is_ignored_include(include, build_dir): + includes.append(include) + continue + + if arg == '-D' and i + 1 < len(argv): + defines.append('-D' + argv[i + 1]) + skip_next = True + continue + + if arg.startswith('-D'): + defines.append(arg) + continue + + flags.append(arg) + + return { + 'includes': includes, + 'defines': defines, + 'flags': flags, + } + + +def counter_diff(left, right): + left_counter = Counter(left) + right_counter = Counter(right) + only_left = list((left_counter - right_counter).elements()) + only_right = list((right_counter - left_counter).elements()) + return tuple(sorted(only_left)), tuple(sorted(only_right)) + + +def command_record(command, base, build_dir=None): + argv = split_command(command) + if not is_compile_command(argv): + return None + + source = source_from_argv(argv, base) + if source is None: + return None + + return { + 'source': source, + 'argv': argv, + 'parts': classify_args(argv, base, build_dir), + 'raw': command, + } + + +def read_autotools_commands(path): + records = {} + base = Path.cwd() + + for line in path.read_text(errors='replace').splitlines(): + line = line.strip() + if not line: + continue + + try: + record = command_record(line, base) + except ValueError: + continue + + if record is not None: + records.setdefault(record['source'], record) + + return records + + +def meson_command_is_default_library(record): + source = record['source'] + output = record.get('output', '') + + if not (source.startswith('src/') or source.startswith('generated/mpn_extras/')): + return False + if '/test/' in source or source.startswith('examples/'): + return False + if 'regression_check' in output: + return False + if '.exe.p' in output or '/test/' in output or 'examples/' in output: + return False + + return output.startswith('libflint') or '/libflint' in output + + +def read_meson_commands(build_dir): + path = build_dir / 'compile_commands.json' + if not path.exists(): + raise SystemExit(f'{path} does not exist. Run meson setup first.') + + data = json.loads(path.read_text()) + all_records = {} + selected = {} + build_dir_rel = normalise_relative_path(build_dir) + + for entry in data: + command = entry.get('command') + if command is None and 'arguments' in entry: + command = shlex.join(entry['arguments']) + if command is None: + continue + + base = Path(entry.get('directory', build_dir)) + record = command_record(command, base, build_dir_rel) + if record is None: + continue + + record['output'] = entry.get('output', '') + all_records.setdefault(record['source'], record) + + if meson_command_is_default_library(record): + selected.setdefault(record['source'], record) + + return selected, all_records + + +def print_list(title, items, max_items=None): + print(title) + if not items: + print(' none') + return + printed_items = items if max_items is None else items[:max_items] + for item in printed_items: + print(f' {item}') + if max_items is not None and len(items) > max_items: + print(f' ... {len(items) - max_items} more') + + +def format_tuple(values): + if not values: + return 'none' + return ' '.join(values) + + +def main(): + parser = argparse.ArgumentParser( + description='Compare autotools and Meson compile commands by source file.') + parser.add_argument('build_dir', type=Path) + parser.add_argument('autotools_commands', type=Path) + parser.add_argument( + '--max-sources', + type=int, + default=None, + help='limit the number of sources printed for each difference group') + args = parser.parse_args() + + autotools = read_autotools_commands(args.autotools_commands) + meson, meson_all = read_meson_commands(args.build_dir) + + autotools_sources = set(autotools) + meson_sources = set(meson) + matched = sorted(autotools_sources & meson_sources) + only_autotools = sorted(autotools_sources - meson_sources) + only_meson = sorted(meson_sources - autotools_sources) + + print(f'Autotools compile commands: {len(autotools)}') + print(f'Meson default-library compile commands: {len(meson)}') + print(f'Meson compile commands before filtering: {len(meson_all)}') + print(f'Matched sources: {len(matched)}') + print() + + print_list('Only in autotools:', only_autotools, args.max_sources) + print() + print_list('Only in Meson default library:', only_meson, args.max_sources) + print() + + groups = defaultdict(list) + same = 0 + + for source in matched: + if source_is_assembly(source): + same += 1 + continue + + auto = autotools[source]['parts'] + mes = meson[source]['parts'] + diff = ( + counter_diff(auto['flags'], mes['flags']), + counter_diff(auto['defines'], mes['defines']), + counter_diff(auto['includes'], mes['includes']), + ) + + if diff == (((), ()), ((), ()), ((), ())): + same += 1 + else: + groups[diff].append(source) + + print(f'Sources with no normalized argument differences: {same}') + print(f'Difference groups: {len(groups)}') + print() + + for idx, (diff, sources) in enumerate(sorted(groups.items(), key=lambda x: (-len(x[1]), x[1][0])), 1): + (flags_auto, flags_meson), (defs_auto, defs_meson), (incs_auto, incs_meson) = diff + print(f'Group {idx}: {len(sources)} source(s)') + print(f' flags only in autotools: {format_tuple(flags_auto)}') + print(f' flags only in Meson: {format_tuple(flags_meson)}') + print(f' defines only in autotools: {format_tuple(defs_auto)}') + print(f' defines only in Meson: {format_tuple(defs_meson)}') + print(f' includes only in autotools: {format_tuple(incs_auto)}') + print(f' includes only in Meson: {format_tuple(incs_meson)}') + print(' sources:') + printed_sources = sources + if args.max_sources is not None: + printed_sources = sources[:args.max_sources] + for source in printed_sources: + print(f' {source}') + if args.max_sources is not None and len(sources) > args.max_sources: + print(f' ... {len(sources) - args.max_sources} more') + print() + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/config.m4.in b/config.m4.in new file mode 100644 index 0000000000..1c12351c30 --- /dev/null +++ b/config.m4.in @@ -0,0 +1,23 @@ +dnl config.m4. Generated automatically by configure. +changequote(<,>) +ifdef(<__CONFIG_M4_INCLUDED__>,,< +define(, <@TEXT@>) +define(, <@DATA@>) +define(, <@LABEL_SUFFIX@>) +define(, <@GLOBL@>) +define(, <@GLOBL_ATTR@>) +define(, <@GSYM_PREFIX@>) +define(, <@RODATA@>) +define(, <@TYPE@>) +define(, <@SIZE@>) +define(, <@LSYM_PREFIX@>) +define(,<@ALIGN_LOGARITHMIC@>) +define(,<@ALIGN_FILL_0x90@>) +define(,<@HAVE_COFF_TYPE@>) +>) +changequote(`,') +ifdef(`__CONFIG_M4_INCLUDED__',,` +include(@SOURCE_DIR@/src/mpn_extras/asm-defs.m4) +@ASM_M4_INCLUDES@ +') +define(`__CONFIG_M4_INCLUDED__') diff --git a/config/check_assembly.py b/config/check_assembly.py new file mode 100644 index 0000000000..ef129cf96c --- /dev/null +++ b/config/check_assembly.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python3 + +import argparse +import re +import shutil +import subprocess +import sys +import tempfile +from pathlib import Path + + +def run(cmd, *, cwd=None, input_text=None): + return subprocess.run( + cmd, + cwd=cwd, + input=input_text, + text=True, + capture_output=True, + check=False, + ) + + +def compile_asm(compiler, c_args, asm_text): + tmpdir = Path(tempfile.mkdtemp(prefix='flint-asm-')) + asm_file = tmpdir / 'probe.s' + obj_file = tmpdir / 'probe.o' + asm_file.write_text(asm_text) + proc = run(list(compiler) + list(c_args) + ['-c', str(asm_file), '-o', str(obj_file)]) + return proc, obj_file if proc.returncode == 0 else None + + +def compile_c_to_asm(compiler, c_args, c_text): + tmpdir = Path(tempfile.mkdtemp(prefix='flint-c-')) + c_file = tmpdir / 'probe.c' + s_file = tmpdir / 'probe.s' + c_file.write_text(c_text) + proc = run(list(compiler) + list(c_args) + ['-S', str(c_file), '-o', str(s_file)]) + return proc, s_file if proc.returncode == 0 else None + + +def nm_output(obj_file): + nm = shutil.which('nm') + if nm is None: + return None + proc = run([nm, str(obj_file)]) + if proc.returncode != 0: + return None + return proc.stdout + + +def probe_asm_candidate(compiler, c_args, asm_text): + proc, _ = compile_asm(compiler, c_args, asm_text) + return proc.returncode == 0 + + +def detect_text(compiler, c_args): + for candidate in ['.text', '.code', '.csect .text[PR]']: + if probe_asm_candidate(compiler, c_args, f'{candidate}\n'): + return candidate + return '.text' + + +def detect_data(compiler, c_args): + for candidate in ['.data', '.csect .data[RW]']: + if probe_asm_candidate(compiler, c_args, f'{candidate}\n'): + return candidate + return '.data' + + +def detect_label_suffix(compiler, c_args, text_dir): + for candidate in ['', ':']: + asm = f'''{text_dir} +foo{candidate} + .byte 0 +''' + if probe_asm_candidate(compiler, c_args, asm): + return candidate + return ':' + + +def detect_gsym_prefix(compiler, c_args, host_system): + default = '_' if host_system == 'darwin' else '' + with tempfile.TemporaryDirectory() as tmp: + tmpdir = Path(tmp) + c_file = tmpdir / 'probe.c' + obj_file = tmpdir / 'probe.o' + c_file.write_text('int gurkmacka;\n') + proc = run(list(compiler) + list(c_args) + ['-c', str(c_file), '-o', str(obj_file)]) + if proc.returncode != 0: + return default + out = nm_output(obj_file) + if out is None: + return default + for line in out.splitlines(): + if 'gurkmacka' not in line: + continue + symbol = line.split()[-1] + if symbol == '_gurkmacka': + return '_' + if symbol == 'gurkmacka': + return '' + return default + + +def detect_lsym_prefix(compiler, c_args, text_dir, label_suffix, gsym_prefix): + candidates = ['L', '.L', '$', 'L$'] + with tempfile.TemporaryDirectory() as tmp: + tmpdir = Path(tmp) + for candidate in candidates: + asm_file = tmpdir / 'probe.s' + obj_file = tmpdir / 'probe.o' + asm_file.write_text(f'''{text_dir} +dummy{label_suffix} +{candidate}gurkmacka{label_suffix} +''') + proc = run(list(compiler) + list(c_args) + ['-c', str(asm_file), '-o', str(obj_file)]) + if proc.returncode != 0: + continue + out = nm_output(obj_file) + if out is None: + continue + if 'gurkmacka' not in out: + return candidate + for line in out.splitlines(): + if 'gurkmacka' not in line: + continue + fields = line.split() + if len(fields) >= 2 and fields[1] in ['t', 'T', 'n', 'N']: + return candidate + return 'L' if gsym_prefix == '_' else '.L' + + +def detect_rodata(compiler, c_args, host_system, gsym_prefix): + default = ' .section __TEXT,__const' if host_system == 'darwin' else '.section .rodata' + c_source = '''extern const int foo[]; +const int foo[] = {1, 2, 3}; +''' + proc, s_file = compile_c_to_asm(compiler, c_args, c_source) + if proc.returncode != 0 or s_file is None: + return default + + current_section = default + label_re = re.compile(r'^\s*' + re.escape(gsym_prefix) + r'?foo(:|\b)') + for line in s_file.read_text().splitlines(): + stripped = line.strip() + if stripped.startswith(('.section', '.csect', '.data', '.rdata', '.text')): + current_section = stripped + if label_re.search(line): + return current_section + return default + + +def detect_type_size(compiler, c_args, text_dir, globl, label_suffix, host_system): + if host_system == 'darwin': + return '', '' + + type_value = '' + size_value = '' + for marker in ['@', '#', '%']: + asm = f'''{text_dir} +{globl} foo +.type foo,{marker}function +foo{label_suffix} + .byte 0 +''' + proc, _ = compile_asm(compiler, c_args, asm) + if proc.returncode == 0: + type_value = f'.type $1,{marker}$2' + break + + asm = f'''{text_dir} +{globl} foo +foo{label_suffix} + .byte 0 +.size foo,.-foo +''' + proc, _ = compile_asm(compiler, c_args, asm) + if proc.returncode == 0: + size_value = '.size $1,$2' + + return type_value, size_value + + +def detect_align_logarithmic(compiler, c_args, data_dir, globl, label_suffix, gsym_prefix): + asm = f'''{data_dir} + .byte 1 + .align 4 +{globl} foo +foo{label_suffix} + .byte 2 +''' + proc, obj_file = compile_asm(compiler, c_args, asm) + if proc.returncode != 0 or obj_file is None: + return 'yes' + + out = nm_output(obj_file) + if out is None: + return 'yes' + + for line in out.splitlines(): + fields = line.split() + if len(fields) >= 3 and fields[-1] == f'{gsym_prefix}foo': + try: + addr = int(fields[0], 16) + except ValueError: + continue + return 'yes' if addr in (16, 0x10) else 'no' + return 'yes' + + +def detect_align_fill_0x90(compiler, c_args, text_dir): + asm = f'''{text_dir} + .align 4, 0x90 + .byte 0 + .align 4, 0x90 +''' + proc, _ = compile_asm(compiler, c_args, asm) + return 'yes' if proc.returncode == 0 else 'no' + + +def detect_coff_type(compiler, c_args, text_dir, globl, label_suffix, gsym_prefix): + symbol = f'{gsym_prefix}foo' + asm = f'''{text_dir} +{globl} {symbol} +.def {symbol} +.scl 2 +.type 32 +.endef +{symbol}{label_suffix} + .byte 0 +''' + proc, _ = compile_asm(compiler, c_args, asm) + return 'yes' if proc.returncode == 0 else 'no' + + +def main(): + parser = argparse.ArgumentParser(description='Detect assembler syntax support') + parser.add_argument('--host-cpu', required=True) + parser.add_argument('--host-system', required=True) + parser.add_argument('compiler', nargs=argparse.REMAINDER) + args = parser.parse_args() + + compiler = args.compiler + if compiler and compiler[0] == '--': + compiler = compiler[1:] + if not compiler: + print('no compiler command provided', file=sys.stderr) + return 1 + + c_args = [] + text_dir = detect_text(compiler, c_args) + data_dir = detect_data(compiler, c_args) + label_suffix = detect_label_suffix(compiler, c_args, text_dir) + globl = '.globl' + globl_attr = '' + gsym_prefix = detect_gsym_prefix(compiler, c_args, args.host_system) + rodata = detect_rodata(compiler, c_args, args.host_system, gsym_prefix) + type_value, size_value = detect_type_size( + compiler, c_args, text_dir, globl, label_suffix, args.host_system) + lsym_prefix = detect_lsym_prefix(compiler, c_args, text_dir, label_suffix, gsym_prefix) + align_logarithmic = detect_align_logarithmic(compiler, c_args, data_dir, globl, label_suffix, gsym_prefix) + align_fill_0x90 = 'no' + have_coff_type = 'no' + if args.host_cpu == 'x86_64': + align_fill_0x90 = detect_align_fill_0x90(compiler, c_args, text_dir) + have_coff_type = detect_coff_type(compiler, c_args, text_dir, globl, label_suffix, gsym_prefix) + + values = { + 'TEXT': text_dir, + 'DATA': data_dir, + 'LABEL_SUFFIX': label_suffix, + 'GLOBL': globl, + 'GLOBL_ATTR': globl_attr, + 'GSYM_PREFIX': gsym_prefix, + 'RODATA': rodata, + 'TYPE': type_value, + 'SIZE': size_value, + 'LSYM_PREFIX': lsym_prefix, + 'ALIGN_LOGARITHMIC': align_logarithmic, + 'ALIGN_FILL_0x90': align_fill_0x90, + 'HAVE_COFF_TYPE': have_coff_type, + } + + for key, value in values.items(): + print(f'{key}={value}') + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/config/generate_msvc_def.py b/config/generate_msvc_def.py new file mode 100644 index 0000000000..377d32aa21 --- /dev/null +++ b/config/generate_msvc_def.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +import argparse +from pathlib import Path +import re +import subprocess + + +DUMPBIN_SYMBOL_RE = re.compile(r"\bExternal\s+\|\s+(\S+)$") +SKIP_PREFIXES = ( + "__imp_", + "__real@", + "__xmm@", + "__ymm@", + "__local_stdio_", +) +SKIP_SYMBOLS = { + "main", +} + + +def object_symbols(obj): + proc = subprocess.run( + ["dumpbin", "/symbols", obj], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + encoding="utf-8", + errors="replace", + ) + + for line in proc.stdout.splitlines(): + if " UNDEF " in line: + continue + + match = DUMPBIN_SYMBOL_RE.search(line) + if not match: + continue + + symbol = match.group(1) + if ( + symbol in SKIP_SYMBOLS + or symbol.startswith(SKIP_PREFIXES) + or symbol.startswith("$") + or symbol.startswith(".") + or "@" in symbol + or "?" in symbol + ): + continue + + yield symbol + + +def main(): + parser = argparse.ArgumentParser( + description="Generate a MSVC module definition file from object files." + ) + parser.add_argument("output") + parser.add_argument("objects", nargs="*") + parser.add_argument("--object-dir") + args = parser.parse_args() + + objects = list(args.objects) + if args.object_dir: + objects.extend(str(obj) for obj in Path(args.object_dir).rglob("*.obj")) + + if not objects: + raise SystemExit("no object files found") + + symbols = set() + for obj in objects: + symbols.update(object_symbols(obj)) + + with open(args.output, "w", encoding="utf-8", newline="\n") as output: + output.write("EXPORTS\n") + for symbol in sorted(symbols): + output.write(f" {symbol}\n") + + +if __name__ == "__main__": + main() diff --git a/config/meson.build b/config/meson.build new file mode 100644 index 0000000000..6e45b88949 --- /dev/null +++ b/config/meson.build @@ -0,0 +1,1404 @@ +# ----------------------------------------------------------------------------- +# FLINT configuration +# ----------------------------------------------------------------------------- + +# Data set on cfg_data is used to generate flint-config.h +# Data set on cfg_data_internal (below) is also used to generate config.h +cfg_data = configuration_data() + +# ----------------------------------------------------------------------------- +# Project options +# ----------------------------------------------------------------------------- + +pthread_opt = get_option('pthread') +reentrant_opt = get_option('reentrant') +thread_safe_opt = get_option('thread_safe') +abi_opt = get_option('abi') +assert_opt = get_option('assert') +pretty_tests_opt = get_option('pretty_tests') +gmp_internals_opt = get_option('gmp_internals') +mpfr_check_opt = get_option('mpfr_check') +assembly_opt = get_option('assembly') +avx2_opt = get_option('avx2') +avx512_opt = get_option('avx512') +fft_small_opt = get_option('fft_small') +blas_opt = get_option('blas') +gc_opt = get_option('gc') +ntl_opt = get_option('ntl') +coverage_opt = get_option('b_coverage') +python = import('python').find_installation() + +flint_abi_c_args = [] +if abi_opt != 'auto' + abi_flag = '-m' + abi_opt + if not cc.has_argument(abi_flag) + error(meson.get_compiler('c').get_id() + ' does not support ' + abi_flag) + endif + flint_abi_c_args += abi_flag +endif + +# ----------------------------------------------------------------------------- +# libm +# ----------------------------------------------------------------------------- + +if not cc.has_function('atan2', dependencies: m_dep) + error('libm is required') +endif + +# ----------------------------------------------------------------------------- +# POSIX threads +# ----------------------------------------------------------------------------- + +pthread_dep = declare_dependency() +have_pthread = false + +if pthread_opt.enabled() + pthread_candidates = [] + + if host_machine.system() == 'windows' + pthread_candidates += [ + dependency('unofficial-pthreads', required: false, method: 'cmake', modules: ['unofficial::pthreads::pthread']), + dependency('PThreads4W', required: false, method: 'cmake', modules: ['PThreads4W::PThreads4W']), + dependency('PThreads4W', required: false, method: 'cmake', modules: ['PThreads4W::pthread']), + dependency('PThreads4W', required: false, method: 'cmake', modules: ['PThreads4W::pthreadVC3']), + dependency('pthreads4w', required: false), + ] + endif + + pthread_candidates += [ + dependency('pthread', required: false), + dependency('pthreads', required: false), + dependency('threads', required: false), + ] + + foreach dep : pthread_candidates + if dep.found() and cc.has_header('pthread.h', dependencies: dep) and cc.has_function('pthread_create', dependencies: dep) + pthread_dep = dep + have_pthread = true + break + endif + endforeach + + if not have_pthread + foreach libname : ['pthread', 'pthreadVC3', 'pthreadVCE3', 'pthreadVSE3'] + libpthread_dep = cc.find_library(libname, required: false) + if libpthread_dep.found() and cc.has_header('pthread.h', dependencies: libpthread_dep) and cc.has_function('pthread_create', dependencies: libpthread_dep) + pthread_dep = libpthread_dep + have_pthread = true + break + endif + endforeach + endif + + if not have_pthread + error('POSIX threads was not found.') + endif +endif + +# ----------------------------------------------------------------------------- +# BLAS +# ----------------------------------------------------------------------------- + +blas_dep = declare_dependency() +have_blas = false + +if not blas_opt.disabled() + blas_candidates = [ + dependency('cblas', required: false), + dependency('openblas', required: false), + ] + + foreach dep : blas_candidates + if dep.found() + dep_has_cblas_h = cc.has_header('cblas.h', dependencies: dep) + dep_has_cblas_dgemm = dep_has_cblas_h and cc.has_function( + 'cblas_dgemm', + prefix: '#include ', + dependencies: dep) + if dep_has_cblas_dgemm + blas_dep = dep + have_blas = true + break + endif + endif + endforeach + + if not have_blas + foreach libname : ['cblas', 'openblas', 'blas'] + libdep = cc.find_library(libname, required: false) + if libdep.found() + lib_has_cblas_h = cc.has_header('cblas.h') + lib_has_cblas_dgemm = lib_has_cblas_h and cc.has_function( + 'cblas_dgemm', + prefix: '#include ', + dependencies: libdep) + if lib_has_cblas_dgemm + blas_dep = libdep + have_blas = true + break + endif + endif + endforeach + endif + + if blas_opt.enabled() and not have_blas + if not cc.has_header('cblas.h') + error('Could not find cblas.h') + else + error('BLAS library was not found.') + endif + endif +endif + +# ----------------------------------------------------------------------------- +# NTL +# ----------------------------------------------------------------------------- + +ntl_dep = declare_dependency() +have_ntl = false + +if not ntl_opt.disabled() + have_cpp = add_languages('cpp', native: false, required: false) + + if have_cpp + cpp = meson.get_compiler('cpp') + add_project_arguments(flint_abi_c_args, language: 'cpp') + add_project_link_arguments(flint_abi_c_args, language: 'cpp') + + ntl_candidates = [ + dependency('ntl', required: false, static: true), + dependency('ntl', required: false), + ] + + foreach dep : ntl_candidates + if dep.found() + dep_has_ntl_h = cpp.has_header('NTL/ZZ.h', dependencies: dep) + dep_links_ntl = dep_has_ntl_h and cpp.links( + ''' + #include + #include + + int main() { + NTL::ZZ a, b, c; + std::cin >> a; + std::cin >> b; + c = (a + 1) * (b + 1); + std::cout << c << "\\n"; + return 0; + } + ''', + dependencies: dep, + name: 'if linking with NTL works') + if dep_links_ntl + ntl_dep = dep + have_ntl = true + break + endif + endif + endforeach + + if not have_ntl + libntl_dep = cpp.find_library('ntl', required: false) + if libntl_dep.found() and cpp.has_header('NTL/ZZ.h') + libntl_full_dep = declare_dependency( + dependencies: [libntl_dep, gmp_link_dep], + ) + lib_links_ntl = cpp.links( + ''' + #include + #include + + int main() { + NTL::ZZ a, b, c; + std::cin >> a; + std::cin >> b; + c = (a + 1) * (b + 1); + std::cout << c << "\\n"; + return 0; + } + ''', + dependencies: libntl_full_dep, + name: 'if linking with NTL works') + if lib_links_ntl + ntl_dep = libntl_full_dep + have_ntl = true + endif + endif + endif + + if ntl_opt.enabled() and not have_ntl + if not cpp.has_header('NTL/ZZ.h') + error('Could not find NTL/ZZ.h') + else + error('NTL library was not found.') + endif + endif + elif ntl_opt.enabled() + error('A C++ compiler is required for NTL support.') + endif +endif + +# ----------------------------------------------------------------------------- +# Boehm GC +# ----------------------------------------------------------------------------- + +gc_dep = declare_dependency() +have_gc = false + +if not gc_opt.disabled() + gc_candidates = [ + dependency('bdw-gc', required: false), + dependency('gc', required: false), + ] + + foreach dep : gc_candidates + if dep.found() + dep_has_gc_h = cc.has_header('gc.h', dependencies: dep) + if dep_has_gc_h + gc_dep = dep + have_gc = true + break + endif + endif + endforeach + + if not have_gc + libgc_dep = cc.find_library('gc', required: false) + if libgc_dep.found() and cc.has_header('gc.h') + gc_dep = libgc_dep + have_gc = true + endif + endif + + if gc_opt.enabled() and not have_gc + if not cc.has_header('gc.h') + error('Could not find gc.h') + else + error('GC library was not found.') + endif + endif +endif + +# ----------------------------------------------------------------------------- +# fmpz memory management +# ----------------------------------------------------------------------------- + +if gc_opt.enabled() + if thread_safe_opt.enabled() + error('GC does not support TLS.') + else + fmpz_c_in = 'fmpz_gc.c' + endif +else + if reentrant_opt.enabled() + fmpz_c_in = 'fmpz_reentrant.c' + else + fmpz_c_in = 'fmpz_single.c' + endif +endif + +# ----------------------------------------------------------------------------- +# GMP +# ----------------------------------------------------------------------------- + +if not cc.has_header('gmp.h', dependencies: gmp_dep) + error('Could not find gmp.h') +endif + +if not cc.has_header('mpfr.h', dependencies: mpfr_dep) + error('Could not find mpfr.h') +endif + +gmp_long_long_limb = cc.compiles(''' +#include +#if !defined(_LONG_LONG_LIMB) +# error mp_limb_t != unsigned long long int +#endif +''', dependencies: gmp_dep) + +if gmp_long_long_limb + cfg_data.set('FLINT_LONG_LONG', 1, + description: 'Define to use long long limbs') +endif + +if abi_opt != 'auto' + flint_bits = abi_opt == '64' ? 64 : 32 +else +gmp_64_bit_limb = cc.compiles(''' +#include +#if GMP_LIMB_BITS != 64 +# error GMP_LIMB_BITS != 64 +#endif +''', args: flint_abi_c_args, dependencies: gmp_dep, name: 'GMP_LIMB_BITS is 64') + +flint_bits = gmp_64_bit_limb ? 64 : 32 +endif + +cfg_data.set('FLINT_BITS', flint_bits, + description: 'Define according to the ABI FLINT was compiled with') + +# We need to check with cc.links rather than cc.has_function etc because we +# are checking if we can link symbols not in the headers. + +gmp_test_code = ''' +#include + +@0@ + +int main() { + @1@ + return 0; +} +''' + +gmp_abi_error_suffix = abi_opt == 'auto' ? '' : ' for ABI ' + abi_opt + ' (check that GMP/MPFR are built for this ABI)' + +gmp_base_tests = { + 'mpz_init': ['', 'mpz_t x; __gmpz_init(x);'], + 'mpn_mul_basecase': [ + 'void __gmpn_mul_basecase(mp_ptr, mp_srcptr, mp_size_t, mp_srcptr, mp_size_t);', + 'mp_limb_t a[2], b[2], p[4]; __gmpn_mul_basecase(p, a, (mp_size_t) 1, b, (mp_size_t) 1);', + ], +} + +foreach name, snippets : gmp_base_tests + _code = gmp_test_code.format(snippets[0], snippets[1]) + if not cc.links(_code, args: flint_abi_c_args, dependencies: gmp_dep, name: name + ' links') + message('This code did not link with -lgmp: ', _code) + error('GMP does not have ' + name + gmp_abi_error_suffix) + endif +endforeach + +if gmp_internals_opt.allowed() + + gmp_internals_required = { + 'mpn_gcd_11': ['', 'mp_limb_t res, a=0, b=0; res = __gmpn_gcd_11(a, b);'], + 'mpn_div_q': [ + 'void __gmpn_div_q(mp_ptr, mp_srcptr, mp_size_t, mp_srcptr, mp_size_t, mp_ptr);', + 'mp_limb_t q[2], n[2], d[2], t[2]; __gmpn_div_q(q, t, (mp_size_t) 1, n, (mp_size_t) 1, d);' + ], + } + + foreach name, snippets : gmp_internals_required + _code = gmp_test_code.format(snippets[0], snippets[1]) + if not cc.links(_code, args: flint_abi_c_args, dependencies: gmp_dep, name: name + ' links') + message('This code did not link with -lgmp: ', _code) + # Match autotools: GMP internals are enabled by default and these symbols + # are required unless -Dgmp_internals=disabled is used. + error('GMP does not have ' + name + gmp_abi_error_suffix) + endif + endforeach + + gmp_internals_optional = { + 'mpn_add_n_sub_n': [ + 'void __gmpn_add_n_sub_n(mp_ptr, mp_ptr, mp_ptr, mp_size_t);', + 'mp_limb_t a[2], b[2], c[2], d[2]; __gmpn_add_n_sub_n(a, b, c, (mp_size_t) 1);', + ], + 'mpn_add_nc': [ + 'mp_limb_t __gmpn_add_nc(mp_ptr, mp_srcptr, mp_size_t, mp_limb_t);', + 'mp_limb_t a[2], b[2]; __gmpn_add_nc(a, b, (mp_size_t) 1, (mp_limb_t) 0);', + ], + 'mpn_addlsh1_n': [ + 'mp_limb_t __gmpn_addlsh1_n(mp_ptr, mp_srcptr, mp_size_t, mp_limb_t);', + 'mp_limb_t a[2], b[2]; __gmpn_addlsh1_n(a, b, (mp_size_t) 1, (mp_limb_t) 0);', + ], + 'mpn_addlsh1_n_ip1': [ + 'mp_limb_t __gmpn_addlsh1_n_ip1(mp_ptr, mp_srcptr, mp_size_t);', + 'mp_limb_t a[2], b[2]; __gmpn_addlsh1_n_ip1(a, b, (mp_size_t) 1);', + ], + 'mpn_addmul_2': [ + 'void __gmpn_addmul_2(mp_ptr, mp_srcptr, mp_size_t, mp_limb_t);', + 'mp_limb_t a[2], b[2]; __gmpn_addmul_2(a, b, (mp_size_t) 1, (mp_limb_t) 0);', + ], + 'mpn_modexact_1_odd': [ + 'mp_limb_t __gmpn_modexact_1_odd(mp_srcptr, mp_size_t, mp_limb_t);', + 'mp_limb_t a[2]; __gmpn_modexact_1_odd(a, (mp_size_t) 1, (mp_limb_t) 0);', + ], + 'mpn_rsh1add_n': [ + 'mp_limb_t __gmpn_rsh1add_n(mp_ptr, mp_srcptr, mp_size_t, mp_limb_t);', + 'mp_limb_t a[2], b[2]; __gmpn_rsh1add_n(a, b, (mp_size_t) 1, (mp_limb_t) 0);', + ], + 'mpn_rsh1sub_n': [ + 'mp_limb_t __gmpn_rsh1sub_n(mp_ptr, mp_srcptr, mp_size_t, mp_limb_t);', + 'mp_limb_t a[2], b[2]; __gmpn_rsh1sub_n(a, b, (mp_size_t) 1, (mp_limb_t) 0);', + ], + 'mpn_sub_nc': [ + 'mp_limb_t __gmpn_sub_nc(mp_ptr, mp_srcptr, mp_size_t, mp_limb_t);', + 'mp_limb_t a[2], b[2]; __gmpn_sub_nc(a, b, (mp_size_t) 1, (mp_limb_t) 0);', + ], + } + + foreach name, snippets : gmp_internals_optional + _code = gmp_test_code.format(snippets[0], snippets[1]) + if cc.links(_code, args: flint_abi_c_args, dependencies: gmp_dep, name: name + ' links') + cfg_data.set('FLINT_HAVE_NATIVE_' + name, 1, + description: 'Define if GMP has ' + name) + endif + endforeach + +endif + +# Match autotools --enable-mpfr-check, which is enabled by default but can be +# disabled for environments where MPFR hides these internal symbols. +if not mpfr_check_opt.disabled() + mpfr_test_code = ''' +#include + +@0@ + +int main(void) { + @1@ + return 0; +} +''' + + mpfr_internal_tests = { + 'mpfr_round_p': [ + 'int mpfr_round_p(mp_srcptr, mp_size_t, mpfr_exp_t, mpfr_prec_t);', + 'mp_limb_t a[1] = { 0 }; return mpfr_round_p(a, (mp_size_t) 1, (mpfr_exp_t) 0, (mpfr_prec_t) 1);', + ], + 'mpfr_mulhigh_n': [ + 'void mpfr_mulhigh_n(mp_ptr, mp_srcptr, mp_srcptr, mp_size_t);', + 'mp_limb_t r[1], a[1] = { 0 }, b[1] = { 0 }; mpfr_mulhigh_n(r, a, b, (mp_size_t) 1);', + ], + 'mpfr_sqrhigh_n': [ + 'void mpfr_sqrhigh_n(mp_ptr, mp_srcptr, mp_size_t);', + 'mp_limb_t r[1], a[1] = { 0 }; mpfr_sqrhigh_n(r, a, (mp_size_t) 1);', + ], + } + + foreach name, snippets : mpfr_internal_tests + _code = mpfr_test_code.format(snippets[0], snippets[1]) + if not cc.links(_code, args: flint_abi_c_args, dependencies: [mpfr_dep, gmp_link_dep], name: name + ' links') + message('This code did not link with -lmpfr: ', _code) + error(name + ' was not found in MPFR. Use -Dmpfr_check=disabled to skip this check.') + endif + endforeach +endif + +# ----------------------------------------------------------------------------- +# CPU detection +# ----------------------------------------------------------------------------- + +# We check here for gcc or clang because the config.guess script probably does +# not work for other compilers. This check will also allow MinGW and cygwin on +# Windows but not MSVC. + +# The config.guess script inspects the current CPU so should not be used for a +# cross build. Maybe there should be a way to configure the exact CPU as a +# project build option. + +if not meson.is_cross_build() and cc.get_id() in ['gcc', 'clang'] + config_guess_result = run_command('./config.guess', check: true) + target_triple = config_guess_result.stdout().strip() + exact_cpu = target_triple.split('-')[0] +else + # This could be 'aarch64' or 'x86_64' + # https://mesonbuild.com/Reference-tables.html#cpu-families + exact_cpu = host_machine.cpu_family() +endif + +message('EXACT CPU:', exact_cpu) + +################################################################################ +# architecture specifics +################################################################################ + +gcc_cflags = '-O3 -pedantic -std=c11' +gcc_warnings = '-pedantic -Werror=implicit-function-declaration -Werror=newline-eof -Wno-stringop-overread -Wno-stringop-overflow -Wno-deprecated-declarations' +gcc_library_warnings = '-Wmissing-prototypes' +gcc_cflags_optlist = '' +gcc_cflags_arch = '' +gcc_cflags_tune = '' +flint_warning_c_args = cc.get_supported_arguments(gcc_warnings.split()) +flint_library_warning_c_args = cc.get_supported_arguments(gcc_library_warnings.split()) +flint_unroll_loops_c_args = cc.get_supported_arguments('-funroll-loops') +flint_msvc_c_args = [] +if cc.get_argument_syntax() == 'msvc' + flint_msvc_c_args += cc.get_supported_arguments('/wd4146') + if pthread_opt.enabled() + flint_msvc_c_args += cc.get_supported_arguments('/experimental:c11atomics') + endif +endif +flint_cpu_c_args = [] + +# We only try to provide specifics for those systems that currently supports +# our assembly routines. If more combinations are wished for than what is +# specified, please open up an issue at +# and we will consider it. +# +# For these systems we aim to provide: +# +# gcc_cflags flags for GCC-compatible compilers +# +# asm_path directory for assembly, relative to src/mpn_extras +# param_path directory for flint-mparam.h, relative to src/mpn_extras +# +# For x86_64 systems, we can also set: +# +# have_avx512 system has AVX512F and AVX512DQ (we assume this implies have_avx2) +# have_avx2 system has AVX2 and FMA + +# x86_64 CPUs that have AVX2: +X86_64_ADX_CPU_NAMES = [ + 'zen1', + 'zen2', + 'zen3', + 'zen4', + 'zen5', + 'coreibwl', + 'broadwell', + 'skylake', + 'skylake_server', + 'cannonlake', + 'kabylake', + 'icelake', + 'icelake_server', + 'rocketlake', + 'tigerlake', + 'alderlake', + 'raptorlake', + 'meteorlake', + 'knightslanding', + 'sapphirerapids', + 'cometlake', +] + +if host_machine.cpu_family() == 'aarch64' + + gcc_cflags = gcc_cflags + gcc_cflags_optlist = 'arch tune' + asm_path = 'arm64' + param_path = 'arm64' + flint_cv_have_fft_small_arm_i = 'yes' + flint_know_strong_order = 'no' + + # NOTE: Cortex values where taken from + # https://developer.arm.com/Processors/Cortex-XXX + + if exact_cpu in ['armcortexa35', 'armcortexa35neon'] + gcc_cflags_arch = '-march=armv8-a' + gcc_cflags_tune = '-mtune=cortex-a35' + + elif exact_cpu in ['armcortexa53', 'armcortexa53neon'] + gcc_cflags_arch = '-march=armv8-a' + gcc_cflags_tune = '-mtune=cortex-a53' + + elif exact_cpu in ['armcortexa55', 'armcortexa55neon'] + gcc_cflags_arch = '-march=armv8.2-a' + gcc_cflags_tune = '-mtune=cortex-a55' + + elif exact_cpu in ['armcortexa57', 'armcortexa57neon'] + gcc_cflags_arch = '-march=armv8-a' + gcc_cflags_tune = '-mtune=cortex-a57' + + elif exact_cpu in ['armcortexa72', 'armcortexa72neon'] + gcc_cflags_arch = '-march=armv8-a' + gcc_cflags_tune = '-mtune=cortex-a72' + + elif exact_cpu in ['armcortexa73', 'armcortexa73neon'] + gcc_cflags_arch = '-march=armv8-a' + gcc_cflags_tune = '-mtune=cortex-a73' + + elif exact_cpu in ['armcortexa75', 'armcortexa75neon'] + gcc_cflags_arch = '-march=armv8.2-a' + gcc_cflags_tune = '-mtune=cortex-a75' + + elif exact_cpu in ['armcortexa76', 'armcortexa76neon'] + gcc_cflags_arch = '-march=armv8.2-a' + gcc_cflags_tune = '-mtune=cortex-a76' + + elif exact_cpu in ['armcortexa77', 'armcortexa77neon'] + gcc_cflags_arch = '-march=armv8.2-a' + gcc_cflags_tune = '-mtune=cortex-a77' + + elif exact_cpu in ['armcortexa65', 'armcortexa65neon'] + gcc_cflags_arch = '-march=armv8.2-a' + gcc_cflags_tune = '-mtune=cortex-a65' + + elif exact_cpu in ['armcortexa34', 'armcortexa34neon'] + gcc_cflags_arch = '-march=armv8-a' + gcc_cflags_tune = '-mtune=cortex-a34' + + elif exact_cpu in ['armcortexa78', 'armcortexa78neon'] + gcc_cflags_arch = '-march=armv8.2-a' + gcc_cflags_tune = '-mtune=cortex-a78' + + elif exact_cpu in ['armexynosm1'] + gcc_cflags_arch = '-march=armv8-a' + gcc_cflags_tune = '-mtune=exynos-m1' + + elif exact_cpu in ['armthunderx'] + gcc_cflags_arch = '-march=armv8-a' + gcc_cflags_tune = '-mtune=thunderx' + + elif exact_cpu in ['armxgene1'] + gcc_cflags_arch = '-march=armv8-a' + gcc_cflags_tune = '-mtune=xgene1' + + elif exact_cpu.startswith('applem1') + gcc_cflags_arch = '-march=armv8.5-a' + gcc_cflags_tune = '' + + elif exact_cpu.startswith('applem2') + gcc_cflags_arch = '-march=armv8.6-a' + gcc_cflags_tune = '' + + elif exact_cpu.startswith('applem3') + gcc_cflags_arch = '-march=armv8.6-a' + gcc_cflags_tune = '' + + elif exact_cpu.startswith('applem4') + gcc_cflags_arch = '-march=armv8.7-a' + gcc_cflags_tune = '' + + elif exact_cpu.startswith('aarch64') or exact_cpu.startswith('armv8') + gcc_cflags_arch = '-march=armv8-a' + gcc_cflags_tune = '' + + else + error_msg = ''' + Error in deciding flags for @0@. + Please report at + ''' + error(error_msg.format(exact_cpu)) + + endif + +elif host_machine.cpu_family() == 'x86_64' and exact_cpu in X86_64_ADX_CPU_NAMES + + gcc_cflags = gcc_cflags + gcc_cflags_optlist = 'arch' + asm_path = 'x86_64/broadwell' + param_path = 'x86_64/broadwell' + + have_avx512 = 'no' + have_avx2 = 'yes' + flint_cv_have_fft_small_x86_i = 'yes' + flint_know_strong_order = 'yes' + + if exact_cpu == 'zen' + gcc_cflags_arch = '-march=znver1' + param_path = 'x86_64/zen3' + + elif exact_cpu == 'zen2' + gcc_cflags_arch = '-march=znver2' + param_path = 'x86_64/zen3' + + elif exact_cpu == 'zen3' + gcc_cflags_arch = '-march=znver3' + param_path = 'x86_64/zen3' + + elif exact_cpu == 'zen4' + gcc_cflags_arch = '-march=znver4' + param_path = 'x86_64/zen4' + have_avx512 = 'yes' + + elif exact_cpu == 'zen5' + gcc_cflags_arch = '-march=znver5 -march=znver4' + param_path = 'x86_64/zen4' + have_avx512 = 'yes' + + elif exact_cpu in ['coreibwl', 'broadwell'] + gcc_cflags_arch = '-march=broadwell' + param_path = 'x86_64/broadwell' + + elif exact_cpu == 'skylake' + gcc_cflags_arch = '-march=skylake' + param_path = 'x86_64/skylake' + + elif exact_cpu == 'skylake_server' + gcc_cflags_arch = '-march=skylake-avx512' + param_path = 'x86_64/skylake' + have_avx512 = 'yes' + + elif exact_cpu == 'cannonlake' + gcc_cflags_arch = '-march=cannonlake' + param_path = 'x86_64/icelake' + have_avx512 = 'yes' + + elif exact_cpu == 'kabylake' + gcc_cflags_arch = '-march=skylake' + param_path = 'x86_64/skylake' + + elif exact_cpu == 'icelake' + gcc_cflags_arch = '-march=icelake-client' + param_path = 'x86_64/icelake' + have_avx512 = 'yes' + + elif exact_cpu == 'icelake_server' + gcc_cflags_arch = '-march=icelake-server' + param_path = 'x86_64/icelake' + have_avx512 = 'yes' + + elif exact_cpu == 'rocketlake' + gcc_cflags_arch = '-march=rocketlake' + param_path = 'x86_64/icelake' + have_avx512 = 'yes' + + elif exact_cpu == 'tigerlake' + gcc_cflags_arch = '-march=tigerlake' + param_path = 'x86_64/tigerlake' + have_avx512 = 'yes' + + elif exact_cpu == 'alderlake' + gcc_cflags_arch = '-march=alderlake' + param_path = 'x86_64/icelake' + + elif exact_cpu == 'raptorlake' + gcc_cflags_arch = '-march=raptorlake -march=alderlake' + param_path = 'x86_64/raptorlake' + + elif exact_cpu == 'meteorlake' + gcc_cflags_arch = '-march=meteorlake -march=alderlake' + param_path = 'x86_64/meteorlake' + + elif exact_cpu == 'knightslanding' + gcc_cflags_arch = '-march=knl' + param_path = 'x86_64/skylake' + + elif exact_cpu == 'sapphirerapids' + gcc_cflags_arch = '-march=sapphirerapids' + param_path = 'x86_64/icelake' + have_avx512 = 'yes' + + elif exact_cpu == 'cometlake' + gcc_cflags_arch = '-march=kabylake' + param_path = 'x86_64/skylake' + + else + error_msg = ''' + Error in deciding flags for @0@. + Please report at + ''' + error(error_msg.format(exact_cpu)) + + endif + + # Disable assembly on Windows + if host_machine.system() == 'windows' + flint_nonstd_abi = 'yes' + asm_path = '' + param_path = 'x86_64' + endif + +elif host_machine.cpu_family() == 'x86_64' + + # NOTE: We do not care if the user specifies noavx! + gcc_cflags = gcc_cflags + gcc_cflags_optlist = 'arch' + asm_path = '' + param_path = 'x86_64' + + have_avx512 = 'no' + have_avx2 = 'no' + flint_know_strong_order = 'yes' + + if exact_cpu in ['coreiibr', 'ivybridge'] + gcc_cflags_arch = '-march=ivybridge' + + elif exact_cpu in ['coreihwl', 'haswell'] + gcc_cflags_arch = '-march=haswell' + + elif exact_cpu in ['piledriver', 'bd2'] + gcc_cflags_arch = '-march=bdver2' + + elif exact_cpu in ['steamroller', 'bd3'] + gcc_cflags_arch = '-march=bdver3' + + elif exact_cpu in ['excavator', 'bd4'] + gcc_cflags_arch = '-march=bdver4' + have_avx2 = 'yes' + + elif exact_cpu in ['x86_64v3'] + gcc_cflags_arch = '-march=x86-64-v3' + have_avx2 = 'yes' + + elif exact_cpu in ['x86_64v4'] + gcc_cflags_arch = '-march=x86-64-v4' + have_avx512 = 'yes' + + else + flint_know_strong_order = 'no' + + endif + + if have_avx512 == 'yes' + have_avx2 = 'yes' + endif + + if have_avx2 == 'yes' + flint_cv_have_fft_small_x86_i = 'yes' + else + flint_cv_have_fft_small_x86_i = 'no' + endif + +else + # Generic case for unrecognised CPU + asm_path = '' + param_path = 'generic' + gcc_cflags = gcc_cflags + + have_avx512 = 'no' + have_avx2 = 'no' + flint_cv_have_fft_small_x86_i = 'no' + flint_know_strong_order = 'no' + +endif + +# Probe and collect CPU-specific compiler flags. For architecture flags, the +# autotools build treats a whitespace-separated list as ordered fallbacks. +foreach opt : gcc_cflags_optlist.split() + candidate_flags = get_variable('gcc_cflags_' + opt, '').split() + found_single = false + + foreach flag : candidate_flags + if found_single + break + endif + + if cc.has_argument(flag) + flint_cpu_c_args += flag + if opt == 'arch' + found_single = true + endif + elif opt == 'arch' + flint_cv_have_fft_small_x86_i = 'no' + flint_cv_have_fft_small_arm_i = 'no' + endif + endforeach +endforeach + +if avx2_opt.enabled() + if host_machine.cpu_family() != 'x86_64' + error('AVX2 is only supported for x86_64 builds') + endif + if not cc.has_argument('-mavx2') + error(meson.get_compiler('c').get_id() + ' does not support -mavx2') + endif + flint_cpu_c_args += '-mavx2' + if cc.has_argument('-mfma') + flint_cpu_c_args += '-mfma' + endif + flint_cv_have_fft_small_x86_i = 'yes' +endif + +if avx512_opt.enabled() + if host_machine.cpu_family() != 'x86_64' + error('AVX512 is only supported for x86_64 builds') + endif + if not cc.has_argument('-mavx512f') + error(meson.get_compiler('c').get_id() + ' does not support -mavx512f') + endif + flint_cpu_c_args += '-mavx512f' +endif + +message('CPU-specific C arguments:', ' '.join(flint_cpu_c_args)) + +# ----------------------------------------------------------------------------- +# mparam +# ----------------------------------------------------------------------------- + +flint_mparam = param_path + +# ----------------------------------------------------------------------------- +# fft_small +# ----------------------------------------------------------------------------- + +fft_small_opt = get_option('fft_small') + +fft_small_arm_code = ''' +#include +#if !(defined(__GNUC__) && defined(__ARM_NEON)) +# if !(defined(_MSC_VER) && defined(_M_ARM64)) +# error +error +# endif +#endif +int main(){return 0;} +''' + +fft_small_x86_code = ''' +#if defined(__GNUC__) +# include +#elif defined(_MSC_VER) +# include +#else +# error +error +#endif + +#if !defined(__AVX2__) +# error +error +#endif +int main(){return 0;} +''' + +if host_machine.cpu_family() == 'aarch64' + fft_small_supported = ( + flint_cv_have_fft_small_arm_i == 'yes' + and cc.compiles(fft_small_arm_code, + args: flint_cpu_c_args, + dependencies: gmp_dep) + ) +else + fft_small_supported = ( + flint_cv_have_fft_small_x86_i == 'yes' + and cc.compiles(fft_small_x86_code, + args: flint_cpu_c_args, + dependencies: gmp_dep) + ) +endif + +# Error if fft_small enabled but not possible, otherwise disable if auto +fft_small_opt = fft_small_opt.require(fft_small_supported, + error_message: 'host CPU does not support AVX/NEON. Set fft_small to disabled or auto') + +# If fft_small was auto we enable it if possible +fft_small_opt = fft_small_opt.enable_auto_if(fft_small_supported) +have_fft_small = fft_small_opt.enabled() + +message('FFT_SMALL: ', have_fft_small ? 'enabled' : 'disabled') + +# ----------------------------------------------------------------------------- +# Assembly +# ----------------------------------------------------------------------------- + +asm_text = '.text' +asm_data = '.data' +asm_label_suffix = ':' +asm_globl = '.globl' +asm_globl_attr = '' +asm_gsym_prefix = '' +asm_rodata = '.section .rodata' +asm_type = '' +asm_size = '' +asm_lsym_prefix = 'L' +asm_align_logarithmic = 'yes' +asm_align_fill_0x90 = 'no' +asm_have_coff_type = 'no' + +if asm_path != '' and flint_bits == 64 + assembly_cpu_supported = true + assembly_dir = 'mpn_extras/' + asm_path + if host_machine.cpu_family() == 'aarch64' + FLINT_HAVE_ASSEMBLY = 'armv8' + elif host_machine.cpu_family() == 'x86_64' + FLINT_HAVE_ASSEMBLY = 'x86_64_adx' + else + error('Assembly only supported for x86_64 or aarch64') + endif +else + assembly_cpu_supported = false + FLINT_HAVE_ASSEMBLY = '' +endif + +assembly_opt = assembly_opt.require(host_machine.system() != 'windows', + error_message: 'No assembly support for Windows') + +assembly_opt = assembly_opt.require(assembly_cpu_supported, + error_message: 'No assembly support for host CPU') + +if host_machine.cpu_family() == 'aarch64' or host_machine.cpu_family() == 'x86_64' + asm_detect = run_command( + python, + files('check_assembly.py'), + [ + '--host-cpu', host_machine.cpu_family(), + '--host-system', host_machine.system(), + '--', + ] + cc.cmd_array() + flint_abi_c_args + flint_cpu_c_args, + check: true, + ) + + asm_detect_lines = asm_detect.stdout().strip().split('\n') + foreach line : asm_detect_lines + if line.contains('=') + asm_pair = line.split('=') + asm_key = asm_pair[0] + asm_value = '' + if asm_pair.length() > 1 + asm_value = asm_pair[1] + endif + if asm_key == 'TEXT' + asm_text = asm_value + elif asm_key == 'DATA' + asm_data = asm_value + elif asm_key == 'LABEL_SUFFIX' + asm_label_suffix = asm_value + elif asm_key == 'GLOBL' + asm_globl = asm_value + elif asm_key == 'GLOBL_ATTR' + asm_globl_attr = asm_value + elif asm_key == 'GSYM_PREFIX' + asm_gsym_prefix = asm_value + elif asm_key == 'RODATA' + asm_rodata = asm_value + elif asm_key == 'TYPE' + asm_type = asm_value + elif asm_key == 'SIZE' + asm_size = asm_value + elif asm_key == 'LSYM_PREFIX' + asm_lsym_prefix = asm_value + elif asm_key == 'ALIGN_LOGARITHMIC' + asm_align_logarithmic = asm_value + elif asm_key == 'ALIGN_FILL_0x90' + asm_align_fill_0x90 = asm_value + elif asm_key == 'HAVE_COFF_TYPE' + asm_have_coff_type = asm_value + endif + endif + endforeach +endif + +asm_m4_includes = '' +if host_machine.cpu_family() == 'aarch64' + if host_machine.system() == 'darwin' + asm_m4_includes = 'include(' + meson.project_source_root() + '/src/mpn_extras/arm64/darwin.m4)\n' + else + asm_m4_includes = 'include(' + meson.project_source_root() + '/src/mpn_extras/arm64/arm64-defs.m4)\n' + endif +elif host_machine.cpu_family() == 'x86_64' + asm_m4_includes = 'include(' + meson.project_source_root() + '/src/mpn_extras/x86_64/x86_64-defs.m4)\n' + if host_machine.system() == 'darwin' + asm_m4_includes += 'include(' + meson.project_source_root() + '/src/mpn_extras/x86_64/darwin.m4)\n' + endif +endif + +asm_cfg = configuration_data() +asm_cfg.set('SOURCE_DIR', meson.project_source_root()) +asm_cfg.set('TEXT', asm_text) +asm_cfg.set('DATA', asm_data) +asm_cfg.set('LABEL_SUFFIX', asm_label_suffix) +asm_cfg.set('GLOBL', asm_globl) +asm_cfg.set('GLOBL_ATTR', asm_globl_attr) +asm_cfg.set('GSYM_PREFIX', asm_gsym_prefix) +asm_cfg.set('RODATA', asm_rodata) +asm_cfg.set('TYPE', asm_type) +asm_cfg.set('SIZE', asm_size) +asm_cfg.set('LSYM_PREFIX', asm_lsym_prefix) +asm_cfg.set('ALIGN_LOGARITHMIC', asm_align_logarithmic) +asm_cfg.set('ALIGN_FILL_0x90', asm_align_fill_0x90) +asm_cfg.set('HAVE_COFF_TYPE', asm_have_coff_type) +asm_cfg.set('ASM_M4_INCLUDES', asm_m4_includes) + +assembly_opt = assembly_opt.enable_auto_if(true) +have_assembly = assembly_opt.enabled() + +message('ASSEMBLY: ', have_assembly ? 'enabled' : 'disabled') +if have_assembly + message('ASM TEXT: "@0@"'.format(asm_text)) + message('ASM DATA: "@0@"'.format(asm_data)) + message('ASM LABEL_SUFFIX: "@0@"'.format(asm_label_suffix)) + message('ASM GLOBL: "@0@"'.format(asm_globl)) + message('ASM GLOBL_ATTR: "@0@"'.format(asm_globl_attr)) + message('ASM GSYM_PREFIX: "@0@"'.format(asm_gsym_prefix)) + message('ASM RODATA: "@0@"'.format(asm_rodata)) + message('ASM TYPE: "@0@"'.format(asm_type)) + message('ASM SIZE: "@0@"'.format(asm_size)) + message('ASM LSYM_PREFIX: "@0@"'.format(asm_lsym_prefix)) + message('ASM ALIGN_LOGARITHMIC: "@0@"'.format(asm_align_logarithmic)) + message('ASM ALIGN_FILL_0x90: "@0@"'.format(asm_align_fill_0x90)) + message('ASM HAVE_COFF_TYPE: "@0@"'.format(asm_have_coff_type)) +endif + +# ----------------------------------------------------------------------------- +# cpu_set_t support +# ----------------------------------------------------------------------------- + +have_cpu_set_t = cc.compiles(''' +#define _GNU_SOURCE +#include +#include + +int main() { + cpu_set_t s; + CPU_ZERO(&s); + pthread_getaffinity_np(pthread_self(), sizeof(cpu_set_t), &s); + return 0; +} +''') + +# ----------------------------------------------------------------------------- +# external configuration for flint_config.h +# ----------------------------------------------------------------------------- + +if host_machine.endian() == 'big' + cfg_data.set('FLINT_BIG_ENDIAN', 1, + description: 'Define if system is big endian.') +endif + +if have_assembly and FLINT_HAVE_ASSEMBLY == 'armv8' + cfg_data.set('FLINT_HAVE_ASSEMBLY_armv8', 1, + description: 'Define if Arm v8 assembly is available') +elif have_assembly and FLINT_HAVE_ASSEMBLY == 'x86_64_adx' + cfg_data.set('FLINT_HAVE_ASSEMBLY_x86_64_adx', 1, + description: 'Define if x86_64 ADX assembly is available') +endif + +if have_assembly + cfg_data.set('FLINT_WANT_ASSEMBLY', 1, + description: 'Define to enable inline assembly.') +endif + +if have_fft_small + cfg_data.set('FLINT_HAVE_FFT_SMALL', 1, + description: 'Define to use the fft_small module') +endif + +if flint_know_strong_order == 'yes' + cfg_data.set('FLINT_KNOW_STRONG_ORDER', 1, + description: 'Define if system is strongly ordered') +endif + +if reentrant_opt.enabled() + cfg_data.set('FLINT_REENTRANT', 1, + description: 'Define to enable reentrant.') +endif + +cfg_data.set('FLINT_UNROLL_LOOPS', 1, + description: 'Define to locally unroll some loops') + +if blas_opt.enabled() + cfg_data.set('FLINT_USES_BLAS', 1, + description: 'Define to enable BLAS.') +endif + +if have_cpu_set_t + cfg_data.set('FLINT_USES_CPUSET', 1, + description: 'Define if system has cpu_set_t') +endif + +if gc_opt.enabled() + cfg_data.set('FLINT_USES_GC', 1, + description: 'Define to enable the Boehm-Demers-Weise garbage collector.') +endif + +if pthread_opt.enabled() + cfg_data.set('FLINT_USES_PTHREAD', 1, + description: 'Define to enable the use of pthread.') +endif + +if thread_safe_opt.enabled() + cfg_data.set('FLINT_USES_TLS', 1, + description: 'Define to enable thread-local storage.') +endif + +if assert_opt.enabled() + cfg_data.set('FLINT_WANT_ASSERT', 1, + description: 'Define to enable use of asserts.') +endif + +if gmp_internals_opt.allowed() + cfg_data.set('FLINT_WANT_GMP_INTERNALS', 1, + description: 'Define to enable use of GMP internals.') +endif + +if pretty_tests_opt.enabled() + cfg_data.set('FLINT_WANT_PRETTY_TESTS', 1, + description: 'Define to enable pretty printing for tests.') +endif + +if cc.get_argument_syntax() == 'msvc' and get_option('default_library') == 'static' + cfg_data.set('FLINT_STATIC_BUILD', 1, + description: 'Define when building FLINT statically with MSVC.') +endif + +# ----------------------------------------------------------------------------- +# compiler pragmas +# ----------------------------------------------------------------------------- + +pragma_compiler = '' +if cc.get_id() == 'clang' + pragma_compiler = 'clang' +elif cc.get_id() == 'gcc' + pragma_compiler = 'GCC' +endif + +pragma_checks = { + 'DIAGNOSTIC_PUSH': [ + 'diagnostic push', + '', + ], + 'DIAGNOSTIC_POP': [ + 'diagnostic pop', + 'diagnostic push', + ], + 'DIAGNOSTIC_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES': [ + 'diagnostic ignored \\"-Wincompatible-function-pointer-types\\"', + '', + ], + 'DIAGNOSTIC_IGNORE_DISCARDED_QUALIFIERS': [ + 'diagnostic ignored \\"-Wdiscarded-qualifiers\\"', + '', + ], + 'DIAGNOSTIC_IGNORE_FORMAT': [ + 'diagnostic ignored \\"-Wformat\\"', + '', + ], + 'DIAGNOSTIC_IGNORE_DANGLING_POINTER': [ + 'diagnostic ignored \\"-Wdangling-pointer\\"', + '', + ], + 'DIAGNOSTIC_IGNORE_CAST_FUNCTION_TYPE': [ + 'diagnostic ignored \\"-Wcast-function-type\\"', + '', + ], + 'DIAGNOSTIC_IGNORE_OVERLENGTH_STRINGS': [ + 'diagnostic ignored \\"-Woverlength-strings\\"', + '', + ], + 'DIAGNOSTIC_IGNORE_UNUSED_VARIABLE': [ + 'diagnostic ignored \\"-Wunused-variable\\"', + '', + ], + 'DIAGNOSTIC_IGNORE_MAYBE_UNINITIALIZED': [ + 'diagnostic ignored \\"-Wmaybe-uninitialized\\"', + '', + ], + 'PUSH_OPTIONS': [ + 'push_options', + '', + ], + 'POP_OPTIONS': [ + 'pop_options', + 'push_options', + ], + 'OPTIMIZE_O2': [ + 'optimize (\\"O2\\")', + '', + ], + 'OPTIMIZE_OSIZE': [ + 'optimize (\\"Os\\")', + '', + ], + 'OPTIMIZE_UNROLL_LOOPS': [ + 'optimize (\\"unroll-loops\\")', + '', + ], +} + +foreach macro_name, pragma_info : pragma_checks + cfg_data.set(macro_name, '', + description: 'Define to the corresponding compiler pragma if supported.') + + if pragma_compiler != '' + pragma = pragma_info[0] + pre_pragma = pragma_info[1] + pragma_value = '_Pragma("@0@ @1@")'.format(pragma_compiler, pragma) + pragma_test = '' + + if pre_pragma != '' + pragma_test += '_Pragma("@0@ @1@")\n'.format(pragma_compiler, pre_pragma) + endif + + pragma_test += pragma_value + '\nint main(void) { return 0; }\n' + + if cc.compiles(pragma_test, + args: ['-Werror', '-Wunknown-pragmas'], + name: macro_name + ' pragma') + cfg_data.set(macro_name, pragma_value, + description: 'Define to the corresponding compiler pragma if supported.') + endif + endif +endforeach + +# ----------------------------------------------------------------------------- +# internal configuration for config.h +# ----------------------------------------------------------------------------- + +cfg_data_internal = cfg_data + +#cfg_data_internal.set('AC_APPLE_UNIVERSAL_BUILD', 1) + +if coverage_opt + cfg_data_internal.set('FLINT_COVERAGE', 1) +endif + +if cc.has_header_symbol('stdlib.h', 'aligned_alloc') + cfg_data_internal.set('HAVE_ALIGNED_ALLOC', 1, + description: 'Define to 1 if you have the \'aligned_alloc\' function.') +endif + +if cc.has_function('_aligned_malloc', prefix: '#include ') + cfg_data_internal.set('HAVE__ALIGNED_MALLOC', 1, + description: 'Define to 1 if you have the \'_aligned_malloc\' function.') +endif + +mandatory_headers = [ + 'stdarg.h', + 'math.h', + 'float.h', + 'errno.h', +] + +foreach h : mandatory_headers + if not cc.has_header(h) + error('Could not find a mandatory header: ' + h) + endif +endforeach + +if pthread_opt.enabled() and not have_pthread and not cc.has_header('pthread.h') + error('Could not find pthread.h') +endif + +if assert_opt.enabled() and not cc.has_header('assert.h') + error('Could not find assert.h') +endif + +ac_headers_check = [ + 'alloca.h', + 'arm_neon.h', + 'dlfcn.h', + 'errno.h', + 'fenv.h', + 'float.h', + 'inttypes.h', + 'malloc.h', + 'math.h', + 'pthread_np.h', + 'stdarg.h', + 'stdint.h', + 'stdio.h', + 'stdlib.h', + 'string.h', + 'strings.h', + 'sys/param.h', + 'sys/stat.h', + 'sys/types.h', + 'unistd.h', + 'windows.h', +] + +foreach h : ac_headers_check + if cc.has_header(h) + cfg_data_internal.set('HAVE_' + h.underscorify().to_upper(), 1, + description: f'Define to 1 if you have the <@h@> header file.') + endif +endforeach + +# XXX: Need better checking for LSYM_PREFIX and related assembly options +cfg_data_internal.set_quoted('LSYM_PREFIX', asm_lsym_prefix, + description: 'Assembler local label prefix') + +cfg_data_internal.set_quoted('LT_OBJDIR', '.libs/', + description: 'Define to the sub-directory where libtool stores uninstalled libraries.') +cfg_data_internal.set_quoted('PACKAGE_BUGREPORT', + 'https://github.com/flintlib/flint/issues/', + description: 'Define to the address where bug reports for this package should be sent.') +cfg_data_internal.set_quoted('PACKAGE_NAME', 'FLINT', + description: 'Define to the full name of this package.') +cfg_data_internal.set_quoted('PACKAGE_STRING', 'FLINT ' + FLINT_VERSION_FULL, + description: 'Define to the full name and version of this package.') +cfg_data_internal.set_quoted('PACKAGE_TARNAME', 'flint', + description: 'Define to the one symbol short name of this package.') +cfg_data_internal.set_quoted('PACKAGE_URL', 'https://flintlib.org/', + description: 'Define to the home page for this package.') +cfg_data_internal.set_quoted('PACKAGE_VERSION', FLINT_VERSION_FULL, + description: 'Define to the version of this package.') + +cfg_data_internal.set('STDC_HEADERS', 1, + description: '''Define to 1 if all of the C89 standard headers exist (not just the ones + required in a freestanding environment). This macro is provided for + backward compatibility; new code need not use it.''') diff --git a/dev/create_build_links.py b/dev/create_build_links.py new file mode 100644 index 0000000000..ed3ae23dd8 --- /dev/null +++ b/dev/create_build_links.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +import os +import shutil +import stat +import sys + + +EXEC_DIRS = {"profile", "test", "tune"} +SKIP_SUFFIXES = {".o", ".obj", ".d", ".pdb", ".ilk", ".rsp"} + + +def is_executable_file(path): + if not os.path.isfile(path): + return False + name = os.path.basename(path) + if name.endswith(".exe"): + return True + if os.path.splitext(name)[1] in SKIP_SUFFIXES: + return False + mode = os.stat(path).st_mode + return bool(mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)) + + +def link_or_copy(src, dst): + os.makedirs(os.path.dirname(dst), exist_ok=True) + + if os.path.lexists(dst): + if os.path.isdir(dst) and not os.path.islink(dst): + return + os.unlink(dst) + + rel_src = os.path.relpath(src, os.path.dirname(dst)) + try: + os.symlink(rel_src, dst) + except OSError: + shutil.copy2(src, dst) + + +def main(argv): + if len(argv) != 2: + print("usage: create_build_links.py BUILDDIR", file=sys.stderr) + return 2 + + build_dir = os.path.abspath(argv[1]) + src_build_dir = os.path.join(build_dir, "src") + + if not os.path.isdir(src_build_dir): + return 0 + + for root, dirs, files in os.walk(src_build_dir): + dirs[:] = [d for d in dirs if not d.endswith(".p")] + rel_root = os.path.relpath(root, src_build_dir) + parts = rel_root.split(os.sep) + + if not any(part in EXEC_DIRS for part in parts): + continue + + for name in files: + src = os.path.join(root, name) + if not is_executable_file(src): + continue + + dst = os.path.join(build_dir, rel_root, name) + link_or_copy(src, dst) + + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/dev/gcc b/dev/gcc new file mode 100755 index 0000000000..e26a67a222 --- /dev/null +++ b/dev/gcc @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +import json +import os +import shutil +import subprocess +import sys +import configparser + + +HELP = """\ +usage: dev/gcc [-C builddir] SOURCE.c [compiler arguments...] + +Compile an ad-hoc C program against the FLINT development build. + +Common examples: + dev/gcc bug.c -o bug + dev/gcc -C build-debug bug.c -o bug -O0 -g + BUILD=build-debug dev/gcc bug.c -o bug + +The script builds libflint first, uses the C compiler configured by Meson, +adds the in-tree FLINT include/library paths, and uses pkg-config for FLINT, +GMP, and MPFR. It reuses the pkg-config path saved in the Meson build +directory; set PKG_CONFIG_PATH only to add extra pkg-config search paths. +""" + + +def pop_builddir(args): + builddir = os.environ.get("BUILD", "build") + out = [] + it = iter(args) + for arg in it: + if arg == "-C": + builddir = next(it, None) + if builddir is None: + raise SystemExit("error: -C needs a build directory") + elif arg.startswith("--builddir="): + builddir = arg.split("=", 1)[1] + else: + out.append(arg) + return builddir, out + + +def run_stdout(cmd, env=None): + return subprocess.check_output(cmd, env=env, text=True).strip() + + +def shell_words(text): + # pkg-config output is shell-like. Keep this helper local so callers do not + # need a shell for the final compiler invocation. + import shlex + return shlex.split(text) + + +def dedup(args): + seen = set() + out = [] + for arg in args: + if arg in seen: + continue + seen.add(arg) + out.append(arg) + return out + + +def configured_pkg_config_path(builddir): + cmd_line = os.path.join(builddir, "meson-private", "cmd_line.txt") + if not os.path.exists(cmd_line): + return [] + + parser = configparser.ConfigParser(interpolation=None) + parser.read(cmd_line) + if not parser.has_option("options", "pkg_config_path"): + return [] + + value = parser.get("options", "pkg_config_path").strip() + if not value: + return [] + + return [path for path in value.split(os.pathsep) if path] + + +def main(argv): + if argv and argv[0] in ["-h", "--help"]: + print(HELP, end="") + return 0 + + builddir, compiler_args = pop_builddir(argv) + if not compiler_args: + print(HELP, end="", file=sys.stderr) + return 2 + + builddir = os.path.abspath(builddir) + source_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) + meson = os.path.join(source_dir, ".venv", "bin", "meson") + if not os.path.exists(meson): + meson = shutil.which("meson") or "meson" + + subprocess.check_call([meson, "compile", "-C", builddir]) + + compilers = json.loads(run_stdout([meson, "introspect", builddir, "--compilers"])) + c_compiler = compilers["host"]["c"]["exelist"] + + env = os.environ.copy() + uninstalled_pc_dir = os.path.join(builddir, "meson-uninstalled") + pkg_config_paths = [uninstalled_pc_dir] + pkg_config_paths += configured_pkg_config_path(builddir) + if env.get("PKG_CONFIG_PATH"): + pkg_config_paths += env["PKG_CONFIG_PATH"].split(os.pathsep) + env["PKG_CONFIG_PATH"] = os.pathsep.join(dedup(pkg_config_paths)) + + pkg_config = env.get("PKG_CONFIG") or shutil.which("pkg-config") or "pkg-config" + flint_pkg = "flint-uninstalled" + if subprocess.call([pkg_config, "--exists", flint_pkg], env=env) != 0: + flint_pkg = "flint" + + cflags = [ + "-I" + os.path.join(builddir, "include"), + "-I" + os.path.join(source_dir, "include"), + ] + cflags += shell_words(run_stdout([pkg_config, "--cflags", flint_pkg, "gmp", "mpfr"], env=env)) + cflags = dedup(cflags) + libs = dedup(shell_words(run_stdout([pkg_config, "--libs", flint_pkg, "gmp", "mpfr"], env=env))) + + rpath_args = [] + if os.name != "nt": + rpath_args = ["-Wl,-rpath," + builddir] + + cmd = c_compiler + cflags + compiler_args + libs + rpath_args + print(" ".join(cmd)) + return subprocess.call(cmd, env=env) + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/include/flint/meson.build b/include/flint/meson.build new file mode 100644 index 0000000000..c4858e8cb0 --- /dev/null +++ b/include/flint/meson.build @@ -0,0 +1,63 @@ +header_install_kwargs = { + 'install_dir': get_option('includedir') / 'flint', + 'install': true, +} + +flint_h = configure_file( + input: '../../src/flint.h.in', + output: 'flint.h', + configuration: { + 'FLINT_MAJOR': FLINT_MAJOR, + 'FLINT_MINOR': FLINT_MINOR, + 'FLINT_PATCH': FLINT_PATCH, + 'FLINT_VERSION_FULL': FLINT_VERSION_FULL, + }, + kwargs: header_install_kwargs, +) + +flint_config_h = configure_file( + output: 'flint-config.h', + configuration: cfg_data, + kwargs: header_install_kwargs, +) + +config_h = configure_file( + output: 'config.h', + configuration: cfg_data_internal, + kwargs: header_install_kwargs, +) + +if gmp_long_long_limb + gmpcompat_h_in = 'gmpcompat-longlong.h.in' +else + gmpcompat_h_in = 'gmpcompat.h.in' +endif + +gmpcompat_h = fs.copyfile( + '../../src' / gmpcompat_h_in, + 'gmpcompat.h', + kwargs: header_install_kwargs, +) + +flint_mparam_h_in = '../../src/mpn_extras' / flint_mparam / 'flint-mparam.h' + +flint_mparam_h = fs.copyfile( + flint_mparam_h_in, + 'flint-mparam.h', + kwargs: header_install_kwargs, +) + +# Copy all the other headers to the build directory so you can do +# gcc -Ibuild-dir/include ... +public_headers = [] +foreach h: headers_all + public_headers += fs.copyfile(h, kwargs: header_install_kwargs) +endforeach + +headers_all = public_headers + [ + flint_h, + flint_config_h, + config_h, + gmpcompat_h, + flint_mparam_h, +] diff --git a/include/meson.build b/include/meson.build new file mode 100644 index 0000000000..39b83d76e8 --- /dev/null +++ b/include/meson.build @@ -0,0 +1,4 @@ +subdir('flint') + +headers_built_inc = include_directories('.') +headers_built_nodir_inc = include_directories('flint') diff --git a/meson.build b/meson.build new file mode 100644 index 0000000000..e10e5721b4 --- /dev/null +++ b/meson.build @@ -0,0 +1,284 @@ +project('FLINT', 'c', + version: '3.6.0-dev', + license: 'LGPL3+', + meson_version: '>=1.1.0', + default_options: [ + 'buildtype=release', + 'c_std=c11', + 'debug=true', + ], +) + +FLINT_MAJOR = meson.project_version().split('.')[0] +FLINT_MINOR = meson.project_version().split('.')[1] +FLINT_PATCH = meson.project_version().split('.')[2].split('-')[0] +FLINT_VERSION_FULL = meson.project_version() +FLINT_MAJOR_SO = '24' +FLINT_MINOR_SO = '0' +FLINT_PATCH_SO = '0' +FLINT_LIBRARY_VERSION = '.'.join([FLINT_MAJOR_SO, FLINT_MINOR_SO, FLINT_PATCH_SO]) + +cc = meson.get_compiler('c') +fs = import('fs') +python3 = find_program('python3') +default_library_opt = get_option('default_library') +build_shared_library = default_library_opt in ['shared', 'both'] +build_static_library = default_library_opt in ['static', 'both'] + +m_dep = cc.find_library('m', required: false) + +gmp_dep = dependency('gmp', version: '>= 6.2.1') +mpfr_dep = dependency('mpfr', version: '>= 4.1.0') + +gmp_link_args = [] +if cc.get_argument_syntax() != 'msvc' + gmp_libdir = gmp_dep.get_variable(pkgconfig: 'libdir') + gmp_link_args = ['-L' + gmp_libdir] +endif +gmp_link_dep = declare_dependency( + dependencies: gmp_dep, + link_args: gmp_link_args, +) + +subdir('config') + +add_project_arguments(flint_warning_c_args + flint_msvc_c_args, language: 'c') + +flint_deps = [gmp_link_dep, mpfr_dep, m_dep, pthread_dep] +if have_blas + flint_deps += blas_dep +endif +if have_gc + flint_deps += gc_dep +endif + +flint_compile_deps = [] +foreach dep : flint_deps + flint_compile_deps += dep.partial_dependency( + compile_args: true, + includes: true, + sources: true, + ) +endforeach + +add_project_arguments(flint_abi_c_args, language: 'c') +add_project_link_arguments(flint_abi_c_args, language: 'c') + +subdir('src') + +if have_assembly + # Generate config.m4 with assembly language configuration + config_m4 = configure_file( + input: 'config.m4.in', + output: 'config.m4', + configuration: asm_cfg, + ) + + s_files_static = [] + s_files_shared = [] + subdir('src' / assembly_dir) +else + s_files_static = [] + s_files_shared = [] +endif + +c_files_static_all = c_files_all + s_files_static +c_files_shared_all = c_files_all + s_files_shared +c_files_static = s_files_static +c_files_shared = s_files_shared + +# Build include directory after list of all source files is complete +subdir('include') + +libflint_c_args = [ + '-DBUILDING_FLINT', + '-DFLINT_NOSTDIO', + '-DFLINT_NOSTDARG', +] + +libflint_shared_link_whole = [] +libflint_static_link_whole = [] + +if cc.get_argument_syntax() != 'msvc' + foreach bucket : [ + ['missing_prototypes', + c_file_buckets_missing_prototypes, + libflint_c_args + flint_cpu_c_args + flint_library_warning_c_args], + ['no_missing_prototypes', + c_file_buckets_no_missing_prototypes, + libflint_c_args + flint_cpu_c_args], + ['unroll', + c_file_buckets_missing_prototypes_unroll, + libflint_c_args + flint_cpu_c_args + flint_library_warning_c_args + flint_unroll_loops_c_args], + ['no_missing_prototypes_unroll', + c_file_buckets_no_missing_prototypes_unroll, + libflint_c_args + flint_cpu_c_args + flint_unroll_loops_c_args], + ] + bucket_name = bucket[0] + bucket_source_groups = bucket[1] + bucket_c_args = bucket[2] + + foreach bucket_source_group : bucket_source_groups + bucket_source_name = bucket_source_group[0] + bucket_sources = bucket_source_group[1] + + if bucket_sources.length() == 0 + continue + endif + + if build_shared_library + libflint_shared_link_whole += [static_library('flint_' + bucket_name + '_' + bucket_source_name + '_shared', + bucket_sources + headers_all, + dependencies: flint_compile_deps, + include_directories: [headers_built_nodir_inc, 'src'], + install: false, + pic: true, + c_args: bucket_c_args, + )] + endif + + if build_static_library + libflint_static_link_whole += [static_library('flint_' + bucket_name + '_' + bucket_source_name + '_static', + bucket_sources + headers_all, + dependencies: flint_compile_deps, + include_directories: [headers_built_nodir_inc, 'src'], + install: false, + pic: false, + c_args: bucket_c_args, + )] + endif + endforeach + endforeach +endif + +regression_c_args = libflint_c_args + flint_cpu_c_args + cc.get_supported_arguments([ + '-march=native', + '-Wall', + '-Wextra', + '-Werror', +]) + +regression_lib = static_library('flint_regression_check', + regression_c_files + headers_all, + dependencies: flint_deps, + include_directories: [headers_built_nodir_inc, 'src'], + install: false, + build_by_default: false, + c_args: regression_c_args, + override_options: ['optimization=0', 'debug=false'], +) + +alias_target('regression-check', regression_lib) + +if cc.get_argument_syntax() == 'msvc' and build_shared_library + libflint_objects = static_library('flint_objects', c_files_shared_all + headers_all, + dependencies: flint_deps, + include_directories: [headers_built_nodir_inc, 'src'], + install: false, + c_args: libflint_c_args + ['-DFLINT_BUILD_DLL'] + flint_cpu_c_args + flint_library_warning_c_args, + ) + + libflint_object_files = libflint_objects.extract_all_objects(recursive: false) + + libflint_def = custom_target( + 'flint.def', + output: 'flint.def', + command: [ + python, + files('config/generate_msvc_def.py'), + '@OUTPUT@', + '--object-dir', + 'libflint_objects.a.p', + ], + depends: libflint_objects, + ) + + libflint_shared = shared_library('flint', + objects: libflint_object_files, + dependencies: flint_deps, + include_directories: [headers_built_nodir_inc, 'src'], + install: true, + version: FLINT_LIBRARY_VERSION, + soversion: FLINT_MAJOR_SO, + vs_module_defs: libflint_def, + ) + + if build_static_library + libflint_static = static_library('flint', c_files_static_all + headers_all, + dependencies: flint_deps, + include_directories: [headers_built_nodir_inc, 'src'], + install: true, + c_args: libflint_c_args + flint_cpu_c_args + flint_library_warning_c_args, + ) + endif + + libflint = libflint_shared +elif cc.get_argument_syntax() == 'msvc' + libflint = static_library('flint', c_files_static_all + headers_all, + dependencies: flint_deps, + include_directories: [headers_built_nodir_inc, 'src'], + install: true, + c_args: libflint_c_args + flint_cpu_c_args + flint_library_warning_c_args, + ) +else + if build_shared_library + libflint_shared = shared_library('flint', c_files_shared + headers_all, + dependencies: flint_deps, + include_directories: [headers_built_nodir_inc, 'src'], + link_whole: libflint_shared_link_whole, + install: true, + version: FLINT_LIBRARY_VERSION, + soversion: FLINT_MAJOR_SO, + c_args: libflint_c_args + flint_cpu_c_args + flint_library_warning_c_args, + ) + libflint = libflint_shared + endif + + if build_static_library + libflint_static = static_library('flint', c_files_static + headers_all, + dependencies: flint_deps, + include_directories: [headers_built_nodir_inc, 'src'], + link_whole: libflint_static_link_whole, + install: true, + c_args: libflint_c_args + flint_cpu_c_args + flint_library_warning_c_args, + ) + + if not build_shared_library + libflint = libflint_static + endif + endif +endif + +flint_test_dep = declare_dependency( + link_with: libflint, + dependencies: flint_deps, + include_directories: [headers_built_nodir_inc, 'src'], + sources: headers_all, + compile_args: flint_cpu_c_args, +) + +subdir('examples') + +foreach mod_profile : mod_profiles + subdir('src' / mod_profile) +endforeach +alias_target('profile', profile_exes) + +foreach mod_tune : mod_tunes + subdir('src' / mod_tune) +endforeach +alias_target('tune', tune_exes) + +foreach mod_test : mod_tests + subdir('src' / mod_test) +endforeach +alias_target('tests', test_exes) + +pkg_mod = import('pkgconfig') +pkg_mod.generate( + libraries: libflint, + version : meson.project_version(), + name: 'flint', + filebase: 'flint', + description: 'Fast Library for Number Theory' +) diff --git a/meson.options b/meson.options new file mode 100644 index 0000000000..0291a8c370 --- /dev/null +++ b/meson.options @@ -0,0 +1,40 @@ +option('pthread', type : 'feature', value : 'enabled', + description: 'Use pthread') +option('reentrant', type : 'feature', value : 'disabled', + description: 'Build the reentrant module') +option('thread_safe', type : 'feature', value : 'enabled', + description: 'Build the threadsafe module') +option('abi', type : 'combo', choices : ['auto', '32', '64'], value : 'auto', + description: 'Target ABI') +option('assert', type : 'feature', value : 'disabled', + description: 'Enable assertions') +option('pretty_tests', type : 'feature', value : 'enabled', + description: 'Pretty print test output') +option('gmp_internals', type : 'feature', value : 'auto', + description: 'Use GMP internals') +option('mpfr_check', type : 'feature', value : 'enabled', + description: 'Check for MPFR internal functions used by FLINT') +option('assembly', type : 'feature', value : 'auto', + description: 'Use assembly code') +option('avx2', type : 'feature', value : 'disabled', + description: 'Use AVX2 instructions') +option('avx512', type : 'feature', value : 'disabled', + description: 'Use AVX512 instructions') +option('fft_small', type : 'feature', value : 'auto', + description: 'Build the fft_small module') +option('blas', type : 'feature', value : 'disabled', + description: 'Use BLAS') +option('gc', type : 'feature', value : 'disabled', + description: 'Use the Boehm garbage collector') +option('ntl', type : 'feature', value : 'disabled', + description: 'Use NTL (in tests)') +# Autotools options covered by Meson built-ins: +# coverage: -Db_coverage=true +# debug: -Ddebug=true or -Dbuildtype=debug +# with-pic: -Db_staticpic=true/false +# with-sysroot: machine-file sys_root or compiler/pkg-config environment +# with-gnu-ld: linker selection via compiler/native/cross file +# dependency_tracking: handled by Meson/Ninja +# +# Not currently mirrored: +# with-aix-soname diff --git a/src/flint.h.in b/src/flint.h.in index a806ac5e86..a0f23b6d5c 100644 --- a/src/flint.h.in +++ b/src/flint.h.in @@ -36,7 +36,17 @@ #endif #ifndef FLINT_DLL -# define FLINT_DLL +# ifdef _MSC_VER +# if defined(FLINT_STATIC_BUILD) +# define FLINT_DLL +# elif defined(FLINT_BUILD_DLL) +# define FLINT_DLL __declspec(dllexport) +# else +# define FLINT_DLL __declspec(dllimport) +# endif +# else +# define FLINT_DLL +# endif #endif #ifndef alloca diff --git a/update_meson.py b/update_meson.py new file mode 100755 index 0000000000..b4ccb89b97 --- /dev/null +++ b/update_meson.py @@ -0,0 +1,804 @@ +#!/usr/bin/env python3 + +import sys +from os import listdir, makedirs +from os.path import join, dirname, abspath, isdir +from shutil import copy2 +from argparse import ArgumentParser + +this_dir = dirname(abspath(__file__)) + +files_to_copy = [ + 'meson.build', + 'meson.options', + 'include/meson.build', + 'include/flint/meson.build', + 'config.m4.in', +] + +# Directories in src that are not modules or that need special handling +exclude_mod_dirs = [ + 'test', + 'profile', + 'interfaces', + 'python', + 'include', +] + +conditional_modules = [ + 'fft_small', +] + +template_modules = [ + 'fq_templates', + 'fq_embed_templates', + 'fq_poly_templates', + 'fq_poly_factor_templates', + 'fq_mat_templates', + 'fq_vec_templates', +] + +test_only_modules = [ + 'fq_zech_vec', +] + +non_library_modules = template_modules + test_only_modules + +unroll_modules = [ + 'ulong_extras', + 'nmod', + 'nmod_vec', + 'nmod_mat', + 'nmod_poly', + 'arith', +] + +# Modules that do not have a corresponding header file +mod_no_header = [ + 'generic_files', +] + +# Headers that do not have a corresponding module +head_no_dir = [ + 'NTL-interface', + 'acb_types', + 'acf_types', + 'arb_types', + 'arf_types', + 'ca_types', + 'crt_helpers', + 'fmpq_types', + 'fmpz_mod_types', + 'fmpz_types', + 'fq_nmod_types', + 'fq_types', + 'fq_zech_types', + 'gettimeofday', + 'gr_types', + 'limb_types', + 'longlong', + 'longlong_asm_clang', + 'longlong_asm_gcc', + 'longlong_asm_gnu', + 'longlong_div_gnu', + 'longlong_msc_arm64', + 'longlong_msc_x86', + 'machine_vectors', + 'mpoly_types', + 'n_poly_types', + 'nmod_types', + 'padic_types', + 'profiler', + 'templates', + 'test_helpers', +] + non_library_modules + +headers_skip = [ + 'flint', + 'config', + 'flint-config', + 'flint-mparam', + 'gmpcompat', +] + +mod_no_tests = [ + 'ca_vec', + 'calcium', + 'fexpr_builtin', + 'fmpz_mod_vec', + 'fq_zech_mpoly', + 'fq_zech_mpoly_factor', + 'generic_files', + 'hypgeom', +] + +mod_no_profiles = [ +] + +mod_no_tunes = [ +] + +regression_modules = [ + 'thread_pool', + 'thread_support', + 'ulong_extras', + 'long_extras', + 'perm', + 'double_extras', + 'd_vec', + 'd_mat', + 'mpn_extras', + 'nmod', + 'nmod_vec', + 'nmod_mat', + 'nmod_poly', + 'mpn_mod', + 'fmpz', + 'fmpz_vec', + 'fmpz_mat', + 'fmpz_poly', + 'fmpz_mod', + 'fmpz_mod_vec', + 'fmpz_mod_mat', + 'fmpz_mod_poly', + 'fmpq', + 'fmpq_vec', + 'fmpq_mat', + 'fmpq_poly', + 'fq', + 'fq_vec', + 'fq_mat', + 'fq_poly', + 'fq_nmod', + 'fq_nmod_vec', + 'fq_nmod_mat', + 'fq_nmod_poly', + 'fq_zech', + 'fq_zech_mat', + 'fq_zech_poly', + 'fq_default', + 'fq_default_mat', + 'fq_default_poly', + 'fq_embed', + 'fq_nmod_embed', + 'fq_zech_embed', + 'padic', + 'padic_mat', + 'padic_poly', + 'qadic', + 'nmod_poly_factor', + 'fmpz_factor', + 'fmpz_poly_factor', + 'fmpz_mod_poly_factor', + 'fq_poly_factor', + 'fq_nmod_poly_factor', + 'fq_zech_poly_factor', + 'fq_default_poly_factor', + 'nmod_poly_mat', + 'fmpz_poly_mat', + 'mpoly', + 'nmod_mpoly', + 'fmpz_mpoly', + 'fmpz_mod_mpoly', + 'fmpq_mpoly', + 'fq_nmod_mpoly', + 'fq_zech_mpoly', + 'nmod_mpoly_factor', + 'fmpz_mpoly_factor', + 'fmpz_mod_mpoly_factor', + 'fmpq_mpoly_factor', + 'fq_nmod_mpoly_factor', + 'fq_zech_mpoly_factor', + 'fft', + 'fft_small', +] + +src_meson_build = '''\ +# +# This file is generated automatically. +# +# To regenerate it, run: +# python update_meson.py +# + +modules = [ +%s +] + +modules_no_header = [ +%s +] + +modules_no_tests = [ +%s +] + +headers_no_dir = [ +%s +] + +regression_modules = [ +%s +] + +unroll_modules = [ +%s +] + +profile_modules = [ +%s +] + +conditional_profile_modules = [ +%s +] + +tune_modules = [ +%s +] + +headers_all = [] +c_files_all = [] +c_files_missing_prototypes = [] +c_files_missing_prototypes_unroll = [] +c_files_no_missing_prototypes = [] +c_files_no_missing_prototypes_unroll = [] +c_file_buckets_missing_prototypes = [] +c_file_buckets_missing_prototypes_unroll = [] +c_file_buckets_no_missing_prototypes = [] +c_file_buckets_no_missing_prototypes_unroll = [] +regression_c_files = [] +mod_tests = [] +test_exes = [] +mod_profiles = [] +profile_exes = [] +mod_tunes = [] +tune_exes = [] + +# Select the right version of fmpz.c (see configuration) +fmpz_link_c_file = files('fmpz/link' / fmpz_c_in) +c_files_all += fmpz_link_c_file +c_files_missing_prototypes += fmpz_link_c_file +c_file_buckets_missing_prototypes += [['fmpz_link', fmpz_link_c_file]] +regression_c_files += fmpz_link_c_file + +foreach mod : modules + subdir(mod) + if mod not in modules_no_tests + mod_tests += [mod / 'test'] + endif +endforeach + +foreach mod : modules + headers_no_dir + if mod not in modules_no_header + headers_all += [mod + '.h'] + endif +endforeach + +# Add conditional modules + +if have_fft_small + subdir('fft_small') + mod_tests += ['fft_small/test'] + mod_profiles += conditional_profile_modules + headers_all += ['fft_small.h'] +endif + +if ntl_opt.enabled() + mod_tests += ['interfaces/test'] +endif + +foreach mod : [ +%s +] + mod_tests += [mod / 'test'] +endforeach + +mod_profiles += profile_modules +mod_tunes += tune_modules + +headers_all = files(headers_all) +''' + +src_mod_meson_build = '''\ +# +# This file is generated automatically. +# +# To regenerate it, run: +# python update_meson.py +# + +module_c_files = files( +%s +) + +module_c_files_no_missing_prototypes = files( +%s +) + +c_files_all += module_c_files +c_files_all += module_c_files_no_missing_prototypes + +if '%s' in unroll_modules + c_files_missing_prototypes_unroll += module_c_files + c_files_no_missing_prototypes_unroll += module_c_files_no_missing_prototypes + c_file_buckets_missing_prototypes_unroll += [['%s', module_c_files]] + c_file_buckets_no_missing_prototypes_unroll += [['%s', module_c_files_no_missing_prototypes]] +else + c_files_missing_prototypes += module_c_files + c_files_no_missing_prototypes += module_c_files_no_missing_prototypes + c_file_buckets_missing_prototypes += [['%s', module_c_files]] + c_file_buckets_no_missing_prototypes += [['%s', module_c_files_no_missing_prototypes]] +endif + +if '%s' in regression_modules + regression_c_files += module_c_files + regression_c_files += module_c_files_no_missing_prototypes +endif +''' + +test_mod_meson_build = '''\ +# This file is generated automatically. +# +# To regenerate it, run: +# python update_meson.py +# + +test_exe = executable('main', + 'main.c', + dependencies: [flint_test_dep], + install: false, + build_by_default: false, +) +test_exes += test_exe + +test('%s', test_exe, timeout: 0) +''' + +profile_mod_meson_build = '''\ +# This file is generated automatically. +# +# To regenerate it, run: +# python update_meson.py +# + +mod_profile_exes = [] + +foreach profile_source : [ +%s +] + profile_stem = fs.stem(profile_source) + profile_exe = executable( + profile_stem, + profile_source, + dependencies: [flint_test_dep], + include_directories: [headers_built_inc], + install: false, + build_by_default: false, + ) + profile_exes += profile_exe + mod_profile_exes += profile_exe +endforeach + +alias_target('%s-profile', mod_profile_exes) +''' + +tune_mod_meson_build = '''\ +# This file is generated automatically. +# +# To regenerate it, run: +# python update_meson.py +# + +foreach tune_source : [ +%s +] + tune_stem = fs.stem(tune_source) + tune_exes += executable( + tune_stem, + tune_source, + dependencies: [flint_test_dep], + include_directories: [headers_built_inc], + install: false, + build_by_default: false, + ) +endforeach +''' + + +test_mod_NTL_meson_build = '''\ +# This file is generated automatically. +# +# To regenerate it, run: +# python update_meson.py +# + +test_exe = executable('main', + 't-NTL-interface.cpp', + dependencies: [flint_test_dep, ntl_dep], + install: false, + build_by_default: false, +) +test_exes += test_exe + +test('%s', test_exe, timeout: 0) +''' + +examples_meson_build = '''\ +# +# This file is generated automatically. +# +# To regenerate it, run: +# python update_meson.py +# + +examples = [ +%s +] +example_exes = [] + +foreach example : examples + example_exe = executable( + example, + example + '.c', + dependencies: [flint_test_dep], + include_directories: [headers_built_inc], + install: false, + build_by_default: false, + ) + + example_exes += example_exe +endforeach + +alias_target('examples', example_exes) +''' + +asm_submodule_arm64 = '''\ + +asm_deps = [ + '../asm-defs.m4', +] + +if host_machine.system() == 'darwin' + asm_deps += ['darwin.m4'] +else + asm_deps += ['arm64-defs.m4'] +endif + +asm_files = [ +%s +] + +src_dir_inc = include_directories('.') +''' + +asm_submodule_x86_broadwell = '''\ + +asm_deps = [ + '../../asm-defs.m4', + '../x86_64-defs.m4', +] + +if host_machine.system() == 'darwin' + asm_deps += ['darwin.m4'] +endif + +asm_files = [ +%s +] +''' + +asm_to_s_files = '''\ + +m4_prog = find_program('m4', native: true) + +foreach asm_file: asm_files + asm_stem = fs.stem(asm_file) + + if default_library_opt in ['static', 'both'] + s_filename = asm_stem + '.s' + s_file = custom_target(s_filename, + input: [asm_file, config_m4] + asm_deps, + output: s_filename, + command: [m4_prog, '-I', meson.project_build_root(), '@INPUT0@'], + capture: true, + ) + s_files_static += [s_file] + endif + + if default_library_opt in ['shared', 'both'] + s_pic_filename = asm_stem + '_pic.s' + s_pic_file = custom_target(s_pic_filename, + input: [asm_file, config_m4] + asm_deps, + output: s_pic_filename, + command: [m4_prog, '-I', meson.project_build_root(), '-DPIC', '@INPUT0@'], + capture: true, + ) + s_files_shared += [s_pic_file] + endif +endforeach +''' + + +asm_modules = [ + ('mpn_extras/arm64', asm_submodule_arm64), + ('mpn_extras/x86_64/broadwell', asm_submodule_x86_broadwell), +] + + +def get_flint_modules(flint_root): + """ + Scan the src directory and return a list of all modules. + + Also check for possible mismatches between subdirs and headers. + """ + src_path = join(flint_root, 'src') + is_mod = lambda p: isdir(join(src_path, p)) and p not in exclude_mod_dirs + subdirs = [p for p in listdir(src_path) if is_mod(p)] + headers = [p[:-2] for p in listdir(src_path) if p.endswith('.h')] + + # Check for mismatches between subdirs and headers apart from the + # known exceptions (exclude_mod_dirs, mod_no_header, head_no_dir) + extra_dirs = set(subdirs) - set(headers) - set(mod_no_header) + extra_headers = set(headers) - set(subdirs) - set(head_no_dir) - set(headers_skip) + if extra_dirs or extra_headers: + print('Mismatch between subdirs and headers in src') + print('Extra headers:\n', '\n'.join(sorted(extra_headers))) + print('Extra subdirs:\n', '\n'.join(sorted(extra_dirs))) + sys.exit(1) + missing_dirs = set(mod_no_header) - set(subdirs) + missing_headers = (set(head_no_dir) - set(headers)) + if missing_dirs or missing_headers: + print('Missing subdirs:\n', '\n'.join(sorted(missing_dirs))) + print('Missing headers:\n', '\n'.join(sorted(missing_headers))) + sys.exit(1) + + return subdirs + + +parser = ArgumentParser(description='Generate Meson build files') +parser.add_argument('-q', '--quiet', action='store_true', + help='Do not print anything') +parser.add_argument('-v', '--verbose', action='store_true', + help='Show all steps') + + +def main(args): + args = parser.parse_args(args) + output_dir = this_dir + + for fname in files_to_copy: + src_path = join(this_dir, fname) + dst_path = join(output_dir, fname) + if not args.quiet: + print('Copying %s to %s' % (src_path, dst_path)) + copy_file(src_path, dst_path) + + modules = get_flint_modules(output_dir) + + # We will generate the meson.build file for conditional modules but the + # main meson.build file will decide whether to include them or not. + modules_unconditional = set(modules) - set(conditional_modules) - set(non_library_modules) + + # src/meson.build + top_profile_dir = join(output_dir, 'src', 'profile') + top_profile_sources = [] + if isdir(top_profile_dir): + top_profile_sources = [ + f for f in listdir(top_profile_dir) + if f.endswith('.c') + ] + profile_modules = [] + if top_profile_sources: + profile_modules.append('profile') + conditional_profile_modules = [] + profile_scan_modules = ( + set(modules) - set(non_library_modules) - set(conditional_modules) + ) | set(mod_no_header) + for mod in profile_scan_modules: + profile_dir = join(output_dir, 'src', mod, 'profile') + if mod not in mod_no_profiles and isdir(profile_dir): + profile_c_files = [ + f for f in listdir(profile_dir) + if f.endswith('.c') + ] + if profile_c_files: + profile_modules.append(mod + '/profile') + for mod in conditional_modules: + profile_dir = join(output_dir, 'src', mod, 'profile') + if mod not in mod_no_profiles and isdir(profile_dir): + profile_c_files = [ + f for f in listdir(profile_dir) + if f.endswith('.c') + ] + if profile_c_files: + conditional_profile_modules.append(mod + '/profile') + + top_tune_dir = join(output_dir, 'src', 'tune') + top_tune_sources = [] + if isdir(top_tune_dir): + top_tune_sources = [ + f for f in listdir(top_tune_dir) + if f.endswith('.c') + ] + tune_modules = [] + if top_tune_sources: + tune_modules.append('tune') + tune_scan_modules = ( + set(modules) - set(non_library_modules) - set(conditional_modules) + ) | set(mod_no_header) + for mod in tune_scan_modules: + tune_dir = join(output_dir, 'src', mod, 'tune') + if mod not in mod_no_tunes and isdir(tune_dir): + tune_c_files = [ + f for f in listdir(tune_dir) + if f.endswith('.c') + ] + if tune_c_files: + tune_modules.append(mod + '/tune') + + src_meson_build_text = src_meson_build % ( + format_lines(modules_unconditional), + format_lines(mod_no_header), + format_lines(mod_no_tests), + format_lines(head_no_dir), + format_lines(regression_modules), + format_lines(unroll_modules), + format_lines(profile_modules), + format_lines(conditional_profile_modules), + format_lines(tune_modules), + format_lines(test_only_modules), + ) + dst_path = join(output_dir, 'src', 'meson.build') + if not args.quiet: + print('Writing %s' % dst_path) + write_file(dst_path, src_meson_build_text) + + if not args.quiet: + print('Making meson.build files in all modules') + # src/mod/meson.build + for mod in modules + mod_no_header: + mod_dir = join(output_dir, 'src', mod) + c_files = [f for f in listdir(mod_dir) if f.endswith('.c')] + if mod == 'fmpz': + c_files = [f for f in c_files if f != 'fmpz.c'] + c_files_no_missing_prototypes = [f for f in c_files if f == 'inlines.c'] + c_files = [f for f in c_files if f not in c_files_no_missing_prototypes] + src_mod_meson_build_text = src_mod_meson_build % ( + format_lines(c_files), + format_lines(c_files_no_missing_prototypes), + mod, + mod, + mod, + mod, + mod, + mod, + ) + dst_path = join(mod_dir, 'meson.build') + if args.verbose: + print('Writing %s' % dst_path) + write_file(dst_path, src_mod_meson_build_text) + + # src/mod/test/meson.build + if mod not in mod_no_tests: + test_dir = join(mod_dir, 'test') + test_mod_meson_build_text = test_mod_meson_build % mod + dst_path = join(test_dir, 'meson.build') + if args.verbose: + print('Writing %s' % dst_path) + write_file(dst_path, test_mod_meson_build_text) + + # src/mod/profile/meson.build + profile_dir = join(mod_dir, 'profile') + if mod not in mod_no_profiles and isdir(profile_dir): + profile_c_files = [f for f in listdir(profile_dir) if f.endswith('.c')] + if profile_c_files: + profile_mod_meson_build_text = profile_mod_meson_build % ( + format_lines(profile_c_files), + mod, + ) + dst_path = join(profile_dir, 'meson.build') + if args.verbose: + print('Writing %s' % dst_path) + write_file(dst_path, profile_mod_meson_build_text) + + # src/mod/tune/meson.build + tune_dir = join(mod_dir, 'tune') + if mod not in mod_no_tunes and isdir(tune_dir): + tune_c_files = [f for f in listdir(tune_dir) if f.endswith('.c')] + if tune_c_files: + tune_mod_meson_build_text = tune_mod_meson_build % ( + format_lines(tune_c_files), + ) + dst_path = join(tune_dir, 'meson.build') + if args.verbose: + print('Writing %s' % dst_path) + write_file(dst_path, tune_mod_meson_build_text) + + # Build for the NTL tests + dst_path = join(output_dir, 'src', 'interfaces', 'test', 'meson.build') + test_mod_meson_build_text = test_mod_NTL_meson_build % 'NTL-interface' + if args.verbose: + print('Writing %s' % dst_path) + write_file(dst_path, test_mod_meson_build_text) + + # examples/meson.build + examples_dir = join(output_dir, 'examples') + examples = [ + f[:-2] for f in listdir(examples_dir) + if f.endswith('.c') + ] + examples_meson_build_text = examples_meson_build % format_lines(examples) + dst_path = join(examples_dir, 'meson.build') + if args.verbose: + print('Writing %s' % dst_path) + write_file(dst_path, examples_meson_build_text) + + # src/profile/meson.build + if top_profile_sources: + profile_mod_meson_build_text = profile_mod_meson_build % ( + format_lines(top_profile_sources), + 'profile', + ) + dst_path = join(top_profile_dir, 'meson.build') + if args.verbose: + print('Writing %s' % dst_path) + write_file(dst_path, profile_mod_meson_build_text) + + # src/tune/meson.build + if top_tune_sources: + tune_mod_meson_build_text = tune_mod_meson_build % ( + format_lines(top_tune_sources), + ) + dst_path = join(top_tune_dir, 'meson.build') + if args.verbose: + print('Writing %s' % dst_path) + write_file(dst_path, tune_mod_meson_build_text) + + # src/mpn_extras/*/meson.build + for path, asm_submodule in asm_modules: + asm_dir = join(output_dir, 'src', path) + asm_files = [f for f in listdir(asm_dir) if f.endswith('.asm')] + asm_submodule_text = asm_submodule % format_lines(asm_files) + asm_submodule_text += asm_to_s_files + dst_path = join(asm_dir, 'meson.build') + if args.verbose: + print('Writing %s' % dst_path) + write_file(dst_path, asm_submodule_text) + + +def format_lines(lst): + return '\n'.join(f" '{m}'," for m in sorted(lst)) + + +def write_file(dst_path, text): + makedirs(dirname(dst_path), exist_ok=True) + with open(dst_path, 'w') as fout: + fout.write(text) + + +def copy_file(src_path, dst_path): + if abspath(src_path) == abspath(dst_path): + return + makedirs(dirname(dst_path), exist_ok=True) + copy2(src_path, dst_path) + + +def same_files(src_path, dst_path): + with open(src_path, 'rb') as f: + src_content = f.read() + return same_content(src_content, dst_path) + + +def same_content(src_content, dst_path): + try: + with open(dst_path, 'rb') as f: + dst_content = f.read() + except FileNotFoundError: + return False + else: + return src_content == dst_content + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:]))