diff --git a/crates/libdatadog-wasm/Cargo.toml b/crates/libdatadog-wasm/Cargo.toml index 36663455..89d3b9e6 100644 --- a/crates/libdatadog-wasm/Cargo.toml +++ b/crates/libdatadog-wasm/Cargo.toml @@ -50,26 +50,28 @@ getrandom = { version = "0.2", features = ["js"] } # which are not supported by Node.js 18. Keep this list to features used by the # Rust output and supported by every Node.js version that we test. wasm-opt = [ - "-Oz", + "-O3", "--enable-mutable-globals", "--enable-nontrapping-float-to-int", "--enable-bulk-memory", "--enable-sign-ext", "--enable-reference-types", "--enable-multivalue", + "--enable-simd", "--converge", ] [package.metadata.wasm-pack.profile.profiling] # Keep function names for crate attribution after wasm-opt runs in Linux CI. wasm-opt = [ - "-Oz", + "-O3", "--enable-mutable-globals", "--enable-nontrapping-float-to-int", "--enable-bulk-memory", "--enable-sign-ext", "--enable-reference-types", "--enable-multivalue", + "--enable-simd", "--converge", "-g", ] diff --git a/scripts/build-wasm.js b/scripts/build-wasm.js index a6717f93..bfe857b8 100644 --- a/scripts/build-wasm.js +++ b/scripts/build-wasm.js @@ -26,6 +26,10 @@ const env = { ...process.env, } +// Keep the min-size profile while relaxing only LLVM's inlining cap for the compression hot path. +// Rebenchmark this LLVM-internal threshold when updating rust-toolchain.toml. +const libdatadogWasmRustFlags = '-C target-feature=+simd128 -C llvm-args=-inline-threshold=45' + if (isMacOS) { const homebrewDir = env.HOMEBREW_DIR ?? '/opt/homebrew' const llvmDir = `${homebrewDir}/opt/llvm/` @@ -64,6 +68,23 @@ if (isMacOS) { function buildWasm (cratePath, outputDirectory, options = {}) { const { profiling = false, skipOptimization = false } = options const resolvedOutputDirectory = path.resolve(cratePath, outputDirectory) + const buildEnvironment = { ...env } + if (path.basename(cratePath) === 'libdatadog-wasm') { + if (buildEnvironment.CARGO_ENCODED_RUSTFLAGS === undefined) { + const rustFlagsName = buildEnvironment.RUSTFLAGS === undefined + ? 'CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUSTFLAGS' + : 'RUSTFLAGS' + buildEnvironment[rustFlagsName] = [ + buildEnvironment[rustFlagsName], + libdatadogWasmRustFlags, + ].filter(Boolean).join(' ') + } else { + buildEnvironment.CARGO_ENCODED_RUSTFLAGS = [ + buildEnvironment.CARGO_ENCODED_RUSTFLAGS, + ...libdatadogWasmRustFlags.split(' '), + ].filter(Boolean).join('\x1F') + } + } fs.rmSync(resolvedOutputDirectory, { force: true, recursive: true }) const args = ['build'] if (profiling) args.push('--profiling') @@ -71,7 +92,7 @@ function buildWasm (cratePath, outputDirectory, options = {}) { args.push('--target', 'nodejs', cratePath, '--out-dir', resolvedOutputDirectory) childProcess.execFileSync('wasm-pack', args, { env: { - ...env, + ...buildEnvironment, // Cargo's release profile strips the function names needed for size attribution. ...(profiling && { CARGO_PROFILE_RELEASE_STRIP: 'false' }), }, diff --git a/test/build-wasm.js b/test/build-wasm.js index 69d613cf..6f10e40d 100644 --- a/test/build-wasm.js +++ b/test/build-wasm.js @@ -9,12 +9,14 @@ const { test } = require('node:test') const buildScript = path.join(__dirname, '..', 'scripts', 'build-wasm.js') -test('cleans WASM output relative to each crate', () => { +test('builds WASM crates with scoped compiler flags and output paths', () => { const temporaryRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'build-wasm-')) const projectRoot = path.join(temporaryRoot, 'nested', 'repository') const binaryDirectory = path.join(temporaryRoot, 'bin') const homebrewDirectory = path.join(temporaryRoot, 'homebrew') const llvmDirectory = path.join(homebrewDirectory, 'opt', 'llvm', 'bin') + const existingRustFlags = '-C debuginfo=1' + const libdatadogWasmRustFlags = '-C target-feature=+simd128 -C llvm-args=-inline-threshold=45' try { fs.mkdirSync(projectRoot, { recursive: true }) @@ -29,6 +31,13 @@ test('cleans WASM output relative to each crate', () => { fs.mkdirSync(outputDirectory, { recursive: true }) fs.writeFileSync(path.join(outputDirectory, '.gitignore'), '') fs.writeFileSync(path.join(outputDirectory, 'built'), '') + fs.writeFileSync( + path.join(outputDirectory, 'rustflags'), + process.env.CARGO_ENCODED_RUSTFLAGS ?? + process.env.RUSTFLAGS ?? + process.env.CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUSTFLAGS ?? + '', + ) `) for (const library of ['library_config', 'pipeline']) { @@ -53,6 +62,7 @@ test('cleans WASM output relative to each crate', () => { cwd: projectRoot, env: { ...process.env, + CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUSTFLAGS: existingRustFlags, HOMEBREW_DIR: homebrewDirectory, PATH: `${binaryDirectory}${path.delimiter}${process.env.PATH}`, }, @@ -73,6 +83,55 @@ test('cleans WASM output relative to each crate', () => { assert(fs.existsSync(path.join(outputDirectory, 'built'))) assert(!fs.existsSync(path.join(outputDirectory, '.gitignore'))) assert(fs.existsSync(path.join(unrelatedDirectory, 'keep'))) + assert.strictEqual( + fs.readFileSync(path.join(outputDirectory, 'rustflags'), 'utf8'), + existingRustFlags, + ) + } + + const crateDirectory = path.join(projectRoot, 'crates', 'libdatadog-wasm') + fs.mkdirSync(crateDirectory, { recursive: true }) + + const encodedExistingRustFlags = existingRustFlags.replaceAll(' ', '\x1F') + const encodedLibdatadogWasmRustFlags = libdatadogWasmRustFlags.replaceAll(' ', '\x1F') + const rustFlagCases = [ + { + environment: { CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUSTFLAGS: existingRustFlags }, + expected: `${existingRustFlags} ${libdatadogWasmRustFlags}`, + name: 'target', + }, + { + environment: { RUSTFLAGS: existingRustFlags }, + expected: `${existingRustFlags} ${libdatadogWasmRustFlags}`, + name: 'standard', + }, + { + environment: { CARGO_ENCODED_RUSTFLAGS: encodedExistingRustFlags }, + expected: `${encodedExistingRustFlags}\x1F${encodedLibdatadogWasmRustFlags}`, + name: 'encoded', + }, + ] + + for (const { environment, expected, name } of rustFlagCases) { + const outputDirectory = path.join(projectRoot, 'prebuilds', `libdatadog-wasm-${name}`) + execFileSync(process.execPath, [buildScript, crateDirectory, outputDirectory], { + cwd: projectRoot, + env: { + ...process.env, + CARGO_ENCODED_RUSTFLAGS: undefined, + CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUSTFLAGS: undefined, + RUSTFLAGS: undefined, + ...environment, + HOMEBREW_DIR: homebrewDirectory, + PATH: `${binaryDirectory}${path.delimiter}${process.env.PATH}`, + }, + stdio: 'pipe', + }) + + assert.strictEqual( + fs.readFileSync(path.join(outputDirectory, 'rustflags'), 'utf8'), + expected, + ) } } finally { fs.rmSync(temporaryRoot, { force: true, recursive: true })