Skip to content

Commit

Permalink
Merge pull request #47 from eshaz/puff
Browse files Browse the repository at this point in the history
WASM Module Caching
  • Loading branch information
eshaz authored Jun 11, 2022
2 parents a45b0b9 + c076a26 commit 0a07e24
Show file tree
Hide file tree
Showing 49 changed files with 3,912 additions and 3,571 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ modules/opusfile/* linguist-vendored
src/mpg123-decoder/dist/* linguist-generated
src/ogg-opus-decoder/dist/* linguist-generated
src/opus-decoder/dist/* linguist-generated

*.js diff
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ node_modules

*.tmp.js
*.sizes.json
Puff.wasm

coverage
test/actual/*
Expand Down
49 changes: 44 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,28 @@ dist-clean:
rm -rf $(OPUS_DECODER_PATH)dist/*
rm -rf $(OGG_OPUS_DECODER_PATH)dist/*
rm -rf $(MPG123_DECODER_PATH)dist/*
rm -rf $(PUFF_EMSCRIPTEN_BUILD)
rm -rf $(OPUS_DECODER_EMSCRIPTEN_BUILD)
rm -rf $(OGG_OPUS_DECODER_EMSCRIPTEN_BUILD)
rm -rf $(MPG123_EMSCRIPTEN_BUILD)

# puff
COMMON_PATH=src/common/
PUFF_SRC=$(COMMON_PATH)src/puff/
PUFF_WASM_LIB=tmp/puff.bc
PUFF_EMSCRIPTEN_BUILD=$(COMMON_PATH)src/puff/Puff.wasm

# ogg-opus-decoder
OGG_OPUS_DECODER_PATH=src/ogg-opus-decoder/
OGG_OPUS_DECODER_EMSCRIPTEN_BUILD=$(OGG_OPUS_DECODER_PATH)src/EmscriptenWasm.tmp.js
OGG_OPUS_DECODER_MODULE=$(OGG_OPUS_DECODER_PATH)dist/ogg-opus-decoder.js
OGG_OPUS_DECODER_MODULE_MIN=$(OGG_OPUS_DECODER_PATH)dist/ogg-opus-decoder.min.js

# Iterations, 222 = 110314
# Iterations, (single / double) 222 = 110314, (backtick) 222 = 109953
ogg-opus-decoder: opus-wasmlib ogg-opus-decoder-minify $(OGG_OPUS_DECODER_EMSCRIPTEN_BUILD)
ogg-opus-decoder-minify: $(OGG_OPUS_DECODER_EMSCRIPTEN_BUILD)
SOURCE_PATH=$(OGG_OPUS_DECODER_PATH) \
OUTPUT_NAME=EmscriptenWasm \
MODULE=$(OGG_OPUS_DECODER_MODULE) \
MODULE_MIN=$(OGG_OPUS_DECODER_MODULE_MIN) \
COMPRESSION_ITERATIONS=222 \
Expand All @@ -37,13 +44,14 @@ OPUS_DECODER_EMSCRIPTEN_BUILD=$(OPUS_DECODER_PATH)src/EmscriptenWasm.tmp.js
OPUS_DECODER_MODULE=$(OPUS_DECODER_PATH)dist/opus-decoder.js
OPUS_DECODER_MODULE_MIN=$(OPUS_DECODER_PATH)dist/opus-decoder.min.js

# Iterations, 230 = 84392, 840 = 84384
# Iterations, (single / double) 230 = 84392, 840 = 84384; (backtick) 116 = 84416
opus-decoder: opus-wasmlib opus-decoder-minify $(OPUS_DECODER_EMSCRIPTEN_BUILD)
opus-decoder-minify: $(OPUS_DECODER_EMSCRIPTEN_BUILD)
SOURCE_PATH=$(OPUS_DECODER_PATH) \
OUTPUT_NAME=EmscriptenWasm \
MODULE=$(OPUS_DECODER_MODULE) \
MODULE_MIN=$(OPUS_DECODER_MODULE_MIN) \
COMPRESSION_ITERATIONS=840 \
COMPRESSION_ITERATIONS=116 \
npm run minify
cp $(OPUS_DECODER_MODULE) $(OPUS_DECODER_MODULE_MIN) $(OPUS_DECODER_MODULE_MIN).map $(DEMO_PATH)

Expand All @@ -61,13 +69,14 @@ MPG123_EMSCRIPTEN_BUILD=$(MPG123_DECODER_PATH)src/EmscriptenWasm.tmp.js
MPG123_MODULE=$(MPG123_DECODER_PATH)dist/mpg123-decoder.js
MPG123_MODULE_MIN=$(MPG123_DECODER_PATH)dist/mpg123-decoder.min.js

# Iterations, 108 = 72560, 729 = 72474
# Iterations, (single / double) 108 = 72560, 729 = 72474; (backtick) 181 = 72714
mpg123-decoder: mpg123-wasmlib mpg123-decoder-minify ${MPG123_EMSCRIPTEN_BUILD}
mpg123-decoder-minify: $(MPG123_EMSCRIPTEN_BUILD)
SOURCE_PATH=$(MPG123_DECODER_PATH) \
OUTPUT_NAME=EmscriptenWasm \
MODULE=$(MPG123_MODULE) \
MODULE_MIN=$(MPG123_MODULE_MIN) \
COMPRESSION_ITERATIONS=729 \
COMPRESSION_ITERATIONS=181 \
npm run minify
cp $(MPG123_MODULE) $(MPG123_MODULE_MIN) $(MPG123_MODULE_MIN).map $(DEMO_PATH)

Expand Down Expand Up @@ -103,6 +112,36 @@ define EMCC_OPTS
-s INCOMING_MODULE_JS_API="[]"
endef

# ----------------------
# puff (inflate library)
# ----------------------
# requires: llvm, clang, llc, binaryen
puff-llvm:
@ clang \
--target=wasm32 \
-nostdlib \
-flto \
-Wl,--export=puff \
-Wl,--export=__heap_base \
-Wl,--no-entry \
-Wl,--lto-O3 \
-Wl,--initial-memory=1048576 \
-Oz \
-DSLOW=1 \
-o "$(PUFF_EMSCRIPTEN_BUILD)" \
$(PUFF_SRC)puff.c
@ wasm-opt \
-lmu \
-O3 \
--reorder-functions \
--reorder-locals \
--strip-producers \
--vacuum \
--converge \
$(PUFF_EMSCRIPTEN_BUILD) \
-o $(PUFF_EMSCRIPTEN_BUILD)
@ npm run build-puff

# ------------------
# opus-decoder
# ------------------
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Decodes MPEG Layer I/II/III into PCM

### [`ogg-opus-decoder`](https://github.com/eshaz/wasm-audio-decoders/tree/master/src/ogg-opus-decoder)
Decodes Ogg Opus data into PCM
* 107.7 KiB minified bundle size
* 107.2 KiB minified bundle size
* Browser and NodeJS support
* Built in Web Worker support
* Multichannel decoding (up to 8 channels)
Expand All @@ -31,7 +31,7 @@ Decodes Ogg Opus data into PCM

### [`opus-decoder`](https://github.com/eshaz/wasm-audio-decoders/tree/master/src/opus-decoder)
Decodes raw Opus audio frames into PCM
* 82.4 KiB minified bundle size
* 82.2 KiB minified bundle size
* Browser and NodeJS support
* Built in Web Worker support
* Multichannel decoding (up to 255 channels)
Expand Down Expand Up @@ -68,7 +68,7 @@ Decodes raw Opus audio frames into PCM
* Everything is bundled in a single minified Javascript file for ease of use.
* WASM binary is encoded inline using yEnc binary encoding and compressed using DEFLATE to significantly reduce bundle size.
* WASM compiler, minifier, and bundler options are tuned for best possible size and performance.
* `tiny-inflate` is included from [foliojs/tiny-inflate](https://github.com/foliojs/tiny-inflate) and is used to decompress the WASM binary.
* `puff` is included as a WASM build from [madler/zlib](https://github.com/madler/zlib/tree/master/contrib/puff) and is used to decompress the WASM binary.

## Licensing

Expand Down
137 changes: 82 additions & 55 deletions build.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const searchFileSize = async (
startIteration,
stopIteration,
sourcePath,
outputName,
rollupOutput,
terserOutput
) => {
Expand All @@ -23,8 +24,14 @@ const searchFileSize = async (
iteration <= stopIteration;
iteration++
) {
await buildWasm(sourcePath, iteration, rollupOutput, terserOutput).then(
(code) => {
await buildWasm(
sourcePath,
outputName,
iteration,
rollupOutput,
terserOutput
)
.then((code) => {
sizes.push({
iteration,
size: code.length,
Expand All @@ -44,8 +51,11 @@ const searchFileSize = async (
}

console.log(iteration, sourcePath, code.length);
}
);
})
.catch((e) => {
console.error("failed", iteration, sourcePath);
console.error(e);
});
}

sizes.sort((a, b) => a.size - b.size || a.iteration - b.iteration);
Expand All @@ -56,12 +66,13 @@ const searchFileSize = async (

const buildWasm = async (
sourcePath,
outputName,
compressionIterations,
rollupOutput,
terserOutput
) => {
const emscriptenInputPath = sourcePath + "src/EmscriptenWasm.tmp.js";
const emscriptenOutputPath = sourcePath + "src/EmscriptenWasm.js";
const emscriptenInputPath = sourcePath + `src/${outputName}.tmp.js`;
const emscriptenOutputPath = sourcePath + `src/${outputName}.js`;
const rollupConfigPath = sourcePath + "rollup.json";
const rollupInput = sourcePath + "index.js";
const terserConfigPath = sourcePath + "terser.json";
Expand All @@ -72,7 +83,17 @@ const buildWasm = async (
const wasmInstantiateMatcher = /WebAssembly\.instantiate\(.*?exports;/s;
decoder = decoder.replace(
decoder.match(wasmInstantiateMatcher)[0],
"EmscriptenWASM.compiled.then((wasm) => WebAssembly.instantiate(wasm, imports)).then(function(instance) {\n var asm = instance.exports;"
`
this.setModule = (data) => {
WASMAudioDecoderCommon.setModule(EmscriptenWASM, data);
};
this.getModule = () =>
WASMAudioDecoderCommon.getModule(EmscriptenWASM);
this.instantiate = () => {
this.getModule().then((wasm) => WebAssembly.instantiate(wasm, imports)).then((instance) => {
var asm = instance.exports;`
);

const wasmBase64ContentMatcher =
Expand All @@ -83,25 +104,20 @@ const buildWasm = async (
const wasmContent = decoder.match(wasmBase64ContentMatcher).groups.wasm;
// compressed buffer
const wasmBuffer = Uint8Array.from(Buffer.from(wasmContent, "base64"));
const wasmBufferCompressed = Zopfli.deflateSync(wasmBuffer, {
numiterations: compressionIterations,
blocksplitting: true,
blocksplittingmax: 0,
});

// yEnc encoded wasm
const dynEncodedSingleWasm = {
wasm: yenc.dynamicEncode(wasmBufferCompressed, "'"),
quote: "'",
};
const dynEncodedDoubleWasm = {
wasm: yenc.dynamicEncode(wasmBufferCompressed, '"'),
quote: '"',
let wasmBufferCompressed = wasmBuffer;

if (compressionIterations > 0) {
wasmBufferCompressed = Zopfli.deflateSync(wasmBuffer, {
numiterations: compressionIterations,
blocksplitting: true,
blocksplittingmax: 0,
});
}

const dynEncodedWasm = {
wasm: yenc.dynamicEncode(wasmBufferCompressed, "`"),
quote: "`",
};
const dynEncodedWasm =
dynEncodedDoubleWasm.wasm.length > dynEncodedSingleWasm.wasm.length
? dynEncodedSingleWasm
: dynEncodedDoubleWasm;

// code before the wasm
const wasmStartIdx = decoder.indexOf(wasmBase64DeclarationMatcher);
Expand All @@ -113,12 +129,11 @@ const buildWasm = async (
decoder = Buffer.concat(
[
decoder.substring(0, wasmStartIdx),
'if (!EmscriptenWASM.compiled) Object.defineProperty(EmscriptenWASM, "compiled", {value: ',
"WebAssembly.compile(WASMAudioDecoderCommon.inflateDynEncodeString(",
'if (!EmscriptenWASM.wasm) Object.defineProperty(EmscriptenWASM, "wasm", {get: () => String.raw',
dynEncodedWasm.quote,
dynEncodedWasm.wasm,
dynEncodedWasm.quote,
`, new Uint8Array(${wasmBuffer.length})))})`,
`})`,
decoder.substring(wasmEndIdx),
].map(Buffer.from)
);
Expand All @@ -137,53 +152,65 @@ const buildWasm = async (
"export default function EmscriptenWASM(WASMAudioDecoderCommon) {\n",
decoder,
"return this;\n",
"}",
"}}",
].map(Buffer.from)
);

fs.writeFileSync(emscriptenOutputPath, finalString, { encoding: "binary" });

// rollup
const rollupConfig = fs.readFileSync(rollupConfigPath).toString();
const rollupInputConfig = JSON.parse(rollupConfig);
rollupInputConfig.input = rollupInput;
rollupInputConfig.plugins = [nodeResolve()];
if (module && moduleMin) {
// rollup
const rollupConfig = fs.readFileSync(rollupConfigPath).toString();
const rollupInputConfig = JSON.parse(rollupConfig);
rollupInputConfig.input = rollupInput;
rollupInputConfig.plugins = [nodeResolve()];

const rollupOutputConfig = JSON.parse(rollupConfig);
rollupOutputConfig.output.file = rollupOutput;
const rollupOutputConfig = JSON.parse(rollupConfig);
rollupOutputConfig.output.file = rollupOutput;

const bundle = await rollup(rollupInputConfig);
const output = (await bundle.generate(rollupOutputConfig)).output[0];
const bundle = await rollup(rollupInputConfig);
const output = (await bundle.generate(rollupOutputConfig)).output[0];

// terser
const terserConfig = JSON.parse(fs.readFileSync(terserConfigPath).toString());
const minified = await minify(
{ [output.fileName]: output.code },
terserConfig
);
// terser
const terserConfig = JSON.parse(
fs.readFileSync(terserConfigPath).toString()
);
const minified = await minify(
{ [output.fileName]: output.code },
terserConfig
);

// write output files
await Promise.all([
bundle.write(rollupOutputConfig),
fs.promises.writeFile(terserOutput, minified.code),
fs.promises.writeFile(terserOutput + ".map", minified.map),
]);
// write output files
await Promise.all([
bundle.write(rollupOutputConfig),
fs.promises.writeFile(terserOutput, minified.code),
fs.promises.writeFile(terserOutput + ".map", minified.map),
]);

return fs.readFileSync(terserOutput);
return fs.readFileSync(terserOutput);
}
};

const sourcePath = process.argv[2];
const compressionIterations = parseInt(process.argv[3]);
const module = process.argv[4];
const moduleMin = process.argv[5];
const outputName = process.argv[3];
const compressionIterations = parseInt(process.argv[4]);
const module = process.argv[5];
const moduleMin = process.argv[6];

await buildWasm(sourcePath, compressionIterations, module, moduleMin);
await buildWasm(
sourcePath,
outputName,
compressionIterations,
module,
moduleMin
);

/*
await searchFileSize(
50, // start iteration
1000, // stop iteration
sourcePath,
outputName,
module,
moduleMin
);
Expand Down
Loading

0 comments on commit 0a07e24

Please sign in to comment.