From 6864e0524c52bc15fc15c9950381e88078f2938f Mon Sep 17 00:00:00 2001 From: Chanho Lee Date: Tue, 8 Sep 2026 11:43:44 +0900 Subject: [PATCH 1/4] build: add Highway source installation Add optional Highway download, checksum verification, extraction, and header installation tests using the existing dependency Make rules. Keep Highway out of the default development dependency installation. Verified a fresh parallel installation and rejection of an invalid checksum before extraction. Existing DSUMPW package tests pass. --- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown_pkg_readmes status: na - task: lint_markdown_docs status: passed - task: lint_markdown status: passed - task: lint_package_json status: na - task: lint_repl_help status: na - task: lint_javascript_src status: na - task: lint_javascript_cli status: na - task: lint_javascript_examples status: na - task: lint_javascript_tests status: na - task: lint_javascript_benchmarks status: na - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: na - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: na - task: lint_typescript_declarations status: passed - task: lint_typescript_tests status: na - task: lint_license_headers status: passed --- --- deps/checksums/highway_1_4_0_tar_gz/sha256 | 1 + deps/test/highway/test_install.cpp | 45 ++++++ docs/contributing/development.md | 3 + docs/links/database.json | 14 ++ tools/make/common.mk | 11 ++ tools/make/lib/install/Makefile | 5 +- tools/make/lib/install/README.md | 35 +++++ tools/make/lib/install/highway.mk | 170 +++++++++++++++++++++ 8 files changed, 282 insertions(+), 2 deletions(-) create mode 100644 deps/checksums/highway_1_4_0_tar_gz/sha256 create mode 100644 deps/test/highway/test_install.cpp create mode 100644 tools/make/lib/install/highway.mk diff --git a/deps/checksums/highway_1_4_0_tar_gz/sha256 b/deps/checksums/highway_1_4_0_tar_gz/sha256 new file mode 100644 index 000000000000..403ebf8232b9 --- /dev/null +++ b/deps/checksums/highway_1_4_0_tar_gz/sha256 @@ -0,0 +1 @@ +e72241ac9524bb653ae52ced768b508045d4438726a303f10181a38f764a453c diff --git a/deps/test/highway/test_install.cpp b/deps/test/highway/test_install.cpp new file mode 100644 index 000000000000..ea6ed09c517f --- /dev/null +++ b/deps/test/highway/test_install.cpp @@ -0,0 +1,45 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2026 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "hwy/highway.h" + +HWY_BEFORE_NAMESPACE(); + +namespace hwy { + namespace HWY_NAMESPACE { + + int test_install( void ) { +#if HWY_HAVE_FLOAT64 + using T = double; +#else + using T = float; +#endif + const ScalableTag tag; + const auto value = Set( tag, static_cast( 1.0 ) ); + + return ( GetLane( value ) == static_cast( 1.0 ) ) ? 0 : 1; + } + + } // namespace HWY_NAMESPACE +} // namespace hwy + +HWY_AFTER_NAMESPACE(); + +int main( void ) { + return hwy::HWY_NAMESPACE::test_install(); +} diff --git a/docs/contributing/development.md b/docs/contributing/development.md index 540f31088fc6..820a190b58c2 100644 --- a/docs/contributing/development.md +++ b/docs/contributing/development.md @@ -78,6 +78,7 @@ The following external libraries can be automatically downloaded and compiled fr - [Boost][boost]: portable C++ libraries - [Cephes][cephes]: C/C++ special functions math library +- [Highway][highway]: performance-portable SIMD library - [OpenBLAS][openblas]: optimized BLAS library - [Electron][electron]: framework for cross-platform desktop applications - [Emscripten][emscripten]: LLVM to JavaScript compiler @@ -406,6 +407,8 @@ For contribution guidelines, see the [contributing guide][stdlib-contributing]. [cephes]: http://www.moshier.net/#Cephes +[highway]: https://github.com/google/highway + [openblas]: https://github.com/xianyi/OpenBLAS [electron]: https://www.electronjs.org/ diff --git a/docs/links/database.json b/docs/links/database.json index 034d2282616f..fb3099855ba3 100644 --- a/docs/links/database.json +++ b/docs/links/database.json @@ -8274,6 +8274,20 @@ "linter" ] }, + "https://github.com/google/highway": { + "id": "highway", + "description": "Highway is a C++ library for performance-portable SIMD with runtime dispatch.", + "short_url": "", + "keywords": [ + "highway", + "simd", + "vectorization", + "intrinsics", + "c++", + "runtime", + "dispatch" + ] + }, "https://github.com/gotwarlost/istanbul": { "id": "istanbul", "description": "Istanbul is a JavaScript code coverage tool.", diff --git a/tools/make/common.mk b/tools/make/common.mk index f570982f637e..9ba6ae536119 100644 --- a/tools/make/common.mk +++ b/tools/make/common.mk @@ -699,3 +699,14 @@ deps_fftpack_version_slug := $(subst .,_,$(DEPS_FFTPACK_VERSION)) # Define the output path when building FFTPACK: DEPS_FFTPACK_BUILD_OUT ?= $(DEPS_BUILD_DIR)/pffft-$(DEPS_FFTPACK_VERSION) + +# Highway... + +# Define the Highway version: +DEPS_HIGHWAY_VERSION ?= 1.4.0 + +# Define the output path when building Highway: +DEPS_HIGHWAY_BUILD_OUT ?= $(DEPS_BUILD_DIR)/highway-$(DEPS_HIGHWAY_VERSION) + +# Define the path to the Highway include directory: +DEPS_HIGHWAY_INCLUDE ?= $(DEPS_HIGHWAY_BUILD_OUT) diff --git a/tools/make/lib/install/Makefile b/tools/make/lib/install/Makefile index be8363f97b9a..2d8cf6a08dcb 100644 --- a/tools/make/lib/install/Makefile +++ b/tools/make/lib/install/Makefile @@ -40,6 +40,7 @@ include $(TOOLS_MAKE_LIB_DIR)/install/cppcheck.mk include $(TOOLS_MAKE_LIB_DIR)/install/electron.mk include $(TOOLS_MAKE_LIB_DIR)/install/emsdk.mk include $(TOOLS_MAKE_LIB_DIR)/install/fftpack.mk +include $(TOOLS_MAKE_LIB_DIR)/install/highway.mk include $(TOOLS_MAKE_LIB_DIR)/install/llvm.mk include $(TOOLS_MAKE_LIB_DIR)/install/node.mk include $(TOOLS_MAKE_LIB_DIR)/install/openblas.mk @@ -168,7 +169,7 @@ install-deps-dev: install-deps-boost install-deps-cephes install-deps-cppcheck i # @example # make clean-deps-dev #/ -clean-deps-dev: clean-deps-boost clean-deps-cephes clean-deps-cppcheck clean-deps-fftpack clean-deps-python clean-deps-r clean-deps-shellcheck +clean-deps-dev: clean-deps-boost clean-deps-cephes clean-deps-cppcheck clean-deps-fftpack clean-deps-highway clean-deps-python clean-deps-r clean-deps-shellcheck .PHONY: clean-deps-dev @@ -178,7 +179,7 @@ clean-deps-dev: clean-deps-boost clean-deps-cephes clean-deps-cppcheck clean-dep # @example # make clean-deps-dev-tests #/ -clean-deps-dev-tests: clean-deps-boost-tests clean-deps-cephes-tests clean-deps-cppcheck-tests clean-deps-fftpack-tests clean-deps-shellcheck-tests +clean-deps-dev-tests: clean-deps-boost-tests clean-deps-cephes-tests clean-deps-cppcheck-tests clean-deps-fftpack-tests clean-deps-highway-tests clean-deps-shellcheck-tests .PHONY: clean-deps-dev-tests diff --git a/tools/make/lib/install/README.md b/tools/make/lib/install/README.md index 87c8c37d14ef..99b1384b86db 100644 --- a/tools/make/lib/install/README.md +++ b/tools/make/lib/install/README.md @@ -37,6 +37,7 @@ This directory contains [`make`][make] rules for running the project's installat - [Cppcheck](#cppcheck) - [Electron](#electron) - [Emscripten SDK](#emscripten-sdk) + - [Highway](#highway) - [LLVM](#llvm) - [OpenBLAS](#openblas) - [Python](#python) @@ -421,6 +422,38 @@ $ make clean-deps-emscripten-tests * * * + + +### Highway + +#### install-deps-highway + +Installs [Highway][highway]. + +This optional installation downloads the source distribution and runs installation tests. It requires a C++17-capable compiler; `install-deps-dev` does not install Highway. + +```bash +$ make install-deps-highway +``` + +#### clean-deps-highway + +Removes an installed [Highway][highway] distribution. + +```bash +$ make clean-deps-highway +``` + +#### clean-deps-highway-tests + +Removes compiled [Highway][highway] installation tests. + +```bash +$ make clean-deps-highway-tests +``` + +* * * + ### LLVM @@ -657,6 +690,8 @@ $ make clean-deps-wasi-libc-tests [emscripten-sdk]: https://github.com/emscripten-core/emsdk +[highway]: https://github.com/google/highway + [llvm]: https://llvm.org [node-js]: https://nodejs.org/en/ diff --git a/tools/make/lib/install/highway.mk b/tools/make/lib/install/highway.mk new file mode 100644 index 000000000000..24ed4cb2c7d6 --- /dev/null +++ b/tools/make/lib/install/highway.mk @@ -0,0 +1,170 @@ +#/ +# @license Apache-2.0 +# +# Copyright (c) 2026 The Stdlib Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#/ + +# VARIABLES # + +# Define the download URL: +DEPS_HIGHWAY_URL ?= https://codeload.github.com/google/highway/tar.gz/refs/tags/$(DEPS_HIGHWAY_VERSION) + +# Determine the basename for the download: +deps_highway_basename := highway-$(DEPS_HIGHWAY_VERSION).tar.gz + +# Define the path to the file containing a checksum to verify a download: +DEPS_HIGHWAY_CHECKSUM ?= $(shell $(CAT) $(DEPS_CHECKSUMS_DIR)/$(subst .,_,$(subst -,_,$(deps_highway_basename)))/sha256) + +# Define the output path when downloading: +DEPS_HIGHWAY_DOWNLOAD_OUT ?= $(DEPS_TMP_DIR)/$(deps_highway_basename) + +# Define the path to the directory containing tests: +DEPS_HIGHWAY_TEST_DIR ?= $(DEPS_DIR)/test/highway + +# Define the output directory path for compiled tests: +DEPS_HIGHWAY_TEST_OUT ?= $(DEPS_HIGHWAY_TEST_DIR)/build + +# Define the path to a test file for checking an installation: +DEPS_HIGHWAY_TEST_INSTALL ?= $(DEPS_HIGHWAY_TEST_DIR)/test_install.cpp + +# Define the output path for a test file: +DEPS_HIGHWAY_TEST_INSTALL_OUT ?= $(DEPS_HIGHWAY_TEST_OUT)/test_install + + +# RULES # + +#/ +# Downloads a Highway distribution. +# +# @private +#/ +$(DEPS_HIGHWAY_DOWNLOAD_OUT): | $(DEPS_TMP_DIR) + $(QUIET) echo 'Downloading Highway...' >&2 + $(QUIET) $(DEPS_DOWNLOAD_BIN) $(DEPS_HIGHWAY_URL) $(DEPS_HIGHWAY_DOWNLOAD_OUT) + +#/ +# Extracts a Highway gzipped tar archive. +# +# @private +#/ +$(DEPS_HIGHWAY_BUILD_OUT): $(DEPS_HIGHWAY_DOWNLOAD_OUT) | deps-verify-highway $(DEPS_BUILD_DIR) + $(QUIET) echo 'Extracting Highway...' >&2 + $(QUIET) $(TAR) -zxf $(DEPS_HIGHWAY_DOWNLOAD_OUT) -C $(DEPS_BUILD_DIR) + +#/ +# Creates a directory for storing compiled tests. +# +# @private +#/ +$(DEPS_HIGHWAY_TEST_OUT): + $(QUIET) $(MKDIR_RECURSIVE) $(DEPS_HIGHWAY_TEST_OUT) + +#/ +# Compiles a test file for testing a Highway installation. +# +# @private +#/ +$(DEPS_HIGHWAY_TEST_INSTALL_OUT): $(DEPS_HIGHWAY_BUILD_OUT) $(DEPS_HIGHWAY_TEST_INSTALL) | $(DEPS_HIGHWAY_TEST_OUT) + $(QUIET) $(CXX) -std=c++17 -I $(DEPS_HIGHWAY_INCLUDE) $(DEPS_HIGHWAY_TEST_INSTALL) -o $(DEPS_HIGHWAY_TEST_INSTALL_OUT) + +#/ +# Downloads a Highway distribution. +# +# @private +# +# @example +# make deps-download-highway +#/ +deps-download-highway: $(DEPS_HIGHWAY_DOWNLOAD_OUT) + +.PHONY: deps-download-highway + +#/ +# Verifies a downloaded Highway distribution. +# +# @private +# +# @example +# make deps-verify-highway +#/ +deps-verify-highway: deps-download-highway + $(QUIET) echo 'Verifying download...' >&2 + $(QUIET) $(DEPS_CHECKSUM_BIN) $(DEPS_HIGHWAY_DOWNLOAD_OUT) $(DEPS_HIGHWAY_CHECKSUM) >&2 + +.PHONY: deps-verify-highway + +#/ +# Extracts a downloaded Highway distribution. +# +# @private +# +# @example +# make deps-extract-highway +#/ +deps-extract-highway: $(DEPS_HIGHWAY_BUILD_OUT) + +.PHONY: deps-extract-highway + +#/ +# Tests an installed Highway distribution. +# +# @private +# +# @example +# make deps-test-highway +#/ +deps-test-highway: $(DEPS_HIGHWAY_TEST_INSTALL_OUT) + $(QUIET) echo 'Running tests...' >&2 + $(QUIET) $(DEPS_HIGHWAY_TEST_INSTALL_OUT) + $(QUIET) echo '' >&2 + $(QUIET) echo 'Success.' >&2 + +.PHONY: deps-test-highway + +#/ +# Installs Highway. +# +# @example +# make install-deps-highway +#/ +install-deps-highway: deps-download-highway deps-verify-highway deps-extract-highway deps-test-highway + +.PHONY: install-deps-highway + +#/ +# Removes an installed Highway distribution. +# +# ## Notes +# +# - The rule does **not** remove a Highway download (if one exists). +# +# @example +# make clean-deps-highway +#/ +clean-deps-highway: clean-deps-highway-tests + $(QUIET) $(DELETE) $(DELETE_FLAGS) $(DEPS_HIGHWAY_BUILD_OUT) + +.PHONY: clean-deps-highway + +#/ +# Removes compiled Highway installation tests. +# +# @example +# make clean-deps-highway-tests +#/ +clean-deps-highway-tests: + $(QUIET) $(DELETE) $(DELETE_FLAGS) $(DEPS_HIGHWAY_TEST_OUT) + +.PHONY: clean-deps-highway-tests From e2d8d34597f96db90738d0846072f86c40aa6a80 Mon Sep 17 00:00:00 2001 From: Chanho Lee Date: Tue, 8 Sep 2026 11:45:00 +0900 Subject: [PATCH 2/4] build: build the Highway runtime library Build the upstream hwy target as a reusable static library and export its headers, compile definitions, and link dependencies as metadata. Add runtime linkage and metadata checks alongside installation docs. Verified fresh GCC builds with and without libatomic, CTest, seven metadata assertions, and filename lint tests. --- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown_pkg_readmes status: na - task: lint_markdown_docs status: na - task: lint_markdown status: passed - task: lint_package_json status: na - task: lint_repl_help status: na - task: lint_javascript_src status: na - task: lint_javascript_cli status: na - task: lint_javascript_examples status: na - task: lint_javascript_tests status: passed - task: lint_javascript_benchmarks status: na - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: na - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: na - task: lint_typescript_declarations status: passed - task: lint_typescript_tests status: na - task: lint_license_headers status: passed --- --- deps/test/highway/test_runtime.cpp | 23 ++++++ .../_tools/lint/filenames/lib/special.json | 1 + .../_tools/lint/filenames/test/test.lint.js | 17 ++++ tools/make/common.mk | 3 + tools/make/lib/install/README.md | 30 ++++++- tools/make/lib/install/highway.mk | 49 +++++++++++- tools/make/lib/install/highway/CMakeLists.txt | 70 ++++++++++++++++ tools/make/test/test.highway.js | 79 +++++++++++++++++++ 8 files changed, 270 insertions(+), 2 deletions(-) create mode 100644 deps/test/highway/test_runtime.cpp create mode 100644 tools/make/lib/install/highway/CMakeLists.txt create mode 100644 tools/make/test/test.highway.js diff --git a/deps/test/highway/test_runtime.cpp b/deps/test/highway/test_runtime.cpp new file mode 100644 index 000000000000..4a940c90eb06 --- /dev/null +++ b/deps/test/highway/test_runtime.cpp @@ -0,0 +1,23 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2026 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "hwy/targets.h" + +int main( void ) { + return hwy::SupportedAndGeneratedTargets().empty() ? 1 : 0; +} diff --git a/lib/node_modules/@stdlib/_tools/lint/filenames/lib/special.json b/lib/node_modules/@stdlib/_tools/lint/filenames/lib/special.json index f3e7978f1229..b6f38dec3b4b 100644 --- a/lib/node_modules/@stdlib/_tools/lint/filenames/lib/special.json +++ b/lib/node_modules/@stdlib/_tools/lint/filenames/lib/special.json @@ -2,6 +2,7 @@ "license": "LICENSE", "notice": "NOTICE", "makefile": "Makefile", + "cmakelists": "CMakeLists.txt", "description": "DESCRIPTION", "require": "REQUIRE", "authors": "AUTHORS", diff --git a/lib/node_modules/@stdlib/_tools/lint/filenames/test/test.lint.js b/lib/node_modules/@stdlib/_tools/lint/filenames/test/test.lint.js index 3b740f1f438a..bfc164803ba4 100644 --- a/lib/node_modules/@stdlib/_tools/lint/filenames/test/test.lint.js +++ b/lib/node_modules/@stdlib/_tools/lint/filenames/test/test.lint.js @@ -48,6 +48,22 @@ tape( 'the function requires `Makefile` to be spelled exactly', function test( t t.end(); }); +tape( 'the function requires `CMakeLists.txt` to be spelled exactly', function test( t ) { + var values; + var names; + + values = [ + 'cmakelists.txt', + 'CMAKELISTS.txt', + 'CMakeLists.cmake' + ]; + + names = lint( values ); + + t.strictEqual( names.length, values.length, 'returns error messages' ); + t.end(); +}); + tape( 'the function requires `LICENSE` to be spelled exactly', function test( t ) { var values; var names; @@ -150,6 +166,7 @@ tape( 'the function returns an empty array if all filenames are valid', function 'data.data.txt', 'README.md', 'Makefile', + 'CMakeLists.txt', 'DESCRIPTION', 'LICENSE', 'CITATION.cff' diff --git a/tools/make/common.mk b/tools/make/common.mk index 9ba6ae536119..5dae0f9362f7 100644 --- a/tools/make/common.mk +++ b/tools/make/common.mk @@ -710,3 +710,6 @@ DEPS_HIGHWAY_BUILD_OUT ?= $(DEPS_BUILD_DIR)/highway-$(DEPS_HIGHWAY_VERSION) # Define the path to the Highway include directory: DEPS_HIGHWAY_INCLUDE ?= $(DEPS_HIGHWAY_BUILD_OUT) + +# Define the native Highway build directory: +DEPS_HIGHWAY_RUNTIME_OUT ?= $(DEPS_HIGHWAY_BUILD_OUT)/build diff --git a/tools/make/lib/install/README.md b/tools/make/lib/install/README.md index 99b1384b86db..560c40d90098 100644 --- a/tools/make/lib/install/README.md +++ b/tools/make/lib/install/README.md @@ -430,12 +430,40 @@ $ make clean-deps-emscripten-tests Installs [Highway][highway]. -This optional installation downloads the source distribution and runs installation tests. It requires a C++17-capable compiler; `install-deps-dev` does not install Highway. +This optional installation downloads the source distribution, builds the native runtime, and runs installation tests. It requires CMake 3.10 or newer and a C++17-capable compiler; `install-deps-dev` does not install Highway. ```bash $ make install-deps-highway ``` +#### deps-build-highway + +Builds Highway's `hwy` target as a static library and tests runtime linkage. Highway's test suite, examples, and contrib libraries are not built. + +```bash +$ make deps-build-highway +``` + +The build directory defaults to `DEPS_HIGHWAY_BUILD_OUT/build` and can be overridden with `DEPS_HIGHWAY_RUNTIME_OUT`. CMake maintains the incremental build there and writes `highway.json`, which records the headers, public compile definitions, static library, and additional link dependencies needed by add-ons. + +Use `DEPS_HIGHWAY_BUILD_TYPE=Debug` for a debug build; `Release` is the default. Use a fresh build directory when changing compilers or toolchain flags, and separate directories when keeping multiple configurations. CMake caches toolchain checks; do not configure the same directory concurrently. + +Compiled tests default to `deps/test/highway/build`. When keeping multiple runtime builds, set `DEPS_HIGHWAY_TEST_OUT` to a separate absolute directory for each build to avoid overwriting test executables. Reuse the same runtime and test output directory pair on subsequent builds. + +```bash +$ make deps-build-highway DEPS_HIGHWAY_BUILD_TYPE=Debug DEPS_HIGHWAY_RUNTIME_OUT=/path/to/highway-debug DEPS_HIGHWAY_TEST_OUT=/path/to/stdlib/deps/test/highway/build/debug +``` + +For an alternate toolchain, pass `C_COMPILER` and `CXX_COMPILER` to the build command. + +#### deps-test-highway-build + +Builds and tests the runtime, then runs JS/Tape checks for generated link metadata through the project's JavaScript test runner. + +```bash +$ make deps-test-highway-build +``` + #### clean-deps-highway Removes an installed [Highway][highway] distribution. diff --git a/tools/make/lib/install/highway.mk b/tools/make/lib/install/highway.mk index 24ed4cb2c7d6..a19323f50346 100644 --- a/tools/make/lib/install/highway.mk +++ b/tools/make/lib/install/highway.mk @@ -42,6 +42,12 @@ DEPS_HIGHWAY_TEST_INSTALL ?= $(DEPS_HIGHWAY_TEST_DIR)/test_install.cpp # Define the output path for a test file: DEPS_HIGHWAY_TEST_INSTALL_OUT ?= $(DEPS_HIGHWAY_TEST_OUT)/test_install +# Define the command for running CMake tests: +CTEST ?= ctest + +# Define the native runtime build configuration: +DEPS_HIGHWAY_BUILD_TYPE ?= Release + # RULES # @@ -139,10 +145,37 @@ deps-test-highway: $(DEPS_HIGHWAY_TEST_INSTALL_OUT) # @example # make install-deps-highway #/ -install-deps-highway: deps-download-highway deps-verify-highway deps-extract-highway deps-test-highway +install-deps-highway: deps-download-highway deps-verify-highway deps-extract-highway deps-test-highway deps-build-highway .PHONY: install-deps-highway +#/ +# Builds and tests the native Highway runtime. +# +# @example +# make deps-build-highway +#/ +deps-build-highway: CFLAGS ?= +deps-build-highway: CXXFLAGS ?= +deps-build-highway: LDFLAGS ?= +deps-build-highway: $(DEPS_HIGHWAY_BUILD_OUT) | $(DEPS_HIGHWAY_TEST_OUT) + $(QUIET) $(MKDIR_RECURSIVE) $(DEPS_HIGHWAY_RUNTIME_OUT) + $(QUIET) cd $(DEPS_HIGHWAY_RUNTIME_OUT) && $(CMAKE) \ + -DHIGHWAY_SOURCE="$(DEPS_HIGHWAY_INCLUDE)" \ + -DHIGHWAY_TEST_SOURCE="$(DEPS_HIGHWAY_TEST_DIR)/test_runtime.cpp" \ + -DHIGHWAY_TEST_OUT="$(DEPS_HIGHWAY_TEST_OUT)" \ + -DCMAKE_BUILD_TYPE="$(DEPS_HIGHWAY_BUILD_TYPE)" \ + -DCMAKE_C_COMPILER="$(CC)" \ + -DCMAKE_CXX_COMPILER="$(CXX)" \ + -DCMAKE_C_FLAGS="$(CFLAGS)" \ + -DCMAKE_CXX_FLAGS="$(CXXFLAGS)" \ + -DCMAKE_EXE_LINKER_FLAGS="$(LDFLAGS)" \ + $(TOOLS_DIR)/make/lib/install/highway + $(QUIET) MAKEFLAGS= MFLAGS= $(CMAKE) --build $(DEPS_HIGHWAY_RUNTIME_OUT) --config $(DEPS_HIGHWAY_BUILD_TYPE) --target stdlib_highway_test + $(QUIET) cd $(DEPS_HIGHWAY_RUNTIME_OUT) && $(CTEST) -C $(DEPS_HIGHWAY_BUILD_TYPE) --output-on-failure + +.PHONY: deps-build-highway + #/ # Removes an installed Highway distribution. # @@ -168,3 +201,17 @@ clean-deps-highway-tests: $(QUIET) $(DELETE) $(DELETE_FLAGS) $(DEPS_HIGHWAY_TEST_OUT) .PHONY: clean-deps-highway-tests + +#/ +# Tests the native Highway build metadata. +# +# @example +# make deps-test-highway-build +#/ +deps-test-highway-build: deps-build-highway + $(QUIET) STDLIB_TEST_HIGHWAY_RUNTIME="$(DEPS_HIGHWAY_RUNTIME_OUT)" \ + STDLIB_TEST_HIGHWAY_SOURCE="$(DEPS_HIGHWAY_INCLUDE)" \ + FILES="$(TOOLS_DIR)/make/test/test.highway.js" \ + $(MAKE) -f $(this_file) test-javascript-files + +.PHONY: deps-test-highway-build diff --git a/tools/make/lib/install/highway/CMakeLists.txt b/tools/make/lib/install/highway/CMakeLists.txt new file mode 100644 index 000000000000..cb439dedec96 --- /dev/null +++ b/tools/make/lib/install/highway/CMakeLists.txt @@ -0,0 +1,70 @@ +# @license Apache-2.0 +# +# Copyright (c) 2026 The Stdlib Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.10) +project(stdlib_highway LANGUAGES C CXX) + +if(NOT EXISTS "${HIGHWAY_SOURCE}/hwy/highway.h") + message(FATAL_ERROR "HIGHWAY_SOURCE must refer to a Highway source distribution") +endif() +if(NOT CMAKE_BUILD_TYPE MATCHES "^(Debug|Release)$") + message(FATAL_ERROR "CMAKE_BUILD_TYPE must be Debug or Release") +endif() + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(CMAKE_CXX_VISIBILITY_PRESET hidden) +set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) +set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) +set(HWY_ENABLE_CONTRIB OFF CACHE BOOL "" FORCE) +set(HWY_ENABLE_EXAMPLES OFF CACHE BOOL "" FORCE) +set(HWY_ENABLE_TESTS OFF CACHE BOOL "" FORCE) +set(HWY_ENABLE_INSTALL OFF CACHE BOOL "" FORCE) + +add_subdirectory("${HIGHWAY_SOURCE}" highway) +add_executable(stdlib_highway_test "${HIGHWAY_TEST_SOURCE}") +target_link_libraries(stdlib_highway_test PRIVATE hwy) +set_target_properties(stdlib_highway_test PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${HIGHWAY_TEST_OUT}") +enable_testing() +add_test(NAME highway_runtime COMMAND stdlib_highway_test) + +get_target_property(defines hwy INTERFACE_COMPILE_DEFINITIONS) +string(REPLACE ";" "\", \"" defines "${defines}") +set(libraries "$") +get_target_property(dependencies hwy LINK_LIBRARIES) +if(dependencies) + foreach(dependency IN LISTS dependencies) + if(TARGET "${dependency}") + list(APPEND libraries "$") + elseif(IS_ABSOLUTE "${dependency}" OR dependency MATCHES "^-") + list(APPEND libraries "${dependency}") + elseif(WIN32) + list(APPEND libraries "${dependency}.lib") + else() + list(APPEND libraries "-l${dependency}") + endif() + endforeach() +endif() +string(REPLACE ";" "\", \"" libraries "${libraries}") +file(GENERATE OUTPUT "${CMAKE_BINARY_DIR}/highway.json" CONTENT +"{ + \"include\": [\"${HIGHWAY_SOURCE}\"], + \"defines\": [\"${defines}\"], + \"libraries\": [\"${libraries}\"] +} +" CONDITION "$") diff --git a/tools/make/test/test.highway.js b/tools/make/test/test.highway.js new file mode 100644 index 000000000000..981ab4e4e599 --- /dev/null +++ b/tools/make/test/test.highway.js @@ -0,0 +1,79 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2026 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +/* eslint-disable stdlib/first-unit-test */ + +'use strict'; + +// MODULES // + +var resolve = require( 'path' ).resolve; +var env = require( 'process' ).env; +var tape = require( 'tape' ); +var IS_BROWSER = require( '@stdlib/assert/is-browser' ); +var IS_WINDOWS = require( '@stdlib/assert/is-windows' ); +var contains = require( '@stdlib/assert/contains' ); +var existsSync = require( '@stdlib/fs/exists' ).sync; +var readFileSync = require( '@stdlib/fs/read-file' ).sync; + + +// VARIABLES // + +var runtimeOpts = { + 'skip': IS_BROWSER || !env.STDLIB_TEST_HIGHWAY_RUNTIME +}; + + +// TESTS // + +tape( 'Highway build integration', function test( t ) { + t.ok( true, __filename ); + t.end(); +}); + +tape( 'the native runtime exports its include directory and static library', runtimeOpts, function test( t ) { + var conf = JSON.parse( readFileSync( resolve( env.STDLIB_TEST_HIGHWAY_RUNTIME, 'highway.json' ), { + 'encoding': 'utf8' + })); + t.deepEqual( conf.include, [ env.STDLIB_TEST_HIGHWAY_SOURCE ], 'exports the Highway include directory' ); + t.strictEqual( contains( conf.defines, 'HWY_STATIC_DEFINE' ), true, 'exports the static library definition' ); + t.strictEqual( existsSync( conf.libraries[ 0 ] ), true, 'exports an existing runtime library' ); + t.end(); +}); + +tape( 'the native runtime exports detected platform definitions and link dependencies', runtimeOpts, function test( t ) { + var cache; + var conf; + var lib; + + conf = JSON.parse( readFileSync( resolve( env.STDLIB_TEST_HIGHWAY_RUNTIME, 'highway.json' ), { + 'encoding': 'utf8' + })); + cache = readFileSync( resolve( env.STDLIB_TEST_HIGHWAY_RUNTIME, 'CMakeCache.txt' ), { + 'encoding': 'utf8' + }); + t.strictEqual( contains( conf.defines, 'TOOLCHAIN_MISS_SYS_AUXV_H' ), !/^HAVE_SYS_AUXV_H:INTERNAL=1$/m.test( cache ), 'preserves the sys/auxv.h detection result' ); + t.strictEqual( contains( conf.defines, 'TOOLCHAIN_MISS_ASM_HWCAP_H' ), !/^HAVE_ASM_HWCAP_H:INTERNAL=1$/m.test( cache ), 'preserves the asm/hwcap.h detection result' ); + if ( IS_WINDOWS ) { + lib = 'atomic.lib'; + } else { + lib = '-latomic'; + } + t.strictEqual( contains( conf.libraries, lib ), !/^ATOMICS_LOCK_FREE_INSTRUCTIONS:INTERNAL=1$/m.test( cache ), 'preserves the libatomic link dependency when needed' ); + t.end(); +}); From fe1afeeffc243a66c8d639305dfbeb0ae42eb64f Mon Sep 17 00:00:00 2001 From: Chanho Lee Date: Tue, 8 Sep 2026 11:47:49 +0900 Subject: [PATCH 3/4] feat: add a Highway backend for native DSUMPW Select the Highway kernel through the native manifest and link the prepared runtime from GYP. Keep the existing C implementation when the backend is unset, and preserve the pairwise reduction grouping. Add native parity and manifest checks with build documentation. Verified 175 assertions for GCC and Clang Release and Debug, GCC with libatomic, and the restored default build. Missing-runtime configuration and 14 integration assertions also pass. --- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown_pkg_readmes status: na - task: lint_markdown_docs status: na - task: lint_markdown status: passed - task: lint_package_json status: na - task: lint_repl_help status: na - task: lint_javascript_src status: na - task: lint_javascript_cli status: na - task: lint_javascript_examples status: na - task: lint_javascript_tests status: passed - task: lint_javascript_benchmarks status: na - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: passed - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: na - task: lint_typescript_declarations status: passed - task: lint_typescript_tests status: na - task: lint_license_headers status: passed --- --- .../@stdlib/blas/ext/base/dsumpw/binding.gyp | 103 +++++++- .../@stdlib/blas/ext/base/dsumpw/include.gypi | 44 +++- .../ext/base/dsumpw/simd/dsumpw_highway.h | 37 +++ .../base/dsumpw/simd/dsumpw_highway_impl.h | 224 ++++++++++++++++++ .../blas/ext/base/dsumpw/manifest.json | 48 +++- .../@stdlib/blas/ext/base/dsumpw/src/main.c | 9 + .../base/dsumpw/src/simd/dsumpw_highway.cpp | 45 ++++ .../base/dsumpw/test/test.ndarray.native.js | 111 +++++++++ tools/make/common.mk | 8 + tools/make/lib/install/README.md | 18 +- tools/make/lib/install/addons.mk | 12 +- tools/make/test/test.highway.js | 32 +++ 12 files changed, 677 insertions(+), 14 deletions(-) create mode 100644 lib/node_modules/@stdlib/blas/ext/base/dsumpw/include/stdlib/blas/ext/base/dsumpw/simd/dsumpw_highway.h create mode 100644 lib/node_modules/@stdlib/blas/ext/base/dsumpw/include/stdlib/blas/ext/base/dsumpw/simd/dsumpw_highway_impl.h create mode 100644 lib/node_modules/@stdlib/blas/ext/base/dsumpw/src/simd/dsumpw_highway.cpp diff --git a/lib/node_modules/@stdlib/blas/ext/base/dsumpw/binding.gyp b/lib/node_modules/@stdlib/blas/ext/base/dsumpw/binding.gyp index 7d0005b2e390..8738eeba58df 100644 --- a/lib/node_modules/@stdlib/blas/ext/base/dsumpw/binding.gyp +++ b/lib/node_modules/@stdlib/blas/ext/base/dsumpw/binding.gyp @@ -29,6 +29,9 @@ # Target name should match the add-on export name: 'addon_target_name%': 'addon', + # SIMD backend: + 'simd%': '', + # Set variables based on the host OS: 'conditions': [ [ @@ -48,13 +51,103 @@ # Define compile targets: 'targets': [ + # Target to compile the Highway kernel: + { + 'target_name': 'simd_kernel', + + # By default, this target has no output: + 'type': 'none', + + # Apply conditions based on the selected SIMD backend: + 'conditions': [ + [ + 'simd=="highway"', + { + # Generate a static library which is linked into the add-on: + 'type': 'static_library', + + # Define directories which contain relevant include headers: + 'include_dirs': [ + '<@(include_dirs)', + '<@(highway_include_dirs)', + ], + + # List of source files: + 'sources': [ + '<@(cxx_src_files)', + ], + + 'defines': [ + '<@(highway_defines)', + ], + + 'link_settings': { + 'libraries': [ + '<@(highway_libraries)', + ], + }, + + # C/C++ compiler flags: + 'cflags': [ + # Enable commonly used warning options: + '-Wall', + + # Aggressive optimization: + '-O3', + + # Disable floating-point contraction: + '-ffp-contract=off', + ], + + # C++ specific compiler flags: + 'cflags_cc': [ + # Use the C++17 standard: + '-std=c++17', + ], + + # Apply conditions based on the host OS: + 'conditions': [ + [ + 'OS=="win"', + { + 'msvs_settings': { + 'VCCLCompilerTool': { + # Use the C++17 standard: + 'AdditionalOptions': [ + '/std:c++17', + ], + }, + }, + }, + ], # end condition (OS=="win") + [ + 'OS!="win"', + { + # C/C++ flags: + 'cflags': [ + # Generate position-independent code: + '-fPIC', + + # Keep implementation symbols private to the add-on: + '-fvisibility=hidden', + ], + }, + ], # end condition (OS!="win") + ], # end conditions + }, + ], # end condition (simd=="highway") + ], # end conditions + }, # end target simd_kernel + # Target to generate an add-on: { # The target name should match the add-on export name: 'target_name': '<(addon_target_name)', # Define dependencies: - 'dependencies': [], + 'dependencies': [ + 'simd_kernel', + ], # Define directories which contain relevant include headers: 'include_dirs': [ @@ -67,6 +160,14 @@ '<@(src_files)', ], + 'sources!': [ + '<@(cxx_src_files)', + ], + + 'defines': [ + '<@(defines)', + ], + # Settings which should be applied when a target's object files are used as linker input: 'link_settings': { # Define libraries: diff --git a/lib/node_modules/@stdlib/blas/ext/base/dsumpw/include.gypi b/lib/node_modules/@stdlib/blas/ext/base/dsumpw/include.gypi index 26476a8c2655..198c6ae7d82e 100644 --- a/lib/node_modules/@stdlib/blas/ext/base/dsumpw/include.gypi +++ b/lib/node_modules/@stdlib/blas/ext/base/dsumpw/include.gypi @@ -23,12 +23,20 @@ { # Define variables to be used throughout the configuration for all targets: 'variables': { + 'variables': { + # SIMD backend: + 'simd%': '', + + # Path to a configured Highway build: + 'highway_dir%': '', + }, + # Source directory: 'src_dir': './src', # Include directories: 'include_dirs': [ - ' 128 and strideX = 1. +*/ +double API_SUFFIX( stdlib_strided_dsumpw_ndarray_highway )( const CBLAS_INT N, const double *X, const CBLAS_INT strideX, const CBLAS_INT offsetX ); + +#ifdef __cplusplus +} +#endif + +#endif // !STDLIB_BLAS_EXT_BASE_DSUMPW_SIMD_HIGHWAY_H diff --git a/lib/node_modules/@stdlib/blas/ext/base/dsumpw/include/stdlib/blas/ext/base/dsumpw/simd/dsumpw_highway_impl.h b/lib/node_modules/@stdlib/blas/ext/base/dsumpw/include/stdlib/blas/ext/base/dsumpw/simd/dsumpw_highway_impl.h new file mode 100644 index 000000000000..e487db77a8d2 --- /dev/null +++ b/lib/node_modules/@stdlib/blas/ext/base/dsumpw/include/stdlib/blas/ext/base/dsumpw/simd/dsumpw_highway_impl.h @@ -0,0 +1,224 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2026 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +// Per-target include guard. Highway toggles `HWY_TARGET_TOGGLE` when this file +// is re-included to generate each dynamically dispatched target. +#if defined( STDLIB_BLAS_EXT_BASE_DSUMPW_SIMD_HIGHWAY_IMPL_H ) == defined( HWY_TARGET_TOGGLE ) +#ifdef STDLIB_BLAS_EXT_BASE_DSUMPW_SIMD_HIGHWAY_IMPL_H +#undef STDLIB_BLAS_EXT_BASE_DSUMPW_SIMD_HIGHWAY_IMPL_H +#else +#define STDLIB_BLAS_EXT_BASE_DSUMPW_SIMD_HIGHWAY_IMPL_H +#endif + +#include "hwy/highway.h" +#include "stdlib/blas/base/shared.h" + +HWY_BEFORE_NAMESPACE(); + +namespace stdlib_blas_ext_base_dsumpw { + namespace HWY_NAMESPACE { + + namespace { + + namespace hn = hwy::HWY_NAMESPACE; + + /** + * Computes a pairwise sum without invoking the public dispatch entry point. + */ +#if !HWY_HAVE_FLOAT64 + double DsumpwScalar( const CBLAS_INT N, const double *X, const CBLAS_INT strideX, const CBLAS_INT offsetX ) { + CBLAS_INT ix; + CBLAS_INT M; + CBLAS_INT n; + CBLAS_INT i; + double sum; + double s0; + double s1; + double s2; + double s3; + double s4; + double s5; + double s6; + double s7; + + if ( N <= 0 ) { + return 0.0; + } + ix = offsetX; + if ( strideX == 0 ) { + return N * X[ ix ]; + } + if ( N < 8 ) { + sum = X[ ix ]; + ix += strideX; + for ( i = 1; i < N; i++ ) { + sum += X[ ix ]; + ix += strideX; + } + return sum; + } + if ( N <= 128 ) { + s0 = X[ ix ]; + s1 = X[ ix + strideX ]; + s2 = X[ ix + ( 2 * strideX ) ]; + s3 = X[ ix + ( 3 * strideX ) ]; + s4 = X[ ix + ( 4 * strideX ) ]; + s5 = X[ ix + ( 5 * strideX ) ]; + s6 = X[ ix + ( 6 * strideX ) ]; + s7 = X[ ix + ( 7 * strideX ) ]; + ix += 8 * strideX; + + M = N % 8; + for ( i = 8; i < N - M; i += 8 ) { + s0 += X[ ix ]; + s1 += X[ ix + strideX ]; + s2 += X[ ix + ( 2 * strideX ) ]; + s3 += X[ ix + ( 3 * strideX ) ]; + s4 += X[ ix + ( 4 * strideX ) ]; + s5 += X[ ix + ( 5 * strideX ) ]; + s6 += X[ ix + ( 6 * strideX ) ]; + s7 += X[ ix + ( 7 * strideX ) ]; + ix += 8 * strideX; + } + sum = ( ( s0 + s1 ) + ( s2 + s3 ) ) + ( ( s4 + s5 ) + ( s6 + s7 ) ); + for ( ; i < N; i++ ) { + sum += X[ ix ]; + ix += strideX; + } + return sum; + } + n = N / 2; + n -= n % 8; + return DsumpwScalar( n, X, strideX, ix ) + DsumpwScalar( N - n, X, strideX, ix + ( n * strideX ) ); + } +#endif + +#if HWY_HAVE_FLOAT64 + + /** + * Computes a pairwise sum using eight logical Highway accumulators. + */ + double DsumpwHighwayImpl( const CBLAS_INT N, const double *X, const CBLAS_INT offsetX ) { + CBLAS_INT ix; + CBLAS_INT M; + CBLAS_INT n; + CBLAS_INT i; + double sum; + HWY_ALIGN double lanes[ 8 ]; + const hn::CappedTag d4; + const hn::CappedTag d2; + const hn::CappedTag d1; + + if ( N > 128 ) { + n = N / 2; + n -= n % 8; + return DsumpwHighwayImpl( n, X, offsetX ) + DsumpwHighwayImpl( N - n, X, offsetX + n ); + } + + ix = offsetX; + M = N % 8; + if ( hn::Lanes( d4 ) == 4 ) { + auto s0 = hn::LoadU( d4, X + ix ); + auto s1 = hn::LoadU( d4, X + ix + 4 ); + + ix += 8; + for ( i = 8; i < N - M; i += 8 ) { + s0 = hn::Add( s0, hn::LoadU( d4, X + ix ) ); + s1 = hn::Add( s1, hn::LoadU( d4, X + ix + 4 ) ); + ix += 8; + } + hn::StoreU( s0, d4, lanes ); + hn::StoreU( s1, d4, lanes + 4 ); + } else if ( hn::Lanes( d2 ) == 2 ) { + auto s0 = hn::LoadU( d2, X + ix ); + auto s1 = hn::LoadU( d2, X + ix + 2 ); + auto s2 = hn::LoadU( d2, X + ix + 4 ); + auto s3 = hn::LoadU( d2, X + ix + 6 ); + + ix += 8; + for ( i = 8; i < N - M; i += 8 ) { + s0 = hn::Add( s0, hn::LoadU( d2, X + ix ) ); + s1 = hn::Add( s1, hn::LoadU( d2, X + ix + 2 ) ); + s2 = hn::Add( s2, hn::LoadU( d2, X + ix + 4 ) ); + s3 = hn::Add( s3, hn::LoadU( d2, X + ix + 6 ) ); + ix += 8; + } + hn::StoreU( s0, d2, lanes ); + hn::StoreU( s1, d2, lanes + 2 ); + hn::StoreU( s2, d2, lanes + 4 ); + hn::StoreU( s3, d2, lanes + 6 ); + } else { + auto s0 = hn::LoadU( d1, X + ix ); + auto s1 = hn::LoadU( d1, X + ix + 1 ); + auto s2 = hn::LoadU( d1, X + ix + 2 ); + auto s3 = hn::LoadU( d1, X + ix + 3 ); + auto s4 = hn::LoadU( d1, X + ix + 4 ); + auto s5 = hn::LoadU( d1, X + ix + 5 ); + auto s6 = hn::LoadU( d1, X + ix + 6 ); + auto s7 = hn::LoadU( d1, X + ix + 7 ); + + ix += 8; + for ( i = 8; i < N - M; i += 8 ) { + s0 = hn::Add( s0, hn::LoadU( d1, X + ix ) ); + s1 = hn::Add( s1, hn::LoadU( d1, X + ix + 1 ) ); + s2 = hn::Add( s2, hn::LoadU( d1, X + ix + 2 ) ); + s3 = hn::Add( s3, hn::LoadU( d1, X + ix + 3 ) ); + s4 = hn::Add( s4, hn::LoadU( d1, X + ix + 4 ) ); + s5 = hn::Add( s5, hn::LoadU( d1, X + ix + 5 ) ); + s6 = hn::Add( s6, hn::LoadU( d1, X + ix + 6 ) ); + s7 = hn::Add( s7, hn::LoadU( d1, X + ix + 7 ) ); + ix += 8; + } + hn::StoreU( s0, d1, lanes ); + hn::StoreU( s1, d1, lanes + 1 ); + hn::StoreU( s2, d1, lanes + 2 ); + hn::StoreU( s3, d1, lanes + 3 ); + hn::StoreU( s4, d1, lanes + 4 ); + hn::StoreU( s5, d1, lanes + 5 ); + hn::StoreU( s6, d1, lanes + 6 ); + hn::StoreU( s7, d1, lanes + 7 ); + } + sum = ( ( lanes[ 0 ] + lanes[ 1 ] ) + ( lanes[ 2 ] + lanes[ 3 ] ) ) + ( ( lanes[ 4 ] + lanes[ 5 ] ) + ( lanes[ 6 ] + lanes[ 7 ] ) ); + for ( ; i < N; i++ ) { + sum += X[ ix ]; + ix += 1; + } + return sum; + } + +#endif // HWY_HAVE_FLOAT64 + + } // end namespace + + /** + * Computes a pairwise sum using Highway when the target supports doubles. + */ + double DsumpwHighway( const CBLAS_INT N, const double *X, const CBLAS_INT strideX, const CBLAS_INT offsetX ) { +#if HWY_HAVE_FLOAT64 + return DsumpwHighwayImpl( N, X, offsetX ); +#else + return DsumpwScalar( N, X, strideX, offsetX ); +#endif + } + + } // end namespace HWY_NAMESPACE +} // end namespace stdlib_blas_ext_base_dsumpw + +HWY_AFTER_NAMESPACE(); + +#endif // include guard diff --git a/lib/node_modules/@stdlib/blas/ext/base/dsumpw/manifest.json b/lib/node_modules/@stdlib/blas/ext/base/dsumpw/manifest.json index ef608a7ea324..3f6c30ba6671 100644 --- a/lib/node_modules/@stdlib/blas/ext/base/dsumpw/manifest.json +++ b/lib/node_modules/@stdlib/blas/ext/base/dsumpw/manifest.json @@ -1,7 +1,8 @@ { "options": { "task": "build", - "wasm": false + "wasm": false, + "simd": "" }, "fields": [ { @@ -23,12 +24,18 @@ "field": "libpath", "resolve": true, "relative": false + }, + { + "field": "defines", + "resolve": false, + "relative": false } ], "confs": [ { "task": "build", "wasm": false, + "simd": "", "src": [ "./src/main.c" ], @@ -45,11 +52,39 @@ "@stdlib/strided/base/stride2offset", "@stdlib/blas/base/shared", "@stdlib/napi/create-double" + ], + "defines": [] + }, + { + "task": "build", + "wasm": false, + "simd": "highway", + "src": [ + "./src/main.c", + "./src/simd/dsumpw_highway.cpp" + ], + "include": [ + "./include" + ], + "libraries": [], + "libpath": [], + "dependencies": [ + "@stdlib/napi/export", + "@stdlib/napi/argv", + "@stdlib/napi/argv-int64", + "@stdlib/napi/argv-strided-float64array", + "@stdlib/strided/base/stride2offset", + "@stdlib/blas/base/shared", + "@stdlib/napi/create-double" + ], + "defines": [ + "STDLIB_BLAS_EXT_BASE_DSUMPW_SIMD_HIGHWAY" ] }, { "task": "benchmark", "wasm": false, + "simd": "", "src": [ "./src/main.c" ], @@ -61,11 +96,13 @@ "dependencies": [ "@stdlib/strided/base/stride2offset", "@stdlib/blas/base/shared" - ] + ], + "defines": [] }, { "task": "examples", "wasm": false, + "simd": "", "src": [ "./src/main.c" ], @@ -77,11 +114,13 @@ "dependencies": [ "@stdlib/strided/base/stride2offset", "@stdlib/blas/base/shared" - ] + ], + "defines": [] }, { "task": "build", "wasm": true, + "simd": "", "src": [ "./src/main.c" ], @@ -93,7 +132,8 @@ "dependencies": [ "@stdlib/strided/base/stride2offset", "@stdlib/blas/base/shared" - ] + ], + "defines": [] } ] } diff --git a/lib/node_modules/@stdlib/blas/ext/base/dsumpw/src/main.c b/lib/node_modules/@stdlib/blas/ext/base/dsumpw/src/main.c index d3c48f7f2f2f..b33716525459 100644 --- a/lib/node_modules/@stdlib/blas/ext/base/dsumpw/src/main.c +++ b/lib/node_modules/@stdlib/blas/ext/base/dsumpw/src/main.c @@ -20,6 +20,10 @@ #include "stdlib/strided/base/stride2offset.h" #include "stdlib/blas/base/shared.h" +#if defined(STDLIB_BLAS_EXT_BASE_DSUMPW_SIMD_HIGHWAY) +#include "stdlib/blas/ext/base/dsumpw/simd/dsumpw_highway.h" +#endif + /** * Computes the sum of double-precision floating-point strided array elements using pairwise summation. * @@ -125,6 +129,11 @@ double API_SUFFIX(stdlib_strided_dsumpw_ndarray)( const CBLAS_INT N, const doubl } return sum; } +#if defined(STDLIB_BLAS_EXT_BASE_DSUMPW_SIMD_HIGHWAY) + if ( strideX == 1 ) { + return API_SUFFIX(stdlib_strided_dsumpw_ndarray_highway)( N, X, strideX, offsetX ); + } +#endif // Recurse by dividing by two, but avoiding non-multiples of unroll factor... n = N / 2; n -= n % 8; diff --git a/lib/node_modules/@stdlib/blas/ext/base/dsumpw/src/simd/dsumpw_highway.cpp b/lib/node_modules/@stdlib/blas/ext/base/dsumpw/src/simd/dsumpw_highway.cpp new file mode 100644 index 000000000000..b6cc7ee1770d --- /dev/null +++ b/lib/node_modules/@stdlib/blas/ext/base/dsumpw/src/simd/dsumpw_highway.cpp @@ -0,0 +1,45 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2026 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#undef HWY_TARGET_INCLUDE +#define HWY_TARGET_INCLUDE "stdlib/blas/ext/base/dsumpw/simd/dsumpw_highway_impl.h" + +#include "hwy/foreach_target.h" + +#include "hwy/highway.h" +#include "stdlib/blas/ext/base/dsumpw/simd/dsumpw_highway.h" + +#include HWY_TARGET_INCLUDE + +#if HWY_ONCE + +namespace stdlib_blas_ext_base_dsumpw { + + HWY_EXPORT( DsumpwHighway ); + + static double DispatchDsumpwHighway( const CBLAS_INT N, const double *X, const CBLAS_INT strideX, const CBLAS_INT offsetX ) { + return HWY_DYNAMIC_DISPATCH( DsumpwHighway )( N, X, strideX, offsetX ); + } + +} // end namespace stdlib_blas_ext_base_dsumpw + +extern "C" double API_SUFFIX( stdlib_strided_dsumpw_ndarray_highway )( const CBLAS_INT N, const double *X, const CBLAS_INT strideX, const CBLAS_INT offsetX ) { + return stdlib_blas_ext_base_dsumpw::DispatchDsumpwHighway( N, X, strideX, offsetX ); +} + +#endif diff --git a/lib/node_modules/@stdlib/blas/ext/base/dsumpw/test/test.ndarray.native.js b/lib/node_modules/@stdlib/blas/ext/base/dsumpw/test/test.ndarray.native.js index 94c3737a1565..3b260f5917d4 100644 --- a/lib/node_modules/@stdlib/blas/ext/base/dsumpw/test/test.ndarray.native.js +++ b/lib/node_modules/@stdlib/blas/ext/base/dsumpw/test/test.ndarray.native.js @@ -24,6 +24,10 @@ var resolve = require( 'path' ).resolve; var tape = require( 'tape' ); var isnan = require( '@stdlib/math/base/assert/is-nan' ); var isNegativeZero = require( '@stdlib/math/base/assert/is-negative-zero' ); +var isSameValue = require( '@stdlib/assert/is-same-value' ); +var uniform = require( '@stdlib/random/base/uniform' ); +var PINF = require( '@stdlib/constants/float64/pinf' ); +var NINF = require( '@stdlib/constants/float64/ninf' ); var Float64Array = require( '@stdlib/array/float64' ); var tryRequire = require( '@stdlib/utils/try-require' ); @@ -84,14 +88,121 @@ tape( 'the function calculates the sum of all strided array elements', opts, fun t.end(); }); +tape( 'the function returns equal contiguous and strided results for seeded finite inputs', opts, function test( t ) { + var expected; + var lengths; + var actual; + var value; + var rand; + var N; + var x; + var y; + var i; + var j; + + lengths = [ + 127, 128, 129, 130, 135, 255, 256, 257, 1023, 1024, 1025, 2049 + ]; + rand = uniform.factory( -100.0, 100.0, { + 'seed': 12345 + }); + + for ( i = 0; i < 64; i++ ) { + if ( i < lengths.length ) { + N = lengths[ i ]; + } else { + N = 129 + ( ( i*67 ) % 4096 ); + } + x = new Float64Array( N+2 ); + y = new Float64Array( ( 2*N )+4 ); + for ( j = 0; j < N; j++ ) { + value = rand(); + x[ j+1 ] = value; + y[ ( 2*j )+3 ] = value; + } + actual = dsumpw( N, x, 1, 1 ); + expected = dsumpw( N, y, 2, 3 ); + t.strictEqual( isSameValue( actual, expected ), true, 'returns expected value for seeded input '+i+' (N = '+N+')' ); + } + t.end(); +}); + +tape( 'the function returns equal contiguous and strided results for cancellation-sensitive inputs', opts, function test( t ) { + var expected; + var values; + var actual; + var x; + var y; + var i; + + values = [ 1.0, 1.0e100, 1.0, -1.0e100 ]; + x = new Float64Array( 1027 ); + y = new Float64Array( 2054 ); + for ( i = 0; i < 1025; i++ ) { + x[ i+1 ] = values[ i%4 ]; + y[ ( 2*i )+3 ] = values[ i%4 ]; + } + actual = dsumpw( 1025, x, 1, 1 ); + expected = dsumpw( 1025, y, 2, 3 ); + t.strictEqual( isSameValue( actual, expected ), true, 'returns expected value for cancellation-sensitive input' ); + t.end(); +}); + +tape( 'the function returns NaN when a large input contains NaN', opts, function test( t ) { + var x; + var y; + + x = new Float64Array( 259 ); + y = new Float64Array( 518 ); + x[ 131 ] = NaN; + y[ 263 ] = NaN; + t.strictEqual( isnan( dsumpw( 257, x, 1, 1 ) ), true, 'returns NaN for contiguous input' ); + t.strictEqual( isnan( dsumpw( 257, y, 2, 3 ) ), true, 'returns NaN for strided input' ); + t.end(); +}); + +tape( 'the function handles infinite values in large inputs', opts, function test( t ) { + var x; + var y; + + x = new Float64Array( 1026 ); + y = new Float64Array( 2052 ); + x[ 11 ] = PINF; + y[ 23 ] = PINF; + t.strictEqual( dsumpw( 1024, x, 1, 1 ), PINF, 'returns positive infinity for contiguous input' ); + t.strictEqual( dsumpw( 1024, y, 2, 3 ), PINF, 'returns positive infinity for strided input' ); + + x[ 11 ] = NINF; + y[ 23 ] = NINF; + t.strictEqual( dsumpw( 1024, x, 1, 1 ), NINF, 'returns negative infinity for contiguous input' ); + t.strictEqual( dsumpw( 1024, y, 2, 3 ), NINF, 'returns negative infinity for strided input' ); + + x[ 12 ] = PINF; + y[ 25 ] = PINF; + t.strictEqual( isnan( dsumpw( 1024, x, 1, 1 ) ), true, 'returns NaN for contiguous input containing both infinities' ); + t.strictEqual( isnan( dsumpw( 1024, y, 2, 3 ) ), true, 'returns NaN for strided input containing both infinities' ); + t.end(); +}); + tape( 'the function preserves the sign of zero', opts, function test( t ) { var x; + var y; var v; + var i; x = new Float64Array( [ -0.0, -0.0, -0.0, -0.0, -0.0 ] ); v = dsumpw( x.length, x, 1, 0 ); t.strictEqual( isNegativeZero( v ), true, 'returns expected value' ); + x = new Float64Array( 131 ); + y = new Float64Array( 262 ); + for ( i = 0; i < 129; i++ ) { + x[ i+1 ] = -0.0; + y[ ( 2*i )+3 ] = -0.0; + } + t.strictEqual( isNegativeZero( dsumpw( 129, x, 1, 1 ) ), true, 'preserves negative zero for large contiguous input' ); + t.strictEqual( isNegativeZero( dsumpw( 129, y, 2, 3 ) ), true, 'preserves negative zero for large strided input' ); + t.end(); }); diff --git a/tools/make/common.mk b/tools/make/common.mk index 5dae0f9362f7..8a03da142e8b 100644 --- a/tools/make/common.mk +++ b/tools/make/common.mk @@ -700,6 +700,11 @@ deps_fftpack_version_slug := $(subst .,_,$(DEPS_FFTPACK_VERSION)) # Define the output path when building FFTPACK: DEPS_FFTPACK_BUILD_OUT ?= $(DEPS_BUILD_DIR)/pffft-$(DEPS_FFTPACK_VERSION) +# SIMD... + +# Define the SIMD backend: +SIMD_BACKEND ?= + # Highway... # Define the Highway version: @@ -713,3 +718,6 @@ DEPS_HIGHWAY_INCLUDE ?= $(DEPS_HIGHWAY_BUILD_OUT) # Define the native Highway build directory: DEPS_HIGHWAY_RUNTIME_OUT ?= $(DEPS_HIGHWAY_BUILD_OUT)/build + +# Define the path to a configured native Highway build: +HIGHWAY_DIR ?= $(DEPS_HIGHWAY_RUNTIME_OUT) diff --git a/tools/make/lib/install/README.md b/tools/make/lib/install/README.md index 560c40d90098..5465af70a8aa 100644 --- a/tools/make/lib/install/README.md +++ b/tools/make/lib/install/README.md @@ -260,6 +260,8 @@ Compiles Node.js native [add-ons][node-js-add-ons]. $ make install-node-addons ``` +`SIMD_BACKEND=highway` enables Highway for supported add-ons and requires a Highway installation and a C++17-capable compiler. Highway handles CPU target selection; unsupported architectures or toolchains may fail to compile without falling back to the default native implementation. Leaving `SIMD_BACKEND` unset preserves the default build. + #### clean-node-addons Removes Node.js native [add-ons][node-js-add-ons]. @@ -446,19 +448,29 @@ $ make deps-build-highway The build directory defaults to `DEPS_HIGHWAY_BUILD_OUT/build` and can be overridden with `DEPS_HIGHWAY_RUNTIME_OUT`. CMake maintains the incremental build there and writes `highway.json`, which records the headers, public compile definitions, static library, and additional link dependencies needed by add-ons. -Use `DEPS_HIGHWAY_BUILD_TYPE=Debug` for a debug build; `Release` is the default. Use a fresh build directory when changing compilers or toolchain flags, and separate directories when keeping multiple configurations. CMake caches toolchain checks; do not configure the same directory concurrently. +Install the runtime before enabling the native backend: + +```bash +$ make install-deps-highway +$ make install-node-addons SIMD_BACKEND=highway +``` + +`install-node-addons` reads the runtime metadata from `HIGHWAY_DIR`, which defaults to `DEPS_HIGHWAY_RUNTIME_OUT`. It does not build the runtime automatically. A missing runtime is an error when Highway is requested; leaving the backend unset does not require Highway or CMake. Direct GYP builds use `-Dsimd=highway -Dhighway_dir=`. + +Use matching compilers and build configurations for the runtime and add-ons. `DEPS_HIGHWAY_BUILD_TYPE` accepts `Release` (the default) or `Debug` independently of `NODE_GYP_FLAGS`. Use a fresh build directory when changing compilers or toolchain flags, and separate directories when keeping multiple configurations. CMake caches toolchain checks; do not configure the same directory concurrently. Compiled tests default to `deps/test/highway/build`. When keeping multiple runtime builds, set `DEPS_HIGHWAY_TEST_OUT` to a separate absolute directory for each build to avoid overwriting test executables. Reuse the same runtime and test output directory pair on subsequent builds. ```bash $ make deps-build-highway DEPS_HIGHWAY_BUILD_TYPE=Debug DEPS_HIGHWAY_RUNTIME_OUT=/path/to/highway-debug DEPS_HIGHWAY_TEST_OUT=/path/to/stdlib/deps/test/highway/build/debug +$ make install-node-addons SIMD_BACKEND=highway HIGHWAY_DIR=/path/to/highway-debug NODE_GYP_FLAGS=--debug ``` -For an alternate toolchain, pass `C_COMPILER` and `CXX_COMPILER` to the build command. +For an alternate toolchain, pass the same `C_COMPILER` and `CXX_COMPILER` to both commands. This path does not configure standalone npm native builds or cross-compilation. #### deps-test-highway-build -Builds and tests the runtime, then runs JS/Tape checks for generated link metadata through the project's JavaScript test runner. +Builds and tests the runtime, then runs JS/Tape checks for the native manifest and generated link metadata through the project's JavaScript test runner. ```bash $ make deps-test-highway-build diff --git a/tools/make/lib/install/addons.mk b/tools/make/lib/install/addons.mk index 9aaee2253556..cc0f8096224f 100644 --- a/tools/make/lib/install/addons.mk +++ b/tools/make/lib/install/addons.mk @@ -36,6 +36,14 @@ ifdef BLAS_DIR endif endif endif +node_gyp_defines := $(NODE_GYP_DEFINES) +ifneq (, $(SIMD_BACKEND)) + node_gyp_defines += simd=$(SIMD_BACKEND) +endif +ifeq ($(SIMD_BACKEND), highway) + node_gyp_defines += highway_dir=$(HIGHWAY_DIR) + export CC CXX +endif # Define an add-on package pattern filter: ifndef NODE_ADDONS_PATTERN @@ -79,7 +87,7 @@ ifeq ($(FAIL_FAST), true) cd $$pkg && \ MAKEFLAGS= \ NODE_PATH="$(NODE_PATH)" \ - GYP_DEFINES="$(NODE_GYP_DEFINES)" \ + GYP_DEFINES="$(node_gyp_defines)" \ $(NODE_GYP) $(NODE_GYP_FLAGS) rebuild \ || { echo "Error: failed to build add-on: $$pkg"; exit 1; } \ done @@ -93,7 +101,7 @@ else cd $$pkg && \ MAKEFLAGS= \ NODE_PATH="$(NODE_PATH)" \ - GYP_DEFINES="$(NODE_GYP_DEFINES)" \ + GYP_DEFINES="$(node_gyp_defines)" \ $(NODE_GYP) $(NODE_GYP_FLAGS) rebuild \ || { echo "Error: failed to build add-on: $$pkg"; exit 0; } \ done diff --git a/tools/make/test/test.highway.js b/tools/make/test/test.highway.js index 981ab4e4e599..2665c974d679 100644 --- a/tools/make/test/test.highway.js +++ b/tools/make/test/test.highway.js @@ -30,10 +30,21 @@ var IS_WINDOWS = require( '@stdlib/assert/is-windows' ); var contains = require( '@stdlib/assert/contains' ); var existsSync = require( '@stdlib/fs/exists' ).sync; var readFileSync = require( '@stdlib/fs/read-file' ).sync; +var manifest = require( '@stdlib/utils/library-manifest' ); // VARIABLES // +var root = resolve( __dirname, '..', '..', '..', 'lib', 'node_modules' ); +var dir = resolve( root, '@stdlib', 'blas', 'ext', 'base', 'dsumpw' ); +var fpath = resolve( dir, 'manifest.json' ); +var mopts = { + 'basedir': dir, + 'paths': 'posix' +}; +var opts = { + 'skip': IS_BROWSER +}; var runtimeOpts = { 'skip': IS_BROWSER || !env.STDLIB_TEST_HIGHWAY_RUNTIME }; @@ -46,6 +57,27 @@ tape( 'Highway build integration', function test( t ) { t.end(); }); +tape( 'the default native manifest selects the C implementation', opts, function test( t ) { + var conf = manifest( fpath, {}, mopts ); + t.strictEqual( contains( conf.src, 'src/main.c' ), true, 'includes the C source' ); + t.strictEqual( contains( conf.src, 'src/simd/dsumpw_highway.cpp' ), false, 'does not include the Highway source' ); + t.deepEqual( conf.defines, [], 'does not enable a SIMD backend' ); + t.deepEqual( conf, manifest( fpath, { + 'simd': '' + }, mopts ), 'an empty backend preserves the default configuration' ); + t.end(); +}); + +tape( 'the Highway native manifest selects the SIMD implementation', opts, function test( t ) { + var conf = manifest( fpath, { + 'simd': 'highway' + }, mopts ); + t.strictEqual( contains( conf.src, 'src/main.c' ), true, 'includes the C source' ); + t.strictEqual( contains( conf.src, 'src/simd/dsumpw_highway.cpp' ), true, 'includes the Highway source' ); + t.deepEqual( conf.defines, [ 'STDLIB_BLAS_EXT_BASE_DSUMPW_SIMD_HIGHWAY' ], 'enables the Highway implementation' ); + t.end(); +}); + tape( 'the native runtime exports its include directory and static library', runtimeOpts, function test( t ) { var conf = JSON.parse( readFileSync( resolve( env.STDLIB_TEST_HIGHWAY_RUNTIME, 'highway.json' ), { 'encoding': 'utf8' From a91d20885dd3aac0e8196982659573c74968bbca Mon Sep 17 00:00:00 2001 From: Chanho Lee Date: Tue, 8 Sep 2026 11:49:40 +0900 Subject: [PATCH 4/4] feat: enable Highway for Wasm DMEANPW Add a static Wasm entry for the shared DSUMPW kernel and propagate backend sources and definitions through the DMEANPW manifest chain. Build C and C++ inputs separately while retaining scalar defaults. Check pointers, special values, and definition resolution failures. Fresh scalar and Highway builds each pass 106 assertions. Verified 22 integration assertions, backend switching, definition invalidation, and 79 assertions for legacy scalar DASUM. --- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown_pkg_readmes status: na - task: lint_markdown_docs status: na - task: lint_markdown status: passed - task: lint_package_json status: na - task: lint_repl_help status: na - task: lint_javascript_src status: na - task: lint_javascript_cli status: na - task: lint_javascript_examples status: na - task: lint_javascript_tests status: passed - task: lint_javascript_benchmarks status: na - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: na - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: passed - task: lint_typescript_declarations status: passed - task: lint_typescript_tests status: na - task: lint_license_headers status: passed --- --- .../blas/ext/base/dsumpw/manifest.json | 21 +++ .../dsumpw/src/simd/dsumpw_highway_wasm.cpp | 34 ++++ .../stats/strided/dmeanpw/manifest.json | 17 +- .../stats/strided/wasm/dmeanpw/manifest.json | 8 +- .../stats/strided/wasm/dmeanpw/src/Makefile | 174 +++++++++++++++++- .../strided/wasm/dmeanpw/test/test.main.js | 48 +++++ .../wasm/dmeanpw/test/test.module.ndarray.js | 77 ++++++++ .../strided/wasm/dmeanpw/test/test.ndarray.js | 16 ++ tools/make/lib/wasm/Makefile | 21 ++- tools/make/lib/wasm/README.md | 5 + tools/make/test/test.highway.js | 73 ++++++++ tools/scripts/compile_wasm | 75 +++++++- 12 files changed, 549 insertions(+), 20 deletions(-) create mode 100644 lib/node_modules/@stdlib/blas/ext/base/dsumpw/src/simd/dsumpw_highway_wasm.cpp diff --git a/lib/node_modules/@stdlib/blas/ext/base/dsumpw/manifest.json b/lib/node_modules/@stdlib/blas/ext/base/dsumpw/manifest.json index 3f6c30ba6671..ac22918ce4b5 100644 --- a/lib/node_modules/@stdlib/blas/ext/base/dsumpw/manifest.json +++ b/lib/node_modules/@stdlib/blas/ext/base/dsumpw/manifest.json @@ -134,6 +134,27 @@ "@stdlib/blas/base/shared" ], "defines": [] + }, + { + "task": "build", + "wasm": true, + "simd": "highway", + "src": [ + "./src/main.c", + "./src/simd/dsumpw_highway_wasm.cpp" + ], + "include": [ + "./include" + ], + "libraries": [], + "libpath": [], + "dependencies": [ + "@stdlib/strided/base/stride2offset", + "@stdlib/blas/base/shared" + ], + "defines": [ + "STDLIB_BLAS_EXT_BASE_DSUMPW_SIMD_HIGHWAY" + ] } ] } diff --git a/lib/node_modules/@stdlib/blas/ext/base/dsumpw/src/simd/dsumpw_highway_wasm.cpp b/lib/node_modules/@stdlib/blas/ext/base/dsumpw/src/simd/dsumpw_highway_wasm.cpp new file mode 100644 index 000000000000..2a69b80db7ae --- /dev/null +++ b/lib/node_modules/@stdlib/blas/ext/base/dsumpw/src/simd/dsumpw_highway_wasm.cpp @@ -0,0 +1,34 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2026 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#define HWY_COMPILE_ONLY_STATIC + +#include "stdlib/blas/ext/base/dsumpw/simd/dsumpw_highway_impl.h" +#include "stdlib/blas/ext/base/dsumpw/simd/dsumpw_highway.h" + +namespace stdlib_blas_ext_base_dsumpw { + + static double DispatchDsumpwHighway( const CBLAS_INT N, const double *X, const CBLAS_INT strideX, const CBLAS_INT offsetX ) { + return HWY_STATIC_DISPATCH( DsumpwHighway )( N, X, strideX, offsetX ); + } + +} // end namespace stdlib_blas_ext_base_dsumpw + +extern "C" double API_SUFFIX( stdlib_strided_dsumpw_ndarray_highway )( const CBLAS_INT N, const double *X, const CBLAS_INT strideX, const CBLAS_INT offsetX ) { + return stdlib_blas_ext_base_dsumpw::DispatchDsumpwHighway( N, X, strideX, offsetX ); +} diff --git a/lib/node_modules/@stdlib/stats/strided/dmeanpw/manifest.json b/lib/node_modules/@stdlib/stats/strided/dmeanpw/manifest.json index 314a0404b4b0..408f8f62fe98 100644 --- a/lib/node_modules/@stdlib/stats/strided/dmeanpw/manifest.json +++ b/lib/node_modules/@stdlib/stats/strided/dmeanpw/manifest.json @@ -23,6 +23,11 @@ "field": "libpath", "resolve": true, "relative": false + }, + { + "field": "defines", + "resolve": false, + "relative": false } ], "confs": [ @@ -46,7 +51,8 @@ "@stdlib/napi/argv-int64", "@stdlib/napi/argv-strided-float64array", "@stdlib/napi/create-double" - ] + ], + "defines": [] }, { "task": "benchmark", @@ -63,7 +69,8 @@ "@stdlib/blas/base/shared", "@stdlib/strided/base/stride2offset", "@stdlib/blas/ext/base/dsumpw" - ] + ], + "defines": [] }, { "task": "examples", @@ -80,7 +87,8 @@ "@stdlib/blas/base/shared", "@stdlib/strided/base/stride2offset", "@stdlib/blas/ext/base/dsumpw" - ] + ], + "defines": [] }, { "task": "build", @@ -97,7 +105,8 @@ "@stdlib/blas/base/shared", "@stdlib/strided/base/stride2offset", "@stdlib/blas/ext/base/dsumpw" - ] + ], + "defines": [] } ] } diff --git a/lib/node_modules/@stdlib/stats/strided/wasm/dmeanpw/manifest.json b/lib/node_modules/@stdlib/stats/strided/wasm/dmeanpw/manifest.json index f2119fbaaa62..0f37f2639396 100644 --- a/lib/node_modules/@stdlib/stats/strided/wasm/dmeanpw/manifest.json +++ b/lib/node_modules/@stdlib/stats/strided/wasm/dmeanpw/manifest.json @@ -20,6 +20,11 @@ "field": "libpath", "resolve": true, "relative": false + }, + { + "field": "defines", + "resolve": false, + "relative": false } ], "confs": [ @@ -30,7 +35,8 @@ "libpath": [], "dependencies": [ "@stdlib/stats/strided/dmeanpw" - ] + ], + "defines": [] } ] } diff --git a/lib/node_modules/@stdlib/stats/strided/wasm/dmeanpw/src/Makefile b/lib/node_modules/@stdlib/stats/strided/wasm/dmeanpw/src/Makefile index d263d9b6d777..c20c1291dd60 100644 --- a/lib/node_modules/@stdlib/stats/strided/wasm/dmeanpw/src/Makefile +++ b/lib/node_modules/@stdlib/stats/strided/wasm/dmeanpw/src/Makefile @@ -25,6 +25,9 @@ # VARIABLES # +# Define the default target before including generated dependency rules: +.DEFAULT_GOAL := all + ifndef VERBOSE QUIET := @ else @@ -59,6 +62,13 @@ else EMCC := emcc endif +# Define the program used for compiling C++ source files to WebAssembly: +ifdef EMXX_COMPILER + EMXX := $(EMXX_COMPILER) +else + EMXX := em++ +endif + # Define the program used for compiling WebAssembly files to the WebAssembly text format: ifdef WASM2WAT WASM_TO_WAT := $(WASM2WAT) @@ -99,6 +109,89 @@ CFLAGS ?= \ # Define the command-line options when compiling C files to WebAssembly and asm.js: EMCCFLAGS ?= $(CFLAGS) +# List of includes (e.g., `-I /foo/bar -I /beep/boop/include`): +INCLUDE ?= + +# List of preprocessor definitions (e.g., `-D NAME=value`): +DEFINES ?= + +# List of source files: +SOURCE_FILES ?= + +# Define the SIMD backend: +SIMD_BACKEND ?= + +# Define a path to a Highway installation include directory: +DEPS_HIGHWAY_INCLUDE ?= + +# Define a directory for intermediate build files: +OBJ_DIR := objs + +# Define the current SIMD backend name: +SIMD_BACKEND_NAME := $(if $(strip $(SIMD_BACKEND)),$(strip $(SIMD_BACKEND)),scalar) + +# Define the current SIMD build configuration: +SIMD_CONFIG := $(SIMD_BACKEND_NAME)$(if $(filter highway,$(SIMD_BACKEND_NAME)),:$(DEPS_HIGHWAY_INCLUDE)):$(INT_TYPE):$(DEFINES) + +# Define a file for recording the SIMD build configuration: +SIMD_CONFIG_FILE := $(OBJ_DIR)/simd-config + +ifeq ($(SIMD_BACKEND),highway) + +# Define the command-line options when compiling C++ files: +CXXFLAGS ?= \ + -std=c++17 \ + -O3 \ + -flto \ + -Wall \ + -pedantic \ + -Wno-gnu-zero-variadic-macro-arguments + +# Preserve shared user options without passing the C language standard to C++ +# compilation or linking. This also keeps the C and C++ integer ABI consistent: +EMCC_CXX_FLAGS := $(filter-out -std=%,$(EMCCFLAGS)) + +# Define command-line options shared by compilation and linking: +EMCC_COMMON_FLAGS := \ + -Oz \ + -flto \ + -fwasm-exceptions \ + -msimd128 \ + -ffp-contract=off + +# Define WebAssembly linker options: +EMCC_LINK_FLAGS := $(EMCC_COMMON_FLAGS) \ + -s SUPPORT_LONGJMP=1 \ + -s SIDE_MODULE=2 \ + -s WASM=1 \ + -s WASM_BIGINT=0 \ + -s EXPORTED_FUNCTIONS="$(shell cat exports.json | tr -d ' \t\n' | sed s/\"/\'/g)" + +# Split source files by language so that each compiler receives the appropriate +# language standard: +C_SOURCES := $(filter %.c,$(SOURCE_FILES)) +CXX_SOURCES := $(filter %.cpp %.cc,$(SOURCE_FILES)) + +# Maps a source path to an object filename: +source_to_object = $(OBJ_DIR)/$(subst :,_,$(subst /,_,$(1))).o + +# Maps a source path to a dependency filename: +source_to_dependency = $(OBJ_DIR)/$(subst :,_,$(subst /,_,$(1))).d + +# List of C and C++ object files: +C_OBJS := $(foreach source,$(C_SOURCES),$(call source_to_object,$(source))) +CXX_OBJS := $(foreach source,$(CXX_SOURCES),$(call source_to_object,$(source))) +ALL_OBJS := $(C_OBJS) $(CXX_OBJS) + +# List of C and C++ dependency files: +DEP_FILES := $(foreach source,$(C_SOURCES) $(CXX_SOURCES),$(call source_to_dependency,$(source))) + +-include $(DEP_FILES) + +$(ALL_OBJS): $(SIMD_CONFIG_FILE) + +else + # Define shared `emcc` flags: EMCC_SHARED_FLAGS := \ -Oz \ @@ -112,11 +205,7 @@ EMCC_WASM_FLAGS := $(EMCC_SHARED_FLAGS) \ -s WASM=1 \ -s WASM_BIGINT=0 -# List of includes (e.g., `-I /foo/bar -I /beep/boop/include`): -INCLUDE ?= - -# List of source files: -SOURCE_FILES ?= +endif # List of libraries (e.g., `-lopenblas -lpthread`): LIBRARIES ?= @@ -144,10 +233,14 @@ browser_js_targets := ./../lib/binary.browser.js # # @param {string} [EMCC_COMPILER] - EMCC compiler (e.g., `emcc`) # @param {string} [EMCCFLAGS] - EMCC compiler options +# @param {string} [EMXX_COMPILER] - EMXX compiler (e.g., `em++`) +# @param {string} [SIMD_BACKEND] - SIMD backend (e.g., `highway`) +# @param {string} [DEPS_HIGHWAY_INCLUDE] - path to a Highway installation include directory # @param {string} [WASM2WAT] - WebAssembly text format compiler (e.g., `wasm2wat`) # @param {string} [WASM2JS] - WebAssembly JavaScript compiler (e.g., `wasm2js`) # @param {string} [INCLUDE] - list of includes (e.g., `-I /foo/bar -I /beep/boop/include`) # @param {string} [SOURCE_FILES] - list of source files +# @param {string} [DEFINES] - list of preprocessor definitions # @param {string} [LIBPATH] - list of library paths (e.g., `-L /foo/bar -L /beep/boop`) # @param {string} [LIBRARIES] - list of libraries (e.g., `-lopenblas -lpthread`) # @@ -166,10 +259,14 @@ all: wasm # # @param {string} [EMCC_COMPILER] - EMCC compiler (e.g., `emcc`) # @param {string} [EMCCFLAGS] - EMCC compiler options +# @param {string} [EMXX_COMPILER] - EMXX compiler (e.g., `em++`) +# @param {string} [SIMD_BACKEND] - SIMD backend (e.g., `highway`) +# @param {string} [DEPS_HIGHWAY_INCLUDE] - path to a Highway installation include directory # @param {string} [WASM2WAT] - WebAssembly text format compiler (e.g., `wasm2wat`) # @param {string} [WASM2JS] - WebAssembly JavaScript compiler (e.g., `wasm2js`) # @param {string} [INCLUDE] - list of includes (e.g., `-I /foo/bar -I /beep/boop/include`) # @param {string} [SOURCE_FILES] - list of source files +# @param {string} [DEFINES] - list of preprocessor definitions # @param {string} [LIBPATH] - list of library paths (e.g., `-L /foo/bar -L /beep/boop`) # @param {string} [LIBRARIES] - list of libraries (e.g., `-lopenblas -lpthread`) # @@ -181,18 +278,78 @@ wasm: $(wasm_targets) $(wat_targets) $(browser_js_targets) .PHONY: wasm #/ -# Compiles C source files to WebAssembly binaries. +# Compiles source files to WebAssembly binaries. # # @private # @param {string} EMCC - EMCC compiler (e.g., `emcc`) # @param {string} EMCCFLAGS - EMCC compiler options +# @param {string} EMXX - EMXX compiler (e.g., `em++`) # @param {string} INCLUDE - list of includes (e.g., `-I /foo/bar`) # @param {string} SOURCE_FILES - list of source files # @param {string} LIBPATH - list of library paths (e.g., `-L /foo/bar`) # @param {string} LIBRARIES - list of libraries (e.g., `-lopenblas`) #/ -$(wasm_targets): - $(QUIET) $(EMCC) $(EMCCFLAGS) $(EMCC_WASM_FLAGS) $(INCLUDE) -o $@ $(SOURCE_FILES) $< $(LIBPATH) $(LIBRARIES) +ifeq ($(SIMD_BACKEND),highway) + +$(wasm_targets): $(SIMD_CONFIG_FILE) $(ALL_OBJS) + $(QUIET) $(EMXX) $(EMCC_CXX_FLAGS) $(EMCC_LINK_FLAGS) -o $@ $(ALL_OBJS) $(LIBPATH) $(LIBRARIES) + +define compile_c_rule +$(call source_to_object,$(1)): $(1) + $(QUIET) mkdir -p $(OBJ_DIR) + $(QUIET) $(EMCC) $(EMCCFLAGS) $(EMCC_COMMON_FLAGS) $(DEFINES) -I$(DEPS_HIGHWAY_INCLUDE) $(INCLUDE) -MMD -MP -MF $(call source_to_dependency,$(1)) -c $(1) -o $$@ +endef + +define compile_cxx_rule +$(call source_to_object,$(1)): $(1) + $(QUIET) mkdir -p $(OBJ_DIR) + $(QUIET) $(EMXX) $(EMCC_CXX_FLAGS) $(CXXFLAGS) $(EMCC_COMMON_FLAGS) $(DEFINES) -I$(DEPS_HIGHWAY_INCLUDE) $(INCLUDE) -MMD -MP -MF $(call source_to_dependency,$(1)) -c $(1) -o $$@ +endef + +$(foreach source,$(C_SOURCES),$(eval $(call compile_c_rule,$(source)))) +$(foreach source,$(CXX_SOURCES),$(eval $(call compile_cxx_rule,$(source)))) + +else + +$(wasm_targets): $(SIMD_CONFIG_FILE) $(SOURCE_FILES) + $(QUIET) $(EMCC) $(EMCCFLAGS) $(EMCC_WASM_FLAGS) $(DEFINES) $(INCLUDE) -o $@ $(SOURCE_FILES) $(LIBPATH) $(LIBRARIES) + +endif + +#/ +# Records the SIMD build configuration. +# +# @private +#/ +$(SIMD_CONFIG_FILE): FORCE | validate-simd-config + $(QUIET) mkdir -p $(OBJ_DIR) + $(QUIET) if [ ! -f "$@" ] || [ "$$(cat "$@")" != "$(SIMD_CONFIG)" ]; then \ + echo "$(SIMD_CONFIG)" > "$@"; \ + fi + +#/ +# Forces a check of the SIMD build configuration. +# +# @private +#/ +FORCE: + +.PHONY: FORCE + +#/ +# Validates the SIMD build configuration. +# +# @private +#/ +validate-simd-config: +ifeq ($(SIMD_BACKEND),highway) +ifeq ($(strip $(DEPS_HIGHWAY_INCLUDE)),) + $(QUIET) echo 'Error: DEPS_HIGHWAY_INCLUDE must be set when SIMD_BACKEND=highway.' >&2 + $(QUIET) exit 1 +endif +endif + +.PHONY: validate-simd-config #/ # Compiles WebAssembly binary files to the WebAssembly text format. @@ -230,6 +387,7 @@ $(browser_js_targets): $(wasm_targets) #/ clean-wasm: $(QUIET) -rm -f *.wasm *.wat *.wasm.js $(browser_js_targets) + $(QUIET) -rm -rf $(OBJ_DIR) .PHONY: clean-wasm diff --git a/lib/node_modules/@stdlib/stats/strided/wasm/dmeanpw/test/test.main.js b/lib/node_modules/@stdlib/stats/strided/wasm/dmeanpw/test/test.main.js index 3e1ab4ea5fe7..497c8115353d 100644 --- a/lib/node_modules/@stdlib/stats/strided/wasm/dmeanpw/test/test.main.js +++ b/lib/node_modules/@stdlib/stats/strided/wasm/dmeanpw/test/test.main.js @@ -58,6 +58,54 @@ tape( 'the `main` method calculates the arithmetic mean of a strided array', fun t.end(); }); +tape( 'the `main` method supports arrays spanning a pairwise block boundary', function test( t ) { + var x; + var n; + var i; + var v; + + x = new Float64Array( 129 ); + for ( i = 0; i < x.length; i++ ) { + x[ i ] = i + 1; + } + for ( n = 128; n <= 129; n++ ) { + v = dmeanpw.main( n, x, 1 ); + t.strictEqual( v, ( n + 1 ) / 2, 'returns expected value for N = '+n ); + } + t.end(); +}); + +tape( 'the `main` method supports non-multiple-of-eight array lengths', function test( t ) { + var x; + var n; + var i; + var v; + + x = new Float64Array( 255 ); + for ( i = 0; i < x.length; i++ ) { + x[ i ] = i + 1; + } + for ( n = 249; n <= 255; n++ ) { + v = dmeanpw.main( n, x, 1 ); + t.strictEqual( v, ( n + 1 ) / 2, 'returns expected value for N = '+n ); + } + t.end(); +}); + +tape( 'the `main` method supports multiple recursive pairwise splits', function test( t ) { + var x; + var i; + var v; + + x = new Float64Array( 1025 ); + for ( i = 0; i < x.length; i++ ) { + x[ i ] = i + 1; + } + v = dmeanpw.main( x.length, x, 1 ); + t.strictEqual( v, 513.0, 'returns expected value' ); + t.end(); +}); + tape( 'if provided an `N` parameter less than or equal to `0`, the `main` method returns `NaN`', function test( t ) { var x; var v; diff --git a/lib/node_modules/@stdlib/stats/strided/wasm/dmeanpw/test/test.module.ndarray.js b/lib/node_modules/@stdlib/stats/strided/wasm/dmeanpw/test/test.module.ndarray.js index c6219224d9e7..3bf3897a838f 100644 --- a/lib/node_modules/@stdlib/stats/strided/wasm/dmeanpw/test/test.module.ndarray.js +++ b/lib/node_modules/@stdlib/stats/strided/wasm/dmeanpw/test/test.module.ndarray.js @@ -23,9 +23,12 @@ // MODULES // var tape = require( 'tape' ); +var isSameValue = require( '@stdlib/assert/is-same-value' ); var Memory = require( '@stdlib/wasm/memory' ); var isnan = require( '@stdlib/math/base/assert/is-nan' ); var Float64Array = require( '@stdlib/array/float64' ); +var PINF = require( '@stdlib/constants/float64/pinf' ); +var NINF = require( '@stdlib/constants/float64/ninf' ); var Module = require( './../lib' ).Module; @@ -71,6 +74,80 @@ tape( 'a module instance has an `ndarray` method which calculates the arithmetic t.end(); }); +tape( 'a module instance supports nonzero pointers and offsets at the end of memory when summing multiple pairwise blocks', function test( t ) { + var mem; + var mod; + var xp; + var x; + var y; + var i; + + mem = new Memory({ + 'initial': 1 + }); + mod = new Module( mem ); + mod.initializeSync(); + + x = new Float64Array( 258 ); + x[ 0 ] = NaN; + for ( i = 1; i <= 257; i++ ) { + x[ i ] = i; + } + // Skip the sentinel so that the first vector load is unaligned. + // Place the last valid double at the end of memory: + xp = mem.buffer.byteLength - x.byteLength; + mod.write( xp, x ); + + y = mod.ndarray( 257, xp, 1, 1 ); + t.strictEqual( y, 129.0, 'returns expected value' ); + t.end(); +}); + +tape( 'a module instance preserves pairwise results for large contiguous inputs with cancellation and special values', function test( t ) { + var values; + var mem; + var mod; + var xp; + var yp; + var x; + var y; + var a; + var b; + var i; + var j; + + mem = new Memory({ + 'initial': 1 + }); + mod = new Module( mem ); + mod.initializeSync(); + + x = new Float64Array( 257 ); + y = new Float64Array( 514 ); + xp = 0; + yp = x.byteLength; + values = [ + [ 1.0, 1.0e100, 1.0, -1.0e100 ], + [ -0.0 ], + [ NaN ], + [ PINF ], + [ NINF ], + [ PINF, NINF ] + ]; + for ( i = 0; i < values.length; i++ ) { + for ( j = 0; j < x.length; j++ ) { + x[ j ] = values[ i ][ j % values[ i ].length ]; + y[ 2*j ] = x[ j ]; + } + mod.write( xp, x ); + mod.write( yp, y ); + a = mod.ndarray( x.length, xp, 1, 0 ); + b = mod.ndarray( x.length, yp, 2, 0 ); + t.strictEqual( isSameValue( a, b ), true, 'matches the strided result for input '+i ); + } + t.end(); +}); + tape( 'if provided an `N` parameter less than or equal to `0`, a module instance has an `ndarray` method which returns `NaN`', function test( t ) { var mem; var mod; diff --git a/lib/node_modules/@stdlib/stats/strided/wasm/dmeanpw/test/test.ndarray.js b/lib/node_modules/@stdlib/stats/strided/wasm/dmeanpw/test/test.ndarray.js index 377d92ba4680..da94d0bfd92f 100644 --- a/lib/node_modules/@stdlib/stats/strided/wasm/dmeanpw/test/test.ndarray.js +++ b/lib/node_modules/@stdlib/stats/strided/wasm/dmeanpw/test/test.ndarray.js @@ -58,6 +58,22 @@ tape( 'the `ndarray` method calculates the arithmetic mean of a strided array', t.end(); }); +tape( 'the `ndarray` method supports an offset when summing multiple pairwise blocks', function test( t ) { + var x; + var i; + var v; + + x = new Float64Array( 259 ); + x[ 0 ] = NaN; + x[ 258 ] = NaN; + for ( i = 1; i <= 257; i++ ) { + x[ i ] = i; + } + v = dmeanpw.ndarray( 257, x, 1, 1 ); + t.strictEqual( v, 129.0, 'returns expected value' ); + t.end(); +}); + tape( 'if provided an `N` parameter less than or equal to `0`, the `ndarray` method returns `NaN`', function test( t ) { var x; var v; diff --git a/tools/make/lib/wasm/Makefile b/tools/make/lib/wasm/Makefile index 523d63229296..665f64da551a 100644 --- a/tools/make/lib/wasm/Makefile +++ b/tools/make/lib/wasm/Makefile @@ -31,7 +31,6 @@ pkgs_wasm_list_wasm_flags := "--pattern $(pkgs_wasm_pattern)" # Define the path to a script for compiling WebAssembly for a provided package path: compile_wasm_bin := $(TOOLS_DIR)/scripts/compile_wasm - # RULES # #/ @@ -49,7 +48,7 @@ compile_wasm_bin := $(TOOLS_DIR)/scripts/compile_wasm # @example # make wasm PKGS_WASM_PATTERN="blas/base/daxpy-wasm" #/ -wasm: $(NODE_MODULES) +wasm: validate-wasm-simd-backend $(NODE_MODULES) $(QUIET) $(MAKE) LIST_PKGS_WASM_FLAGS=$(pkgs_wasm_list_wasm_flags) -f $(this_file) list-pkgs-wasm | while read -r pkg; do \ if echo "$$pkg" | grep -v '^\/.*\|^[a-zA-Z]:.*' >/dev/null; then \ continue; \ @@ -62,14 +61,32 @@ wasm: $(NODE_MODULES) CLANG_COMPILER="$(DEPS_LLVM_CLANG)" \ CLANG_SYSROOT="$(DEPS_WASI_LIBC_SYSROOT)" \ EMCC_COMPILER="$(DEPS_EMSDK_EMSCRIPTEN_EMCC)" \ + EMXX_COMPILER="$(DEPS_EMSDK_EMSCRIPTEN_EMXX)" \ WASM2WAT="$(DEPS_WABT_WASM2WAT)" \ WASM2JS="$(DEPS_EMSDK_EMSCRIPTEN_WASM2JS)" \ + SIMD_BACKEND="$(SIMD_BACKEND)" \ + DEPS_HIGHWAY_INCLUDE="$(DEPS_HIGHWAY_INCLUDE)" \ "${compile_wasm_bin}" $$pkg \ || { echo "Error: failed to compile WebAssembly: $$pkg"; exit 0; } \ done .PHONY: wasm +#/ +# Validates the SIMD backend used to build WebAssembly artifacts. +# +# @private +#/ +validate-wasm-simd-backend: +ifneq ($(strip $(SIMD_BACKEND)),) +ifneq ($(strip $(SIMD_BACKEND)),highway) + $(QUIET) echo 'Error: unsupported SIMD backend.' >&2 + $(QUIET) exit 1 +endif +endif + +.PHONY: validate-wasm-simd-backend + #/ # Removes all compiled and generated WebAssembly files. # diff --git a/tools/make/lib/wasm/README.md b/tools/make/lib/wasm/README.md index aba0b95311bd..7bd9a246f48e 100644 --- a/tools/make/lib/wasm/README.md +++ b/tools/make/lib/wasm/README.md @@ -57,6 +57,11 @@ $ make wasm The command supports the following environment variables: - **PKGS_WASM_PATTERN**: package pattern; e.g., `blas/base/daxpy-wasm`. +- **SIMD_BACKEND**: optional SIMD backend for supported packages; `highway` or empty (default). + +Highway builds require WebAssembly SIMD support and do not automatically fall back to a scalar module. Clean the affected package before rebuilding with different compiler flags. + +Backend-specific source files and preprocessor definitions belong in the kernel's library manifest. Declare a `defines` field, with `resolve` and `relative` set to `false`, in each manifest along the dependency chain. The build script passes the resolved definitions to supporting package Makefiles through `DEFINES`; consumers do not need to name their dependencies' backend macros. If unable to compile WebAssemby artifacts, the command prints an error message and tries compiling WebAssembly artifacts for the next package. diff --git a/tools/make/test/test.highway.js b/tools/make/test/test.highway.js index 2665c974d679..5efbd222fa33 100644 --- a/tools/make/test/test.highway.js +++ b/tools/make/test/test.highway.js @@ -23,6 +23,7 @@ // MODULES // var resolve = require( 'path' ).resolve; +var execFile = require( 'child_process' ).execFile; var env = require( 'process' ).env; var tape = require( 'tape' ); var IS_BROWSER = require( '@stdlib/assert/is-browser' ); @@ -48,6 +49,9 @@ var opts = { var runtimeOpts = { 'skip': IS_BROWSER || !env.STDLIB_TEST_HIGHWAY_RUNTIME }; +var cliOpts = { + 'skip': IS_BROWSER || IS_WINDOWS +}; // TESTS // @@ -78,6 +82,75 @@ tape( 'the Highway native manifest selects the SIMD implementation', opts, funct t.end(); }); +tape( 'Wasm consumers inherit the selected DSUMPW implementation', opts, function test( t ) { + var consumers; + var backends; + var expected; + var source; + var file; + var conf; + var src; + var i; + var j; + var k; + + consumers = [ + '@stdlib/stats/strided/wasm/dmeanpw' + ]; + backends = [ '', 'highway' ]; + source = resolve( dir, 'src/simd/dsumpw_highway_wasm.cpp' ); + for ( i = 0; i < consumers.length; i++ ) { + file = resolve( root, consumers[ i ], 'manifest.json' ); + for ( j = 0; j < backends.length; j++ ) { + conf = manifest( file, { + 'wasm': true, + 'simd': backends[ j ] + }, mopts ); + src = []; + for ( k = 0; k < conf.src.length; k++ ) { + if ( /dsumpw_highway[^/]*\.cpp$/.test( conf.src[ k ] ) ) { + src.push( resolve( file, '..', conf.src[ k ] ) ); + } + } + expected = ( backends[ j ] ) ? [ 'STDLIB_BLAS_EXT_BASE_DSUMPW_SIMD_HIGHWAY' ] : []; + t.deepEqual( conf.defines, expected, consumers[ i ]+': inherits the backend definition' ); + expected = ( backends[ j ] ) ? [ source ] : []; + t.deepEqual( src, expected, consumers[ i ]+': selects the expected Highway sources without duplicates' ); + if ( !backends[ j ] ) { + t.deepEqual( conf, manifest( file, { + 'wasm': true + }, mopts ), consumers[ i ]+': an empty backend preserves the default configuration' ); + } + } + } + t.end(); +}); + +tape( 'the Wasm build stops when resolving preprocessor definitions fails', cliOpts, function test( t ) { + var options; + var script; + + script = resolve( __dirname, '..', '..', 'scripts', 'compile_wasm' ); + options = { + 'env': { + 'PATH': env.PATH, + 'NODE': 'false', + 'INCLUDE': 'unused', + 'SOURCE_FILES': 'unused', + 'LIBRARIES': 'unused', + 'LIBPATH': 'unused' + } + }; + execFile( 'bash', [ script, __dirname ], options, done ); + + function done( error, stdout, stderr ) { + t.ok( error, 'returns an error' ); + t.strictEqual( contains( stderr, 'Resolving preprocessor definitions...' ), true, 'attempts to resolve definitions' ); + t.strictEqual( contains( stderr, 'Compiling WebAssembly...' ), false, 'does not attempt compilation' ); + t.end(); + } +}); + tape( 'the native runtime exports its include directory and static library', runtimeOpts, function test( t ) { var conf = JSON.parse( readFileSync( resolve( env.STDLIB_TEST_HIGHWAY_RUNTIME, 'highway.json' ), { 'encoding': 'utf8' diff --git a/tools/scripts/compile_wasm b/tools/scripts/compile_wasm index 1a8a37c21d3c..beab56fef17f 100755 --- a/tools/scripts/compile_wasm +++ b/tools/scripts/compile_wasm @@ -32,13 +32,17 @@ # CLANG_COMPILER LLVM Clang compiler. Default: `clang`. # CLANG_SYSROOT Path to a C standard library. # EMCC_COMPILER Emscripten C compiler. Default: `emcc`. +# EMXX_COMPILER Emscripten C++ compiler. Default: `em++`. # WASM2WAT Command for converting WebAssembly binary format to text format. Default. `wasm2wat`. # WASM2JS Command for converting WebAssembly binary format to JavaScript. Default. `wasm2js`. # SRC_FOLDER Folder containing source files. Default: `src`. # INCLUDE Includes (e.g., `-I /foo/bar -I /a/b`). # SOURCE_FILES Source file list. +# DEFINES Preprocessor definitions (e.g., `-D NAME=value`). # LIBRARIES Linked libraries (e.g., `-lopenblas -lpthreads`). # LIBPATH Library paths (e.g., `-L /foo/bar -L /a/b`). +# SIMD_BACKEND SIMD backend. Default: empty. +# DEPS_HIGHWAY_INCLUDE Path to a Highway installation include directory. Default: empty. # # shellcheck disable=SC2181,SC2153 @@ -70,6 +74,12 @@ if [[ -z "${emcc_compiler}" ]]; then emcc_compiler='emcc' fi +# Define the path to the Emscripten `em++` compiler: +emxx_compiler="${EMXX_COMPILER}" +if [[ -z "${emxx_compiler}" ]]; then + emxx_compiler='em++' +fi + # Define the path to an executable for converting a WebAssembly binary to the WebAssembly text format: wasm_to_wat="${WASM2WAT}" if [[ -z "${wasm_to_wat}" ]]; then @@ -97,12 +107,21 @@ include="${INCLUDE}" # Define a list of external source files: source_files="${SOURCE_FILES}" +# Define a list of preprocessor definitions: +defines="${DEFINES}" + # Define a list of libraries (e.g., `-lopenblas -lpthreads`): libraries="${LIBRARIES}" # Define a list of library paths (e.g., `-L /foo/bar -L /beep/boop`): libpath="${LIBPATH}" +# Define the SIMD backend: +simd_backend="${SIMD_BACKEND}" + +# Define a path to a Highway installation include directory: +deps_highway_include="${DEPS_HIGHWAY_INCLUDE}" + # FUNCTIONS # @@ -120,6 +139,19 @@ cleanup() { echo '' >&2 } +# Validates the SIMD backend. +validate_simd_backend() { + case "${simd_backend}" in + ''|'highway') + return 0 + ;; + *) + echo "ERROR: unsupported SIMD backend: ${simd_backend}." >&2 + return 1 + ;; + esac +} + # Prints a success message. print_success() { echo 'Success!' >&2 @@ -140,7 +172,7 @@ resolve_includes() { local script local opts - opts="{'wasm':true,'os':''}" + opts="{'wasm':true,'os':'','simd':'${simd_backend}'}" # Generate the script for resolving external include directories: script='"'"var path = require('path'); var arr = require('@stdlib/utils/library-manifest')(path.join('$1','manifest.json'),${opts},{'basedir':'$1','paths':'posix'}).include; var str = ''; for (var i = 0; i < arr.length; i++){var p = path.resolve('$1', arr[i]); if (p.indexOf('$1') === 0) {continue;}; str += '-I '+p+' ';}; console.log(str.substring(0, str.length-1));"'"' @@ -159,7 +191,7 @@ resolve_source_files() { local script local opts - opts="{'wasm':true,'os':''}" + opts="{'wasm':true,'os':'','simd':'${simd_backend}'}" # Generate the script for resolving external source files: script='"'"var path = require('path'); var arr = require('@stdlib/utils/library-manifest')(path.join('$1','manifest.json'),${opts},{'basedir':'$1','paths':'posix'}).src; var str = ''; for (var i = 0; i < arr.length; i++){var p = path.resolve('$1', arr[i]); if (p.indexOf('$1') === 0) {continue;}; str += p+' ';}; console.log(str.substring(0, str.length-1));"'"' @@ -170,6 +202,28 @@ resolve_source_files() { echo "${source_files}" } +# Resolves preprocessor definitions. +# +# $1 - package directory +resolve_defines() { + local defines + local script + local opts + + opts="{'wasm':true,'os':'','simd':'${simd_backend}'}" + + # Generate the script for resolving preprocessor definitions: + script='"'"var path = require('path'); var arr = require('@stdlib/utils/library-manifest')(path.join('$1','manifest.json'),${opts},{'basedir':'$1','paths':'posix'}).defines || []; var str = ''; for (var i = 0; i < arr.length; i++){str += '-D '+arr[i]+' ';}; console.log(str.substring(0, str.length-1));"'"' + + # Resolve preprocessor definitions: + defines=$(eval NODE_PATH="${node_path}" "${node_cmd}" -e "${script}") + if [[ "$?" -ne 0 ]]; then + return 1 + fi + + echo "${defines}" +} + # Resolves libraries. # # $1 - package directory @@ -178,7 +232,7 @@ resolve_libraries() { local script local opts - opts="{'wasm':true,'os':''}" + opts="{'wasm':true,'os':'','simd':'${simd_backend}'}" # Generate the script for resolving libraries: script='"'"var path = require('path'); var arr = require('@stdlib/utils/library-manifest')(path.join('$1','manifest.json'),${opts},{'basedir':'$1','paths':'posix'}).libraries; var str = ''; for (var i = 0; i < arr.length; i++){str += arr[i]+' ';}; console.log(str.substring(0, str.length-1));"'"' @@ -197,7 +251,7 @@ resolve_libpaths() { local script local opts - opts="{'wasm':true,'os':''}" + opts="{'wasm':true,'os':'','simd':'${simd_backend}'}" # Generate the script for resolving library paths: script='"'"var path = require('path'); var arr = require('@stdlib/utils/library-manifest')(path.join('$1','manifest.json'),${opts},{'basedir':'$1','paths':'posix'}).libpath; var str = ''; for (var i = 0; i < arr.length; i++){var p = path.resolve('$1', arr[i]); str += '-L '+p+' ';}; console.log(str.substring(0, str.length-1));"'"' @@ -212,7 +266,7 @@ resolve_libpaths() { # # $1 - source directory compile() { - cd "$1" && CLANG_COMPILER="${clang_compiler}" CLANG_SYSROOT="${clang_sysroot}" EMCC_COMPILER="${emcc_compiler}" WASM2WAT="${wasm_to_wat}" WASM2JS="${wasm_to_js}" INCLUDE="${include}" SOURCE_FILES="${source_files}" LIBRARIES="${libraries}" LIBPATH="${libpath}" make wasm 2>&1 + cd "$1" && CLANG_COMPILER="${clang_compiler}" CLANG_SYSROOT="${clang_sysroot}" EMCC_COMPILER="${emcc_compiler}" EMXX_COMPILER="${emxx_compiler}" WASM2WAT="${wasm_to_wat}" WASM2JS="${wasm_to_js}" INCLUDE="${include}" SOURCE_FILES="${source_files}" DEFINES="${defines}" LIBRARIES="${libraries}" LIBPATH="${libpath}" SIMD_BACKEND="${simd_backend}" DEPS_HIGHWAY_INCLUDE="${deps_highway_include}" make wasm 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Error when attempting to compile WebAssembly.' >&2 return 1 @@ -228,6 +282,10 @@ compile() { main() { local src_dir="${pkg_path}/${src_folder}" + validate_simd_backend + if [[ "$?" -ne 0 ]]; then + on_error 1 + fi if [[ -z "${include}" ]]; then echo 'Resolving external include directories...' >&2 include=$(resolve_includes "${pkg_path}") @@ -242,6 +300,13 @@ main() { on_error 1 fi fi + if [[ -z "${defines}" ]]; then + echo 'Resolving preprocessor definitions...' >&2 + defines=$(resolve_defines "${pkg_path}") + if [[ "$?" -ne 0 ]]; then + on_error 1 + fi + fi if [[ -z "${libraries}" ]]; then echo 'Resolving libraries...' >&2 libraries=$(resolve_libraries "${pkg_path}")