mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-04-13 16:54:03 +02:00
Avoid expressions duplication in the ps AST and use a local instead when compiling to WASM
This commit is contained in:
parent
a40b91f0bb
commit
63cf35b47f
@ -623,7 +623,50 @@ class PSStackToTree {
|
||||
stack.push(new PsArgNode(i));
|
||||
}
|
||||
this._evalBlock(program.body, stack);
|
||||
return this._failed ? null : stack;
|
||||
if (this._failed) {
|
||||
return null;
|
||||
}
|
||||
PSStackToTree.#markShared(stack);
|
||||
return stack;
|
||||
}
|
||||
|
||||
// Set node.shared / sharedCount on non-atomic nodes referenced more than
|
||||
// once. arg/const are excluded — they are cheap to re-emit inline.
|
||||
static #markShared(outputs) {
|
||||
const refCount = new Map();
|
||||
const visit = node => {
|
||||
if (!node || node.type === PS_NODE.arg || node.type === PS_NODE.const) {
|
||||
return;
|
||||
}
|
||||
const prev = refCount.get(node) ?? 0;
|
||||
refCount.set(node, prev + 1);
|
||||
if (prev > 0) {
|
||||
return;
|
||||
}
|
||||
switch (node.type) {
|
||||
case PS_NODE.unary:
|
||||
visit(node.operand);
|
||||
break;
|
||||
case PS_NODE.binary:
|
||||
visit(node.first);
|
||||
visit(node.second);
|
||||
break;
|
||||
case PS_NODE.ternary:
|
||||
visit(node.cond);
|
||||
visit(node.then);
|
||||
visit(node.otherwise);
|
||||
break;
|
||||
}
|
||||
};
|
||||
for (const output of outputs) {
|
||||
visit(output);
|
||||
}
|
||||
for (const [node, count] of refCount) {
|
||||
if (count > 1) {
|
||||
node.shared = true;
|
||||
node.sharedCount = count; // remaining-use tracking in backends
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_evalBlock(block, stack) {
|
||||
|
||||
@ -260,6 +260,8 @@ class PsWasmCompiler {
|
||||
// Params 0..nIn-1 are automatically locals; extras start at _nextLocal.
|
||||
this._nextLocal = this._nIn;
|
||||
this._freeLocals = [];
|
||||
// node → {local, remaining} for shared sub-expression caching (CSE).
|
||||
this._sharedLocals = new Map();
|
||||
}
|
||||
|
||||
// Wasm emit helpers
|
||||
@ -310,9 +312,30 @@ class PsWasmCompiler {
|
||||
|
||||
/**
|
||||
* Emit Wasm instructions for `node`, leaving exactly one f64 on the Wasm
|
||||
* operand stack. Returns false if the node cannot be compiled.
|
||||
* operand stack. Returns false if the node cannot be compiled.
|
||||
*/
|
||||
_compileNode(node) {
|
||||
if (node.shared) {
|
||||
const entry = this._sharedLocals.get(node);
|
||||
if (entry !== undefined) {
|
||||
this._emitLocalGet(entry.local);
|
||||
if (--entry.remaining === 0) {
|
||||
this._releaseLocal(entry.local);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!this._compileNodeImpl(node)) {
|
||||
return false;
|
||||
}
|
||||
const local = this._allocLocal();
|
||||
this._sharedLocals.set(node, { local, remaining: node.sharedCount - 1 });
|
||||
this._emitLocalTee(local);
|
||||
return true;
|
||||
}
|
||||
return this._compileNodeImpl(node);
|
||||
}
|
||||
|
||||
_compileNodeImpl(node) {
|
||||
switch (node.type) {
|
||||
case PS_NODE.arg:
|
||||
this._emitLocalGet(node.index);
|
||||
@ -642,11 +665,13 @@ class PsWasmCompiler {
|
||||
}
|
||||
|
||||
_compileStandardBinaryNode(op, first, second) {
|
||||
// Identical compound operands: compile once, reuse via local_tee/local_get.
|
||||
// Identical non-atomic operands: compile once, tee/get.
|
||||
// Skip when shared — _compileNode already handles that case.
|
||||
if (
|
||||
first === second &&
|
||||
first.type !== PS_NODE.arg &&
|
||||
first.type !== PS_NODE.const
|
||||
first.type !== PS_NODE.const &&
|
||||
!first.shared
|
||||
) {
|
||||
const tmp = this._allocLocal();
|
||||
try {
|
||||
|
||||
@ -986,6 +986,18 @@ describe("PostScript Type 4 lexer, parser, and Wasm compiler", function () {
|
||||
expect(r).toBeCloseTo(0.25, 9);
|
||||
});
|
||||
|
||||
it("CSE: shared subexpression compiled once, correct result, one local", async function () {
|
||||
// { 3 add dup mul } → (x+3)^2. The "x+3" node is shared (dup), so CSE
|
||||
// caches it in one local that is released and reused for later operands.
|
||||
const source = "{ 3 add dup mul }";
|
||||
const bytes = compilePostScriptToWasm(source, [0, 10], [0, 169]);
|
||||
expect(bytes).not.toBeNull();
|
||||
expect(getWasmLocalCount(bytes)).toBe(1);
|
||||
// x=2 → (2+3)^2 = 25
|
||||
const r = await compileAndRun(source, [0, 10], [0, 169], [2]);
|
||||
expect(r).toBeCloseTo(25, 9);
|
||||
});
|
||||
|
||||
it("reuses temporary locals across sequential shared-subexpression codegen", function () {
|
||||
const bytes = compilePostScriptToWasm(
|
||||
"{ dup 1 add dup mul exch 2 add dup mul add }",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user