Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ option(DYNAMIC_OLDER "Include specific support for older x86 cpu models (Penryn,

option(BUILD_RELAPACK "Build with ReLAPACK (recursive implementation of several LAPACK functions on top of standard LAPACK)" OFF)

option(WASM_RELAXED_SIMD "WASM: emit relaxed SIMD opcodes (not portable to engines without the feature, including shipping Safari)" OFF)

option(USE_LOCKING "Use locks even in single-threaded builds to make them callable from multiple threads" OFF)

option(USE_PERL "Use the older PERL scripts for build preparation instead of universal shell scripts" OFF)
Expand Down
8 changes: 8 additions & 0 deletions Makefile.rule
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,14 @@ COMMON_PROF = -pg
# If you want to enable the experimental HFLOAT16 support
# BUILD_HFLOAT16 = 1

# WASM only: emit WebAssembly relaxed SIMD (for example f32x4.relaxed_madd)
# in kernels that support it. Off by default because a module that contains
# those opcodes will not instantiate on engines without the relaxed-simd
# feature, including shipping Safari. See docs/install.md#webassembly and
# https://webassembly.org/features/
# WASM_RELAXED_SIMD = 1


# Set the thread number threshold beyond which the job array for the threaded level3 BLAS
# will be allocated on the heap rather than the stack. (This array alone requires
# NUM_THREADS*NUM_THREADS*128 bytes of memory so should not pose a problem at low cpu
Expand Down
10 changes: 9 additions & 1 deletion Makefile.wasm
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
CCOMMON_OPT += -msimd128 -mrelaxed-simd
# Portable WASM SIMD128. Do not pass -mrelaxed-simd by default: the resulting
# module contains opcodes that engines without the relaxed-simd feature reject
# at instantiate time (shipping Safari / JavaScriptCore among them). Engine
# support is tracked at https://webassembly.org/features/
# Opt in with WASM_RELAXED_SIMD=1 (see Makefile.rule and docs/install.md).
CCOMMON_OPT += -msimd128
ifeq ($(WASM_RELAXED_SIMD), 1)
CCOMMON_OPT += -mrelaxed-simd
endif
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,9 @@ e.g.:

- **WASM128_GENERIC**: Optimized SGEMM,DGEMM, DAXPY, SSUM/DSUM, SDOT/DDOT and SROT/DROT

Builds target [WASM SIMD128](https://github.com/WebAssembly/simd) (`-msimd128`) by default. Relaxed SIMD is **not** enabled globally: a module that contains those opcodes fails to instantiate on engines that do not implement the proposal (notably shipping Safari / JavaScriptCore). Engine support is listed at [webassembly.org/features](https://webassembly.org/features/). Pass `WASM_RELAXED_SIMD=1` (or `-DWASM_RELAXED_SIMD=ON` with CMake) to opt in. Details: [docs/install.md](docs/install.md#webassembly).



### Support for multiple targets in a single library

Expand Down
8 changes: 8 additions & 0 deletions cmake/arch.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,11 @@ if (RISCV64)
set(BINARY_DEFINED 1)
endif ()

if (WASM)
set(CCOMMON_OPT "${CCOMMON_OPT} -msimd128")
if (WASM_RELAXED_SIMD)
set(CCOMMON_OPT "${CCOMMON_OPT} -mrelaxed-simd")
endif ()
endif ()


4 changes: 4 additions & 0 deletions docs/build_system.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ though - please read the linked Makefiles if you want to see all variables.
- `FCOMMON_OPT`: flags to add to all invocations of the target Fortran compiler
(overrides `FFLAGS`)
- `LDFLAGS`: flags to add to all target linker invocations
- `WASM_RELAXED_SIMD`: WASM only; if set to `1`, pass `-mrelaxed-simd` so
kernels can emit relaxed SIMD FMA. Off by default because the binary will
not load on engines without the feature (see
[install.md](install.md#webassembly)).
- `AR`, `AS`, `LD`, `RANLIB`: `TARGET` toolchain helpers used for compilation
(can be cross-toolchains).
- `HOSTCC`: compiler of build machine, needed to create proper config files for
Expand Down
56 changes: 56 additions & 0 deletions docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,62 @@ riscv64-linux-gnu-objdump -d libopenblas*.a | \
returns approximately 12,000-14,000 (GCC 14: ~12,691; GCC 15: ~14,355).


### WebAssembly

OpenBLAS can be cross-compiled with [Emscripten](https://emscripten.org/)
(`TARGET=WASM128_GENERIC`). `Makefile.wasm` always passes `-msimd128` so
kernels can use the portable [WASM SIMD128](https://github.com/WebAssembly/simd)
instruction set (128-bit `v128` vectors).

#### Relaxed SIMD

[Relaxed SIMD](https://github.com/WebAssembly/relaxed-simd) is a follow-on
proposal. It keeps the same 128-bit vectors but allows a few operations —
in particular fused multiply-add (`f32x4.relaxed_madd` /
`f64x2.relaxed_madd`) — to map to native hardware FMA. The result may use
one rounding or two, depending on the CPU, so it is not bit-identical across
engines.

OpenBLAS kernels already select `relaxed_madd` when the compiler defines
`__wasm_relaxed_simd__` (from `-mrelaxed-simd`), and fall back to a separate
multiply then add otherwise. That compile-time split is not enough for a
single portable binary: WebAssembly validates the whole module, so any
relaxed-SIMD opcode causes instantiate to fail on an engine that does not
implement the feature. There is no in-module runtime dispatch.

Engine support is tracked at [webassembly.org/features](https://webassembly.org/features/).
As of 2026, shipping Safari / JavaScriptCore still lacks relaxed SIMD
(Safari Technology Preview has it; a page cannot enable the JSC flag
`useWasmRelaxedSIMD` itself). Chrome/V8 and current Firefox do support it.

For a library that must load in every current browser, leave relaxed SIMD
**off** (the default). Opt in only when every target engine is known to
support it, or when you ship a second binary and select it from JavaScript
with [wasm-feature-detect](https://github.com/GoogleChromeLabs/wasm-feature-detect).

```bash
# Portable default (SIMD128 only)
make TARGET=WASM128_GENERIC \
HOSTCC=gcc CC=emcc AR=emar RANLIB=emranlib \
USE_THREAD=0 NOFORTRAN=1

# Relaxed SIMD (FMA); will not instantiate on engines without the feature
make TARGET=WASM128_GENERIC WASM_RELAXED_SIMD=1 \
HOSTCC=gcc CC=emcc AR=emar RANLIB=emranlib \
USE_THREAD=0 NOFORTRAN=1
```

With CMake, pass `-DWASM_RELAXED_SIMD=ON`.

After the tree is configured for WASM (`ARCH=wasm` in `Makefile.conf`), check
that the default build stays SIMD128-only and that the opt-in path emits
`relaxed_madd`:

```bash
./kernel/wasm/test_relaxed_simd.sh
```


### FreeBSD

You will need to install the following tools from the FreeBSD ports tree:
Expand Down
238 changes: 238 additions & 0 deletions kernel/wasm/test_relaxed_simd.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
#!/usr/bin/env bash
# Verify WASM relaxed SIMD is opt-in:
# * the default compile does not pass -mrelaxed-simd and emits no relaxed_madd
# * WASM_RELAXED_SIMD=1 passes the flag and kernels use relaxed_madd
#
# Requires a WASM-configured tree (ARCH=wasm in Makefile.conf), emcc, and
# wasm-dis (Binaryen, shipped with Emscripten). Node is used to instantiate
# the default module when available.
set -euo pipefail

ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
JOBS="${JOBS:-$(nproc 2>/dev/null || echo 4)}"
FAILS=0

activate_emscripten() {
if command -v emcc >/dev/null 2>&1; then
return 0
fi
local prefix="${OPENBLAS_EM_PREFIX:-}"
if [[ -z "$prefix" && -n "${CONDA_PREFIX:-}" && -f "${CONDA_PREFIX}/bin/activate_emscripten.sh" ]]; then
prefix="$CONDA_PREFIX"
fi
if [[ -z "$prefix" && -n "${EMSDK:-}" && -x "${EMSDK}/upstream/emscripten/emcc" ]]; then
export PATH="${EMSDK}/upstream/emscripten:${EMSDK}/upstream/bin:${PATH}"
return 0
fi
if [[ -z "$prefix" && -f "$ROOT/.em-prefix/bin/activate_emscripten.sh" ]]; then
prefix="$ROOT/.em-prefix"
fi
if [[ -z "$prefix" || ! -f "$prefix/bin/activate_emscripten.sh" ]]; then
echo "emcc not found. Set OPENBLAS_EM_PREFIX to an Emscripten prefix." >&2
exit 1
fi
export CONDA_PREFIX="$prefix"
export PREFIX="${PREFIX:-$prefix}"
export PATH="$prefix/bin:$prefix/opt/emsdk/upstream/emscripten:$prefix/opt/emsdk/upstream/bin:$PATH"
# shellcheck disable=SC1091
source "$prefix/bin/activate_emscripten.sh"
}

count_op() {
# wasm-dis prints a warning on relocatable objects; keep stdout only.
local n
n="$(wasm-dis "$1" 2>/dev/null | grep -c "$2" || true)"
echo "${n:-0}"
}

expect() {
local name="$1" got="$2" pred="$3" want="$4"
if [[ "$pred" == "eq" && "$got" == "$want" ]]; then
echo "PASS $name ($got)"
return 0
fi
if [[ "$pred" == "gt" && "$got" -gt "$want" ]]; then
echo "PASS $name ($got > $want)"
return 0
fi
echo "FAIL $name (got $got, expected $pred $want)" >&2
FAILS=$((FAILS + 1))
}

activate_emscripten
cd "$ROOT"

if ! command -v wasm-dis >/dev/null 2>&1; then
echo "wasm-dis not found (Binaryen). It is usually on PATH after activating Emscripten." >&2
exit 1
fi

if [[ ! -f Makefile.conf ]] || ! grep -q '^ARCH=wasm$' Makefile.conf; then
echo "Makefile.conf is not a WASM config (need ARCH=wasm)." >&2
echo "Configure first, for example:" >&2
echo " make TARGET=WASM128_GENERIC HOSTCC=gcc CC=emcc AR=emar RANLIB=emranlib USE_THREAD=0 NOFORTRAN=1 NO_LAPACK=1" >&2
exit 1
fi

MAKE_COMMON=(
HOSTCC=gcc
CC=emcc
AR=emar
RANLIB=emranlib
TARGET=WASM128_GENERIC
USE_THREAD=0
NOFORTRAN=1
NO_LAPACK=1
COLORCODE=0
)

# Kernels that emit madd when __wasm_relaxed_simd__ is set.
OBJS=(
sgemm_kernel.o
dgemm_kernel.o
cgemm_kernel_n.o
zgemm_kernel_n.o
sdot_k.o
ddot_k.o
dsdot_k.o
srot_k.o
dtrmm_kernel_LN.o
)

EXPORTS=_sgemm_kernel,_dgemm_kernel,_cgemm_kernel_n,_zgemm_kernel_n,_sdot_k,_ddot_k,_dsdot_k,_srot_k,_dtrmm_kernel_LN

MAKE_BIN="${MAKE:-make}"

sgemm_line() {
local extra=("$@")
rm -f kernel/sgemm_kernel.o
# GNU make -n prints the compile line only when the target is out of date.
"$MAKE_BIN" -C kernel -n sgemm_kernel.o "${MAKE_COMMON[@]}" "${extra[@]}" 2>/dev/null \
| grep -E '(^|[[:space:]])emcc[[:space:]]' | tail -1 || true
}

echo "==> compile-line flags"
DEFAULT_LINE="$(sgemm_line)"
RELAXED_LINE="$(sgemm_line WASM_RELAXED_SIMD=1)"
echo " default: $DEFAULT_LINE"
echo " relaxed: $RELAXED_LINE"
if [[ -z "$DEFAULT_LINE" || -z "$RELAXED_LINE" ]]; then
echo "FAIL could not extract an emcc compile line from make -n" >&2
exit 1
fi

case "$DEFAULT_LINE" in
*-msimd128*) expect "default passes -msimd128" 1 eq 1 ;;
*) expect "default passes -msimd128" 0 eq 1 ;;
esac
case "$DEFAULT_LINE" in
*-mrelaxed-simd*) expect "default omits -mrelaxed-simd" 1 eq 0 ;;
*) expect "default omits -mrelaxed-simd" 0 eq 0 ;;
esac
case "$RELAXED_LINE" in
*-mrelaxed-simd*) expect "WASM_RELAXED_SIMD=1 passes -mrelaxed-simd" 1 eq 1 ;;
*) expect "WASM_RELAXED_SIMD=1 passes -mrelaxed-simd" 0 eq 1 ;;
esac

OUT="$(mktemp -d "${TMPDIR:-/tmp}/openblas-relaxed-simd.XXXXXX")"
trap 'rm -rf "$OUT"' EXIT

build_variant() {
local dest="$1"
shift
rm -f "${OBJS[@]/#/kernel/}"
"$MAKE_BIN" -C kernel -j"$JOBS" "${OBJS[@]}" "${MAKE_COMMON[@]}" "$@"
mkdir -p "$dest"
local o
for o in "${OBJS[@]}"; do
cp "kernel/$o" "$dest/"
done
}

echo "==> compile default kernels"
build_variant "$OUT/default"

echo "==> compile WASM_RELAXED_SIMD=1 kernels"
build_variant "$OUT/relaxed" WASM_RELAXED_SIMD=1

sum_op() {
local dir="$1" op="$2" total=0 n
local o
for o in "$dir"/*.o; do
n="$(count_op "$o" "$op")"
total=$((total + n))
done
echo "$total"
}

DEFAULT_MADD="$(sum_op "$OUT/default" 'relaxed_madd')"
RELAXED_MADD="$(sum_op "$OUT/relaxed" 'relaxed_madd')"
DEFAULT_MUL="$(sum_op "$OUT/default" 'f32x4.mul')"
RELAXED_MUL="$(sum_op "$OUT/relaxed" 'f32x4.mul')"

echo "==> opcode counts (wasm-dis)"
echo " default relaxed_madd=$DEFAULT_MADD f32x4.mul=$DEFAULT_MUL"
echo " relaxed relaxed_madd=$RELAXED_MADD f32x4.mul=$RELAXED_MUL"

expect "default objects have no relaxed_madd" "$DEFAULT_MADD" eq 0
expect "WASM_RELAXED_SIMD=1 objects use relaxed_madd" "$RELAXED_MADD" gt 0
expect "default objects still use f32x4.mul" "$DEFAULT_MUL" gt 0

link_wasm() {
local dir="$1"
local extra=()
if [[ "${2:-}" == "relaxed" ]]; then
extra+=(-mrelaxed-simd)
fi
emcc -O2 -msimd128 "${extra[@]}" --no-entry \
-sEXPORTED_FUNCTIONS="$EXPORTS" \
-sERROR_ON_UNDEFINED_SYMBOLS=0 \
-sSTANDALONE_WASM=1 \
"$dir"/*.o -o "$dir/kernels.wasm"
}

echo "==> link and instantiate"
link_wasm "$OUT/default"
link_wasm "$OUT/relaxed" relaxed

LINKED_DEFAULT_MADD="$(count_op "$OUT/default/kernels.wasm" 'relaxed_madd')"
LINKED_RELAXED_MADD="$(count_op "$OUT/relaxed/kernels.wasm" 'relaxed_madd')"
expect "linked default module has no relaxed_madd" "$LINKED_DEFAULT_MADD" eq 0
expect "linked WASM_RELAXED_SIMD=1 module has relaxed_madd" "$LINKED_RELAXED_MADD" gt 0

if command -v node >/dev/null 2>&1; then
node -e '
const fs = require("fs");
const defb = fs.readFileSync(process.argv[1]);
const relb = fs.readFileSync(process.argv[2]);
if (!WebAssembly.validate(defb)) {
console.error("FAIL default module failed WebAssembly.validate");
process.exit(1);
}
WebAssembly.instantiate(defb).then(function () {
console.log("PASS default module instantiates (SIMD128 only)");
if (!WebAssembly.validate(relb)) {
console.log("SKIP WASM_RELAXED_SIMD=1 module does not validate on this engine (expected without relaxed SIMD)");
return;
}
return WebAssembly.instantiate(relb).then(function () {
console.log("PASS WASM_RELAXED_SIMD=1 module instantiates on this engine");
});
}).catch(function (err) {
console.error(err);
process.exit(1);
});
' "$OUT/default/kernels.wasm" "$OUT/relaxed/kernels.wasm"
else
echo "SKIP node not found; opcode checks still apply"
fi

# Rebuild default objects so a following 'make libs' is not mixed.
rm -f "${OBJS[@]/#/kernel/}"
"$MAKE_BIN" -C kernel -j"$JOBS" "${OBJS[@]}" "${MAKE_COMMON[@]}"

if [[ "$FAILS" -ne 0 ]]; then
echo "$FAILS check(s) failed" >&2
exit 1
fi
echo "relaxed-SIMD opt-in checks passed"
Loading