diff --git a/.Rbuildignore b/.Rbuildignore index 1d20737..a981b71 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -6,3 +6,4 @@ ^autom4te\.cache$ ^config\.log$ ^config\.status$ +^\.gitattributes$ diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c6bc410 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# Ensure shell scripts and configure files always use LF line endings +configure text eol=lf +configure.ac text eol=lf +configure.win text eol=lf +cleanup text eol=lf +*.sh text eol=lf diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 2ff3f6f..980245d 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -18,12 +18,14 @@ jobs: fail-fast: false matrix: config: - - {os: macos-latest, r: 'release'} - - {os: ubuntu-latest, r: 'release'} + #- {os: macos-latest, r: 'release'} + #- {os: ubuntu-latest, r: 'release'} + - {os: windows-latest, r: 'release'} env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} R_KEEP_PKG_SOURCE: yes + INTEL_OPENCL_URL: "https://registrationcenter-download.intel.com/akdlm/IRC_NAS/b6dccdb7-b503-41ea-bd4b-a78e9c2d8dd6/w_opencl_runtime_p_2025.1.0.972.exe" steps: - uses: actions/checkout@v4 @@ -38,12 +40,80 @@ jobs: - name: Install clblast via Homebrew (macOS) if: runner.os == 'macOS' - run: brew install clblast clblas + run: brew install clblast clblas clinfo - name: Install clblast via apt (Ubuntu) if: runner.os == 'Linux' run: sudo apt-get update && sudo apt-get install -y opencl-headers ocl-icd-opencl-dev libclblast-dev + - name: Cache Intel OpenCL Runtime + if: runner.os == 'Windows' + id: cache-opencl-win + uses: actions/cache@v5 + with: + # Default install location is x86 ... + path: C:\Program Files (x86)\Common Files\Intel\Shared Libraries + key: intel-opencl-runtime-2025.3.1 + + - name: Install OpenCL SDK (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + vcpkg install opencl:x64-windows clblast:x64-windows + # Convert to forward slashes to avoid escaping issues in configure.win + $vcpkgPath = "$env:VCPKG_INSTALLATION_ROOT/installed/x64-windows" -replace '\\','/' + + # Register OpenCL location + echo "OPENCL_CPPFLAGS=-I$vcpkgPath/include" >> $env:GITHUB_ENV + echo "OPENCL_LIBS=-L$vcpkgPath/lib -lOpenCL" >> $env:GITHUB_ENV + + # Register CLBlast location + echo "CLBLAST_CPPFLAGS=-I$vcpkgPath/include" >> $env:GITHUB_ENV + echo "CLBLAST_LIBS=-L$vcpkgPath/lib -lclblast" >> $env:GITHUB_ENV + + # Add vcpkg bin to PATH so DLLs can be found at runtime + echo "$env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\bin" >> $env:GITHUB_PATH + + - name: Install Intel CPU Runtime for OpenCL (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + # Download + curl -o opencl-installer.exe "${{ env.INTEL_OPENCL_URL }}" + + # Extract MSI from the self-extracting exe + $proc = Start-Process "./opencl-installer.exe" "-s -x -f extracted" -NoNewWindow -PassThru + $proc.WaitForExit() + + # Install via msiexec + $msi = Get-ChildItem ./extracted/*.msi | Select-Object -First 1 + $proc = Start-Process "msiexec" "/i `"$msi`" /qn /l*! install.log" -NoNewWindow -PassThru + $proc.WaitForExit() + + if ($proc.ExitCode -ne 0) { + Get-Content install.log + exit $proc.ExitCode + } + + Remove-Item -Recurse -Force extracted, opencl-installer.exe + + - name: Register Intel OpenCL ICD (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + $dllPath = "C:\Program Files (x86)\Common Files\Intel\Shared Libraries\bin\OpenCL.dll" + REG ADD "HKLM\SOFTWARE\Khronos\OpenCL\Vendors" /v $dllPath /t REG_DWORD /d 0 /f + REG ADD "HKLM\SOFTWARE\Wow6432Node\Khronos\OpenCL\Vendors" /v $dllPath /t REG_DWORD /d 0 /f + Add-Content $env:GITHUB_PATH "C:\Program Files (x86)\Common Files\Intel\Shared Libraries\bin\" + + - name: Debug OpenCL paths + if: runner.os == 'Windows' + shell: pwsh + run: | + echo "OPENCL_CPPFLAGS: $env:OPENCL_CPPFLAGS" + echo "OPENCL_LIBS: $env:OPENCL_LIBS" + ls "$env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\include\CL" + - uses: r-lib/actions/setup-r-dependencies@v2 with: extra-packages: any::rcmdcheck diff --git a/configure.win b/configure.win new file mode 100755 index 0000000..63aa438 --- /dev/null +++ b/configure.win @@ -0,0 +1,184 @@ +#!/bin/sh +## +## RcppBandicoot configure.win +## +## Windows-specific configuration script +## Detects OpenCL and CLBlast from environment variables +## +## Copyright (C) 2023-2025 James Balamuta +## Licensed under GPL-2 or later +## + +echo "Configuring RcppBandicoot for Windows..." + +## Get R_HOME +: ${R_HOME=$(R RHOME)} +if test -z "${R_HOME}"; then + echo "ERROR: Could not determine R_HOME" + exit 1 +fi + +## Default values +BANDICOOT_CXXFLAGS="" +BANDICOOT_LIBS="" +OPENMP_CXXFLAGS='$(SHLIB_OPENMP_CXXFLAGS)' +OPENCL_TARGET_VERSION=300 + +## Kernel source directory (matches configure.ac logic) +BANDICOOT_KERNELS_DIR=$("${R_HOME}/bin/Rscript" -e 'cat(paste(head(.libPaths(),1), "RcppBandicoot", "include", "bandicoot_bits", "ks", "", sep="/"))') + +## Always disable CUDA on Windows (requires manual setup) +BANDICOOT_CXXFLAGS="${BANDICOOT_CXXFLAGS} -DCOOT_DONT_USE_CUDA" + +## Check for OpenCL headers via environment variable +if [ -n "${OPENCL_CPPFLAGS}" ]; then + echo " OpenCL headers: found (via OPENCL_CPPFLAGS)" + BANDICOOT_CXXFLAGS="${BANDICOOT_CXXFLAGS} -DCOOT_USE_OPENCL ${OPENCL_CPPFLAGS}" + HAVE_OPENCL=1 +else + echo " OpenCL headers: not found" + echo " Set OPENCL_CPPFLAGS to specify OpenCL header location" + HAVE_OPENCL=0 +fi + +## Check for OpenCL library +if [ -n "${OPENCL_LIBS}" ]; then + echo " OpenCL library: found (via OPENCL_LIBS): ${OPENCL_LIBS}" + BANDICOOT_LIBS="${BANDICOOT_LIBS} ${OPENCL_LIBS}" +else + echo " OpenCL library: not found (OPENCL_LIBS not set)" + echo " WARNING: Linking will fail without OpenCL library!" +fi + +## Check for CLBlast via environment variable +if [ -n "${CLBLAST_CPPFLAGS}" ]; then + echo " CLBlast: found (via CLBLAST_CPPFLAGS)" + BANDICOOT_CXXFLAGS="${BANDICOOT_CXXFLAGS} -DCOOT_USE_CLBLAST ${CLBLAST_CPPFLAGS}" + HAVE_CLBLAST=1 +else + echo " CLBlast: not found, disabling" + BANDICOOT_CXXFLAGS="${BANDICOOT_CXXFLAGS} -DCOOT_DONT_USE_CLBLAST" + HAVE_CLBLAST=0 +fi + +## Check for CLBlast library +if [ -n "${CLBLAST_LIBS}" ]; then + echo " CLBlast library: ${CLBLAST_LIBS}" + BANDICOOT_LIBS="${BANDICOOT_LIBS} ${CLBLAST_LIBS}" +fi + +## Always disable clBLAS (CLBlast is preferred) +BANDICOOT_CXXFLAGS="${BANDICOOT_CXXFLAGS} -DCOOT_DONT_USE_CLBLAS" +HAVE_CLBLAS=0 + +## Add common flags +BANDICOOT_CXXFLAGS="${OPENMP_CXXFLAGS} ${BANDICOOT_CXXFLAGS}" + +## Add R's LAPACK/BLAS +BANDICOOT_LIBS="${BANDICOOT_LIBS} \$(LAPACK_LIBS) \$(BLAS_LIBS) \$(FLIBS)" + +## Check if we have at least OpenCL +if [ "${HAVE_OPENCL}" = "0" ]; then + echo "" + echo "WARNING: No GPU backend detected!" + echo "" + echo "RcppBandicoot requires OpenCL headers to compile on Windows." + echo "Set the following environment variables before building:" + echo "" + echo " OPENCL_CPPFLAGS - Path to OpenCL headers (e.g., -IC:/OpenCL/include)" + echo " OPENCL_LIBS - OpenCL library flags (e.g., -LC:/OpenCL/lib -lOpenCL)" + echo " CLBLAST_CPPFLAGS - Path to CLBlast headers (optional)" + echo " CLBLAST_LIBS - CLBlast library flags (optional)" + echo "" +fi + +echo "" +echo "Configuration Summary:" +echo " OpenCL: ${HAVE_OPENCL}" +echo " CLBlast: ${HAVE_CLBLAST}" +echo " CUDA: disabled (Windows)" +echo " Kernels: ${BANDICOOT_KERNELS_DIR}" +echo "" + +## Generate Makevars.win from template +if [ -f "src/Makevars.win.in" ]; then + sed -e "s|@BANDICOOT_CXXFLAGS@|${BANDICOOT_CXXFLAGS}|g" \ + -e "s|@BANDICOOT_LIBS@|${BANDICOOT_LIBS}|g" \ + -e "s|@OPENMP_CXXFLAGS@|${OPENMP_CXXFLAGS}|g" \ + -e "s|@OPENCL_TARGET_VERSION@|${OPENCL_TARGET_VERSION}|g" \ + -e "s|@BANDICOOT_KERNELS_DIR@|${BANDICOOT_KERNELS_DIR}|g" \ + src/Makevars.win.in > src/Makevars.win + echo "Generated src/Makevars.win" +else + echo "ERROR: src/Makevars.win.in not found!" + exit 1 +fi + +## Verify the generated file +if [ -f "src/Makevars.win" ]; then + echo "Contents of src/Makevars.win:" + cat src/Makevars.win +else + echo "ERROR: Failed to generate src/Makevars.win!" + exit 1 +fi + +## Generate R/flags.R from template +## Set variables for flags.R.in substitution +HAVE_CUDA=0 +HAVE_OPENMP=1 +DEFAULT_BACKEND="CL_BACKEND" +OPENCL_CXXFLAGS_FOR_R="" +OPENCL_LIBS_FOR_R="" +CUDA_CXXFLAGS="" +CUDA_LIBS="" +CLBLAST_CXXFLAGS_FOR_R="" +CLBLAST_LIBS_FOR_R="" +CLBLAS_CXXFLAGS="" +CLBLAS_LIBS="" +LAPACK_BLAS_LIBS="\$(LAPACK_LIBS) \$(BLAS_LIBS) \$(FLIBS)" +CLBLAST_PREFIX="" +CLBLAS_PREFIX="" +CUDA_HOME="" +SDKPATH="" + +## Set OpenCL flags for R if detected +if [ "${HAVE_OPENCL}" = "1" ]; then + OPENCL_CXXFLAGS_FOR_R="-DCOOT_USE_OPENCL ${OPENCL_CPPFLAGS}" + OPENCL_LIBS_FOR_R="${OPENCL_LIBS}" +fi + +## Set CLBlast flags for R if detected +if [ "${HAVE_CLBLAST}" = "1" ]; then + CLBLAST_CXXFLAGS_FOR_R="-DCOOT_USE_CLBLAST ${CLBLAST_CPPFLAGS}" + CLBLAST_LIBS_FOR_R="${CLBLAST_LIBS}" +fi + +if [ -f "R/flags.R.in" ]; then + sed -e "s|@BANDICOOT_CXXFLAGS@|${BANDICOOT_CXXFLAGS}|g" \ + -e "s|@BANDICOOT_LIBS@|${BANDICOOT_LIBS}|g" \ + -e "s|@OPENCL_CXXFLAGS@|${OPENCL_CXXFLAGS_FOR_R}|g" \ + -e "s|@OPENCL_LIBS@|${OPENCL_LIBS_FOR_R}|g" \ + -e "s|@CUDA_CXXFLAGS@|${CUDA_CXXFLAGS}|g" \ + -e "s|@CUDA_LIBS@|${CUDA_LIBS}|g" \ + -e "s|@CLBLAST_CXXFLAGS@|${CLBLAST_CXXFLAGS_FOR_R}|g" \ + -e "s|@CLBLAST_LIBS@|${CLBLAST_LIBS_FOR_R}|g" \ + -e "s|@CLBLAS_CXXFLAGS@|${CLBLAS_CXXFLAGS}|g" \ + -e "s|@CLBLAS_LIBS@|${CLBLAS_LIBS}|g" \ + -e "s|@LAPACK_BLAS_LIBS@|${LAPACK_BLAS_LIBS}|g" \ + -e "s|@HAVE_OPENCL@|${HAVE_OPENCL}|g" \ + -e "s|@HAVE_CUDA@|${HAVE_CUDA}|g" \ + -e "s|@HAVE_CLBLAST@|${HAVE_CLBLAST}|g" \ + -e "s|@HAVE_CLBLAS@|${HAVE_CLBLAS}|g" \ + -e "s|@HAVE_OPENMP@|${HAVE_OPENMP}|g" \ + -e "s|@DEFAULT_BACKEND@|${DEFAULT_BACKEND}|g" \ + -e "s|@CLBLAST_PREFIX@|${CLBLAST_PREFIX}|g" \ + -e "s|@CLBLAS_PREFIX@|${CLBLAS_PREFIX}|g" \ + -e "s|@CUDA_HOME@|${CUDA_HOME}|g" \ + -e "s|@SDKPATH@|${SDKPATH}|g" \ + R/flags.R.in > R/flags.R + echo "Generated R/flags.R" +else + echo "ERROR: R/flags.R.in not found!" + exit 1 +fi diff --git a/src/.gitignore b/src/.gitignore index 22034c4..5948b32 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,3 +1,5 @@ *.o *.so *.dll +Makevars +Makevars.win diff --git a/src/Makevars.win b/src/Makevars.win deleted file mode 100644 index 667e3f0..0000000 --- a/src/Makevars.win +++ /dev/null @@ -1,11 +0,0 @@ -## RcppBandicoot Makevars.win -## -## Windows-specific Makevars -## GPU backend configuration must be done manually on Windows - -PKG_CPPFLAGS = -I../inst/include - -## Warning: GPU backends may not be properly configured -## Users may need to manually specify OpenCL or CUDA paths -PKG_CXXFLAGS = $(SHLIB_OPENMP_CXXFLAGS) -Wno-missing-braces -PKG_LIBS = $(SHLIB_OPENMP_CXXFLAGS) $(LAPACK_LIBS) $(BLAS_LIBS) $(FLIBS) diff --git a/src/Makevars.win.in b/src/Makevars.win.in new file mode 100644 index 0000000..ccdda30 --- /dev/null +++ b/src/Makevars.win.in @@ -0,0 +1,12 @@ +## RcppBandicoot Makevars.win.in +## +## This file is processed by configure.win to generate Makevars.win +## It includes GPU backend configuration (OpenCL, CLBlast) for Windows + +PKG_CPPFLAGS = -I../inst/include -DCOOT_TARGET_OPENCL_VERSION=@OPENCL_TARGET_VERSION@ -DCOOT_KERNEL_SOURCE_DIR=\"@BANDICOOT_KERNELS_DIR@\" + +## Compiler flags from configure.win +PKG_CXXFLAGS = @BANDICOOT_CXXFLAGS@ + +## Linker flags from configure.win +PKG_LIBS = @OPENMP_CXXFLAGS@ @BANDICOOT_LIBS@