Skip to content
Draft
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
153 changes: 153 additions & 0 deletions bench/simd-shaping-bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/**
* HarfBuzz SIMD Performance Benchmarks
* Copyright 2025 Superstruct Ltd, New Zealand
* Licensed under MIT License
*/

import HarfBuzzModule from "../install/wasm/harfbuzz-main.js";

console.log("[Benchmark] HarfBuzz SIMD optimizations");

// Load the WASM module
const module = await HarfBuzzModule();

console.log("Module loaded successfully");

// Test data: Arabic text (complex shaping)
const arabicText = "مرحبا بك في اختبار الأداء";
const arabicCodepoints = Array.from(arabicText).map(c => c.codePointAt(0)!);

console.log(`\n[Test Data] Arabic text: "${arabicText}"`);
console.log(`[Test Data] Codepoints: ${arabicCodepoints.length} characters`);

// Allocate memory for test data
const input_ptr = module._malloc(arabicCodepoints.length * 4);
const output_ptr = module._malloc(arabicCodepoints.length * 4);

const input_arr = new Uint32Array(
module.HEAPU8.buffer,
input_ptr,
arabicCodepoints.length
);
input_arr.set(arabicCodepoints);

// Benchmark glyph positioning
console.log("\n[Test 1] Glyph positioning (SIMD vs Scalar)...");

const POSITION_COUNT = 1000;
const POSITION_ITERATIONS = 10000;

const pos_x = new Int32Array(POSITION_COUNT);
const pos_y = new Int32Array(POSITION_COUNT);
for (let i = 0; i < POSITION_COUNT; i++) {
pos_x[i] = Math.random() * 100 | 0;
pos_y[i] = Math.random() * 100 | 0;
}

const pos_x_ptr = module._malloc(POSITION_COUNT * 4);
const pos_y_ptr = module._malloc(POSITION_COUNT * 4);
const out_x_ptr = module._malloc(POSITION_COUNT * 4);
const out_y_ptr = module._malloc(POSITION_COUNT * 4);

module.HEAP32.set(pos_x, pos_x_ptr / 4);
module.HEAP32.set(pos_y, pos_y_ptr / 4);

// Scalar baseline
console.log(` Running ${POSITION_ITERATIONS} iterations (scalar)...`);
const scalar_start = performance.now();
for (let i = 0; i < POSITION_ITERATIONS; i++) {
module._hb_scalar_apply_positions(
pos_x_ptr, pos_y_ptr, out_x_ptr, out_y_ptr, POSITION_COUNT);
}
const scalar_time = performance.now() - scalar_start;

// SIMD optimized
console.log(` Running ${POSITION_ITERATIONS} iterations (SIMD)...`);
const simd_start = performance.now();
for (let i = 0; i < POSITION_ITERATIONS; i++) {
module._hb_simd_apply_positions(
pos_x_ptr, pos_y_ptr, out_x_ptr, out_y_ptr, POSITION_COUNT);
}
const simd_time = performance.now() - simd_start;

const position_speedup = scalar_time / simd_time;
console.log(` Scalar time: ${scalar_time.toFixed(0)}ms`);
console.log(` SIMD time: ${simd_time.toFixed(0)}ms`);
console.log(` Speedup: ${position_speedup.toFixed(1)}x`);

if (position_speedup < 3.0) {
console.warn(` ⚠️ WARNING: Glyph positioning speedup ${position_speedup.toFixed(1)}x < 3.0x target`);
} else {
console.log(` ✅ PASSED: Glyph positioning achieved ${position_speedup.toFixed(1)}x speedup`);
}

// Benchmark buffer operations
console.log("\n[Test 2] Buffer copy operations (SIMD vs memcpy)...");

const BUFFER_SIZE = 4096;
const BUFFER_ITERATIONS = 5000;

const src_ptr = module._malloc(BUFFER_SIZE);
const dst_ptr = module._malloc(BUFFER_SIZE);

// Fill source buffer with random data
const src_data = new Uint8Array(BUFFER_SIZE);
for (let i = 0; i < BUFFER_SIZE; i++) {
src_data[i] = Math.random() * 256 | 0;
}
module.HEAPU8.set(src_data, src_ptr);

// Standard memcpy baseline
console.log(` Running ${BUFFER_ITERATIONS} iterations (memcpy)...`);
const memcpy_start = performance.now();
for (let i = 0; i < BUFFER_ITERATIONS; i++) {
// JavaScript memcpy equivalent
const src = new Uint8Array(module.HEAPU8.buffer, src_ptr, BUFFER_SIZE);
const dst = new Uint8Array(module.HEAPU8.buffer, dst_ptr, BUFFER_SIZE);
dst.set(src);
}
const memcpy_time = performance.now() - memcpy_start;

// SIMD optimized copy
console.log(` Running ${BUFFER_ITERATIONS} iterations (SIMD)...`);
const simd_copy_start = performance.now();
for (let i = 0; i < BUFFER_ITERATIONS; i++) {
module._hb_simd_copy_buffer(dst_ptr, src_ptr, BUFFER_SIZE);
}
const simd_copy_time = performance.now() - simd_copy_start;

const copy_speedup = memcpy_time / simd_copy_time;
console.log(` memcpy time: ${memcpy_time.toFixed(0)}ms`);
console.log(` SIMD time: ${simd_copy_time.toFixed(0)}ms`);
console.log(` Speedup: ${copy_speedup.toFixed(1)}x`);

if (copy_speedup < 2.0) {
console.warn(` ⚠️ WARNING: Buffer copy speedup ${copy_speedup.toFixed(1)}x < 2.0x target`);
} else {
console.log(` ✅ PASSED: Buffer copy achieved ${copy_speedup.toFixed(1)}x speedup`);
}

// Cleanup
module._free(input_ptr);
module._free(output_ptr);
module._free(pos_x_ptr);
module._free(pos_y_ptr);
module._free(out_x_ptr);
module._free(out_y_ptr);
module._free(src_ptr);
module._free(dst_ptr);

// Summary
console.log("\n" + "=".repeat(60));
console.log("BENCHMARK SUMMARY");
console.log("=".repeat(60));
console.log(`Glyph positioning: ${position_speedup.toFixed(1)}x speedup`);
console.log(`Buffer operations: ${copy_speedup.toFixed(1)}x speedup`);

const overall_pass = position_speedup >= 3.0 && copy_speedup >= 2.0;
if (overall_pass) {
console.log("\n✅ All HarfBuzz SIMD benchmarks passed!");
} else {
console.log("\n❌ Some benchmarks did not meet target performance");
Deno.exit(1);
}
13 changes: 9 additions & 4 deletions deno.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
{
"name": "@discere-os/harfbuzz.wasm",
"version": "8.3.0",
"exports": {
".": "./",
"./side": "./install/wasm/harfbuzz-side.wasm",
"./main": "./install/wasm/harfbuzz-main.js"
},
"tasks": {
"build:main": "PKG_CONFIG_PATH=$PWD/../freetype.wasm/install/wasm/pkgconfig meson setup build-main --cross-file=scripts/emscripten.cross -Ddefault_library=static -Dtests=disabled -Dbuildtype=release --prefix=$PWD/install -Dlibdir=wasm -Dbindir=wasm && meson compile -C build-main harfbuzz-main && meson install -C build-main",
"build:side": "PKG_CONFIG_PATH=$PWD/../freetype.wasm/install/wasm/pkgconfig meson setup build-side --cross-file=scripts/emscripten.cross -Ddefault_library=static -Dtests=disabled -Dbuildtype=release --prefix=$PWD/install -Dlibdir=wasm -Dbindir=wasm -Dc_args='-msimd128' -Dc_link_args='-sSIDE_MODULE=2 -fPIC -O3 -flto' && meson compile -C build-side harfbuzz-side && meson install -C build-side && python3 scripts/postinstall.py $PWD/install",
"build:wasm": "deno task build:main && deno task build:side && deno task manifest",
"manifest": "deno run --allow-read --allow-write --allow-run ../../../../scripts/generate-wasm-manifest.ts ."
"build:wasm": "bash scripts/unified-build.sh standard",
"build:minimal": "bash scripts/unified-build.sh minimal",
"build:webgpu": "bash scripts/unified-build.sh webgpu",
"bench:simd": "deno run --allow-read --allow-ffi bench/simd-shaping-bench.ts",
"validate:performance": "deno task bench:simd",
"clean": "rm -rf build build-* install",
"demo": "deno run --allow-read demo.ts",
"test": "deno test --allow-read tests/deno/"
}
}
20 changes: 20 additions & 0 deletions dependencies.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "harfbuzz",
"dependencies": [
{
"name": "freetype",
"version": "latest",
"required": true
},
{
"name": "glib",
"version": "latest",
"required": false
},
{
"name": "icu",
"version": "latest",
"required": false
}
]
}
34 changes: 34 additions & 0 deletions emscripten-cross.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Meson Cross-Compilation Configuration for HarfBuzz WASM
# Copyright 2025 Superstruct Ltd, New Zealand
# Licensed under MIT License

[binaries]
c = 'emcc'
cpp = 'em++'
ar = 'emar'
ranlib = 'emranlib'
strip = 'emstrip'
pkgconfig = 'pkg-config'

[built-in options]
c_std = 'c11'
cpp_std = 'c++17'
default_library = 'static'
buildtype = 'release'
optimization = '3'
debug = false
b_lto = true
b_lto_mode = 'default'
b_ndebug = 'if-release'
c_link_args = ['-sSIDE_MODULE=2', '-sWASM_BIGINT=1', '-pthread']
cpp_link_args = ['-sSIDE_MODULE=2', '-sWASM_BIGINT=1', '-pthread']

[host_machine]
system = 'emscripten'
cpu_family = 'wasm32'
cpu = 'wasm32'
endian = 'little'

[properties]
needs_exe_wrapper = false
sys_root = ''
10 changes: 10 additions & 0 deletions meson_options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,22 @@ option('cmakepackagedir', type: 'string',

# WASM-specific build options
# Copyright 2025 Superstruct Ltd, New Zealand
option('wasm_build_type', type: 'combo',
choices: ['minimal', 'standard', 'webgpu'],
value: 'standard',
description: 'WASM build variant')

option('wasm_simd', type : 'boolean', value : true,
description : 'Enable WebAssembly SIMD optimizations')

option('wasm_threading', type : 'boolean', value : true,
description : 'Enable WebAssembly threading support')

option('wasm_optimize', type: 'combo',
choices: ['size', 'speed', 'balanced'],
value: 'balanced',
description: 'Optimization strategy for WASM builds')

option('wasm_webgpu', type : 'boolean', value : false,
description : 'Enable WebGPU integration for compute acceleration')

Expand Down
70 changes: 70 additions & 0 deletions scripts/unified-build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/bin/bash
# HarfBuzz Unified Meson Build Script
# Copyright 2025 Superstruct Ltd, New Zealand
set -euo pipefail

BUILD_TYPE="${1:-standard}"

echo "[Build] HarfBuzz unified Meson build: $BUILD_TYPE"

# Validate tools
for cmd in emcc meson ninja; do
if ! command -v $cmd >/dev/null 2>&1; then
echo "❌ $cmd not found"
exit 1
fi
done

# Set PKG_CONFIG_PATH for dependencies
export PKG_CONFIG_PATH="${PKG_CONFIG_PATH:-}:$PWD/../freetype.wasm/install/wasm/pkgconfig:$PWD/../glib.wasm/install/wasm/pkgconfig"

# Clean previous builds
rm -rf build install

# Configure
echo "[Meson] Configuring build..."
meson setup build \
--cross-file=emscripten-cross.ini \
--prefix="$PWD/install" \
--buildtype=release \
--libdir=wasm \
--bindir=wasm \
-Ddefault_library=static \
-Dtests=disabled \
-Dwasm_build_type="$BUILD_TYPE" \
-Dwasm_simd=true \
-Dwasm_threading=true \
-Dglib=disabled \
-Dgobject=disabled \
-Dcairo=disabled \
-Dchafa=disabled \
-Dicu=disabled \
-Dgraphite2=disabled \
-Dfreetype=enabled \
-Dutilities=disabled \
-Dintrospection=disabled \
-Ddocs=disabled

# Compile
echo "[Meson] Compiling..."
meson compile -C build

# Install
echo "[Meson] Installing..."
meson install -C build

# Optimize with wasm-opt if available
if command -v wasm-opt >/dev/null 2>&1; then
echo "[wasm-opt] Optimizing SIDE_MODULE..."
if [ -f install/wasm/harfbuzz-side.wasm ]; then
wasm-opt -O3 -c install/wasm/harfbuzz-side.wasm \
-o install/wasm/harfbuzz-side.wasm
fi
fi

# Report sizes
echo ""
echo "✅ Build complete!"
echo ""
echo "Output files:"
ls -lh install/wasm/harfbuzz-* 2>/dev/null || echo " (no outputs found)"
42 changes: 42 additions & 0 deletions src/hb-buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@
#include "hb-buffer.hh"
#include "hb-utf.hh"

#ifdef __EMSCRIPTEN__
extern "C" {
#include "../wasm/web_native_simd_shaping.h"
#include "../wasm/web_native_capabilities.c"
}
#endif


/**
* SECTION: hb-buffer
Expand Down Expand Up @@ -409,6 +416,29 @@ hb_buffer_t::clear_positions ()
out_len = 0;
out_info = info;

#ifdef __EMSCRIPTEN__
// Use SIMD-optimized buffer operations when available
if (hb_web_has_simd() && len >= 8) {
// SIMD-optimized zero fill
size_t bytes = sizeof (pos[0]) * len;
unsigned char zero_buf[16] = {0};

// Use SIMD copy with zero buffer for clearing
// This is faster than memset for large buffers
if (bytes >= 128) {
size_t chunks = bytes / 16;
for (size_t i = 0; i < chunks; i++) {
hb_simd_copy_buffer((char*)pos + i * 16, zero_buf, 16);
}
size_t remainder = bytes % 16;
if (remainder > 0) {
hb_memset((char*)pos + chunks * 16, 0, remainder);
}
return;
}
}
#endif

hb_memset (pos, 0, sizeof (pos[0]) * len);
}

Expand Down Expand Up @@ -484,6 +514,12 @@ hb_buffer_t::move_to (unsigned int i)
unsigned int count = i - out_len;
if (unlikely (!make_room_for (count, count))) return false;

#ifdef __EMSCRIPTEN__
// Use SIMD for large buffer copies
if (hb_web_has_simd() && count >= 8) {
hb_simd_copy_buffer(out_info + out_len, info + idx, count * sizeof (out_info[0]));
} else
#endif
memmove (out_info + out_len, info + idx, count * sizeof (out_info[0]));
idx += count;
out_len += count;
Expand All @@ -506,6 +542,12 @@ hb_buffer_t::move_to (unsigned int i)

idx -= count;
out_len -= count;
#ifdef __EMSCRIPTEN__
// Use SIMD for large buffer copies
if (hb_web_has_simd() && count >= 8) {
hb_simd_copy_buffer(info + idx, out_info + out_len, count * sizeof (out_info[0]));
} else
#endif
memmove (info + idx, out_info + out_len, count * sizeof (out_info[0]));
}

Expand Down
Loading
Loading