Remove unused OpenJPEG wasm fallback logic

Emscripten generates code that allows the caller to provide the Wasm
module (thorugh Module.instantiateWasm), with a fallback in case
.instantiateWasm is not provided. We always define instantiateWasm, so
we can hard-code the check and let our dead code elimination logic
remove the unused fallback.

This commit also improved the dead code elimination logic so that if
a function declaration becomes unused as a result of removing dead
code, the function itself is removed.
This commit is contained in:
Nicolò Ribaudo 2025-05-13 14:06:44 +02:00
parent 2bb8099831
commit aebe0cb67f
No known key found for this signature in database
GPG Key ID: AAFDA9101C58F338
14 changed files with 105 additions and 35 deletions

View File

@ -47,6 +47,25 @@ function handlePreprocessorAction(ctx, actionName, args, path) {
}
function babelPluginPDFJSPreprocessor(babel, ctx) {
function removeUnusedFunctions(path) {
let removed;
do {
removed = false;
path.scope.crawl();
for (const name in path.scope.bindings) {
const binding = path.scope.bindings[name];
if (!binding.referenced) {
const { path: bindingPath } = binding;
if (bindingPath.isFunctionDeclaration()) {
bindingPath.remove();
removed = true;
}
}
}
// If we removed some functions, there might be new unused ones
} while (removed);
}
return {
name: "babel-plugin-pdfjs-preprocessor",
manipulateOptions({ parserOpts }) {
@ -173,36 +192,6 @@ function babelPluginPDFJSPreprocessor(babel, ctx) {
path.replaceWith(t.importExpression(source));
}
},
NewExpression(path) {
const { node } = path;
if (
t.isIdentifier(node.callee, { name: "URL" }) &&
node.arguments.length === 2
) {
const [arg1, arg2] = node.arguments;
if (
arg1.type === "StringLiteral" &&
arg1.value.endsWith(".wasm") &&
arg2.type === "MemberExpression"
) {
// This statement is generated by the Emscripten Compiler (emcc),
// however we're manually loading wasm-files and we want to ensure
// that bundlers will leave it alone; this *must* include Webpack.
arg1.leadingComments = [
{
type: "CommentBlock",
value: "webpackIgnore: true",
},
{
type: "CommentBlock",
value: "@vite-ignore",
},
];
}
}
},
"BlockStatement|StaticBlock": {
// Visit node in post-order so that recursive flattening
// of blocks works correctly.
@ -255,6 +244,8 @@ function babelPluginPDFJSPreprocessor(babel, ctx) {
// Function body ends with return without arg -- removing it.
body.pop();
}
removeUnusedFunctions(path);
},
},
ClassMethod: {
@ -277,6 +268,26 @@ function babelPluginPDFJSPreprocessor(babel, ctx) {
}
},
},
Program: {
exit(path) {
if (path.node.sourceType === "module") {
removeUnusedFunctions(path);
}
},
},
MemberExpression(path) {
// The Emscripten Compiler (emcc) generates code that allows the caller
// to provide the Wasm module (thorugh Module.instantiateWasm), with
// a fallback in case .instantiateWasm is not provided.
// We always define instantiateWasm, so we can hard-code the check
// and let our dead code elimination logic remove the unused fallback.
if (
path.parentPath.isIfStatement({ test: path.node }) &&
path.matchesPattern("Module.instantiateWasm")
) {
path.replaceWith(t.booleanLiteral(true));
}
},
},
};
}

View File

@ -8,3 +8,4 @@ function test() {
}
"4";
}
test();

View File

@ -17,3 +17,4 @@ function test() {
"4";
}
}
test();

View File

@ -2,10 +2,12 @@ function f1() {
"1";
"2";
}
f1();
function f2() {
"1";
"2";
}
f2();
function f3() {
if ("1") {
"1";
@ -15,3 +17,4 @@ function f3() {
"4";
}
}
f3();

View File

@ -6,6 +6,7 @@ function f1() {
"2";
/* tail */
}
f1();
function f2() {
// head
@ -14,6 +15,7 @@ function f2() {
"2";
// tail
}
f2();
function f3() {
if ("1") { // begin block
@ -24,3 +26,4 @@ function f3() {
"4";
}
}
f3();

View File

@ -1,14 +1,18 @@
function f1() {}
f1();
function f2() {
return 1;
}
f2();
function f3() {
var i = 0;
throw "test";
}
f3();
function f4() {
var i = 0;
}
f4();
var obj = {
method1() {},
method2() {}

View File

@ -2,17 +2,20 @@ function f1() {
return;
var i = 0;
}
f1();
function f2() {
return 1;
var i = 0;
}
f2();
function f3() {
var i = 0;
throw "test";
var j = 0;
}
f3();
function f4() {
var i = 0;
@ -22,6 +25,7 @@ function f4() {
throw "test";
var j = 0;
}
f4();
var obj = {
method1() { return; var i = 0; },

View File

@ -16,3 +16,4 @@ if ('1') {
function f1() {
"1";
}
f1();

View File

@ -32,3 +32,4 @@ function f1() {
"2";
}
}
f1();

View File

@ -1,4 +0,0 @@
const wasmUrl = new URL(
/*webpackIgnore: true*/
/*@vite-ignore*/
"qwerty.wasm", import.meta.url);

View File

@ -1 +0,0 @@
const wasmUrl = new URL("qwerty.wasm" , import.meta.url);

View File

@ -0,0 +1,5 @@
function usedByUsed() {}
function used() {
usedByUsed();
}
used();

View File

@ -0,0 +1,14 @@
function usedByUsed() {}
function usedByUnused() {}
function usedByRemovedCode() {}
function used() {
usedByUsed();
return;
usedByRemovedCode();
}
function unused() {
usedByUnused();
}
used();

View File

@ -339,6 +339,33 @@ function createWebpackConfig(
new webpack2.BannerPlugin({ banner: licenseHeaderLibre, raw: true })
);
}
plugins.push({
/** @param {import('webpack').Compiler} compiler */
apply(compiler) {
const errors = [];
compiler.hooks.afterCompile.tap("VerifyImportMeta", compilation => {
for (const asset of compilation.getAssets()) {
if (asset.name.endsWith(".mjs")) {
const source = asset.source.source();
if (
typeof source === "string" &&
/new URL\([^,)]*,\s*import\.meta\.url/.test(source)
) {
errors.push(
`Output module ${asset.name} uses new URL(..., import.meta.url)`
);
}
}
}
});
compiler.hooks.afterEmit.tap("VerifyImportMeta", compilation => {
// Emit the errors after emitting the files, so that it's possible to
// look at the contents of the invalid bundle.
compilation.errors.push(...errors);
});
},
});
const alias = createWebpackAlias(bundleDefines);
const experiments = isModule ? { outputModule: true } : undefined;