mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-08-02 04:17:24 +02:00
Merge pull request #21023 from calixteman/wasm_stack_js
Add a js fallback for interpreting ps code
This commit is contained in:
commit
d1a711bca3
@ -15,14 +15,12 @@
|
|||||||
|
|
||||||
import { Dict, Ref } from "./primitives.js";
|
import { Dict, Ref } from "./primitives.js";
|
||||||
import {
|
import {
|
||||||
FeatureTest,
|
|
||||||
FormatError,
|
FormatError,
|
||||||
info,
|
info,
|
||||||
shadow,
|
shadow,
|
||||||
unreachable,
|
unreachable,
|
||||||
warn,
|
warn,
|
||||||
} from "../shared/util.js";
|
} from "../shared/util.js";
|
||||||
import { PostScriptLexer, PostScriptParser } from "./ps_parser.js";
|
|
||||||
import { BaseStream } from "./base_stream.js";
|
import { BaseStream } from "./base_stream.js";
|
||||||
import { buildPostScriptJsFunction } from "./postscript/js_evaluator.js";
|
import { buildPostScriptJsFunction } from "./postscript/js_evaluator.js";
|
||||||
import { buildPostScriptWasmFunction } from "./postscript/wasm_compiler.js";
|
import { buildPostScriptWasmFunction } from "./postscript/wasm_compiler.js";
|
||||||
@ -371,89 +369,20 @@ class PDFFunction {
|
|||||||
throw new FormatError("No range.");
|
throw new FormatError("No range.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const psCode = fn.getString();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (factory.useWasm) {
|
if (factory.useWasm) {
|
||||||
const wasmFn = buildPostScriptWasmFunction(
|
const wasmFn = buildPostScriptWasmFunction(psCode, domain, range);
|
||||||
fn.getString(),
|
|
||||||
domain,
|
|
||||||
range
|
|
||||||
);
|
|
||||||
if (wasmFn) {
|
if (wasmFn) {
|
||||||
return wasmFn; // (src, srcOffset, dest, destOffset) → void
|
return wasmFn; // (src, srcOffset, dest, destOffset) → void
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
const jsFn = buildPostScriptJsFunction(fn.getString(), domain, range);
|
|
||||||
if (jsFn) {
|
|
||||||
return jsFn; // (src, srcOffset, dest, destOffset) → void
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {}
|
||||||
// Fall through to the existing interpreter-based path.
|
|
||||||
}
|
|
||||||
|
|
||||||
warn("Unable to compile PS function, using interpreter");
|
warn("Failed to compile PostScript function to wasm, falling back to JS");
|
||||||
fn.reset();
|
|
||||||
|
|
||||||
const lexer = new PostScriptLexer(fn);
|
return buildPostScriptJsFunction(psCode, domain, range);
|
||||||
const parser = new PostScriptParser(lexer);
|
|
||||||
const code = parser.parse();
|
|
||||||
|
|
||||||
if (factory.isEvalSupported && FeatureTest.isEvalSupported) {
|
|
||||||
const compiled = new PostScriptCompiler().compile(code, domain, range);
|
|
||||||
if (compiled) {
|
|
||||||
// Compiled function consists of simple expressions such as addition,
|
|
||||||
// subtraction, Math.max, and also contains 'var' and 'return'
|
|
||||||
// statements. See the generation in the PostScriptCompiler below.
|
|
||||||
// eslint-disable-next-line no-new-func
|
|
||||||
return new Function("src", "srcOffset", "dest", "destOffset", compiled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info("Unable to compile PS function");
|
|
||||||
|
|
||||||
const numOutputs = range.length >> 1;
|
|
||||||
const numInputs = domain.length >> 1;
|
|
||||||
const evaluator = new PostScriptEvaluator(code);
|
|
||||||
// Cache the values for a big speed up, the cache size is limited though
|
|
||||||
// since the number of possible values can be huge from a PS function.
|
|
||||||
const cache = Object.create(null);
|
|
||||||
// The MAX_CACHE_SIZE is set to ~4x the maximum number of distinct values
|
|
||||||
// seen in our tests.
|
|
||||||
const MAX_CACHE_SIZE = 2048 * 4;
|
|
||||||
let cache_available = MAX_CACHE_SIZE;
|
|
||||||
const tmpBuf = new Float32Array(numInputs);
|
|
||||||
|
|
||||||
return function constructPostScriptFn(src, srcOffset, dest, destOffset) {
|
|
||||||
let i, value;
|
|
||||||
let key = "";
|
|
||||||
const input = tmpBuf;
|
|
||||||
for (i = 0; i < numInputs; i++) {
|
|
||||||
value = src[srcOffset + i];
|
|
||||||
input[i] = value;
|
|
||||||
key += value + "_";
|
|
||||||
}
|
|
||||||
|
|
||||||
const cachedValue = cache[key];
|
|
||||||
if (cachedValue !== undefined) {
|
|
||||||
dest.set(cachedValue, destOffset);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = new Float32Array(numOutputs);
|
|
||||||
const stack = evaluator.execute(input);
|
|
||||||
const stackIndex = stack.length - numOutputs;
|
|
||||||
for (i = 0; i < numOutputs; i++) {
|
|
||||||
output[i] = MathClamp(
|
|
||||||
stack[stackIndex + i],
|
|
||||||
range[i * 2],
|
|
||||||
range[i * 2 + 1]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (cache_available > 0) {
|
|
||||||
cache_available--;
|
|
||||||
cache[key] = output;
|
|
||||||
}
|
|
||||||
dest.set(output, destOffset);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -529,23 +529,311 @@ function compilePostScriptToIR(source, domain, range) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same calling convention as the Wasm wrapper:
|
* Direct stack-based interpreter for a parsed PsProgram.
|
||||||
* fn(src, srcOffset, dest, destOffset)
|
* Used when PSStackToTree fails to optimize the AST.
|
||||||
|
*/
|
||||||
|
class PSStackBasedInterpreter {
|
||||||
|
// Safe: JS is single-threaded.
|
||||||
|
static #stack = new Float64Array(100);
|
||||||
|
|
||||||
|
static #sp = 0;
|
||||||
|
|
||||||
|
static #push(v) {
|
||||||
|
if (this.#sp < this.#stack.length) {
|
||||||
|
this.#stack[this.#sp++] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static #execOp(op) {
|
||||||
|
const stack = this.#stack;
|
||||||
|
switch (op) {
|
||||||
|
case TOKEN.true:
|
||||||
|
this.#push(1);
|
||||||
|
break;
|
||||||
|
case TOKEN.false:
|
||||||
|
this.#push(0);
|
||||||
|
break;
|
||||||
|
case TOKEN.abs:
|
||||||
|
stack[this.#sp - 1] = Math.abs(stack[this.#sp - 1]);
|
||||||
|
break;
|
||||||
|
case TOKEN.neg:
|
||||||
|
stack[this.#sp - 1] = -stack[this.#sp - 1];
|
||||||
|
break;
|
||||||
|
case TOKEN.ceiling:
|
||||||
|
stack[this.#sp - 1] = Math.ceil(stack[this.#sp - 1]);
|
||||||
|
break;
|
||||||
|
case TOKEN.floor:
|
||||||
|
stack[this.#sp - 1] = Math.floor(stack[this.#sp - 1]);
|
||||||
|
break;
|
||||||
|
case TOKEN.round:
|
||||||
|
stack[this.#sp - 1] = Math.floor(stack[this.#sp - 1] + 0.5);
|
||||||
|
break;
|
||||||
|
case TOKEN.truncate:
|
||||||
|
stack[this.#sp - 1] = Math.trunc(stack[this.#sp - 1]);
|
||||||
|
break;
|
||||||
|
case TOKEN.sqrt:
|
||||||
|
stack[this.#sp - 1] = Math.sqrt(stack[this.#sp - 1]);
|
||||||
|
break;
|
||||||
|
case TOKEN.sin:
|
||||||
|
stack[this.#sp - 1] = Math.sin(
|
||||||
|
(stack[this.#sp - 1] % 360) * _DEG_TO_RAD
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case TOKEN.cos:
|
||||||
|
stack[this.#sp - 1] = Math.cos(
|
||||||
|
(stack[this.#sp - 1] % 360) * _DEG_TO_RAD
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case TOKEN.ln:
|
||||||
|
stack[this.#sp - 1] = Math.log(stack[this.#sp - 1]);
|
||||||
|
break;
|
||||||
|
case TOKEN.log:
|
||||||
|
stack[this.#sp - 1] = Math.log10(stack[this.#sp - 1]);
|
||||||
|
break;
|
||||||
|
case TOKEN.cvi:
|
||||||
|
stack[this.#sp - 1] = Math.trunc(stack[this.#sp - 1]) | 0;
|
||||||
|
break;
|
||||||
|
case TOKEN.cvr:
|
||||||
|
break; // values are already f64
|
||||||
|
case TOKEN.not: {
|
||||||
|
const v = stack[this.#sp - 1];
|
||||||
|
stack[this.#sp - 1] = v === 0 || v === 1 ? 1 - v : ~(v | 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.add: {
|
||||||
|
const b = stack[--this.#sp];
|
||||||
|
stack[this.#sp - 1] += b;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.sub: {
|
||||||
|
const b = stack[--this.#sp];
|
||||||
|
stack[this.#sp - 1] -= b;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.mul: {
|
||||||
|
const b = stack[--this.#sp];
|
||||||
|
stack[this.#sp - 1] *= b;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.div: {
|
||||||
|
const b = stack[--this.#sp];
|
||||||
|
stack[this.#sp - 1] = b !== 0 ? stack[this.#sp - 1] / b : 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.idiv: {
|
||||||
|
const b = stack[--this.#sp];
|
||||||
|
stack[this.#sp - 1] = b !== 0 ? Math.trunc(stack[this.#sp - 1] / b) : 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.mod: {
|
||||||
|
const b = stack[--this.#sp];
|
||||||
|
stack[this.#sp - 1] = b !== 0 ? stack[this.#sp - 1] % b : 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.exp: {
|
||||||
|
const b = stack[--this.#sp];
|
||||||
|
stack[this.#sp - 1] **= b;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.atan: {
|
||||||
|
// Stack: [..., dy, dx] — dx on top.
|
||||||
|
const dx = stack[--this.#sp];
|
||||||
|
const deg = Math.atan2(stack[this.#sp - 1], dx) * _RAD_TO_DEG;
|
||||||
|
stack[this.#sp - 1] = deg < 0 ? deg + 360 : deg;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.eq: {
|
||||||
|
const b = stack[--this.#sp];
|
||||||
|
stack[this.#sp - 1] = stack[this.#sp - 1] === b ? 1 : 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.ne: {
|
||||||
|
const b = stack[--this.#sp];
|
||||||
|
stack[this.#sp - 1] = stack[this.#sp - 1] !== b ? 1 : 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.gt: {
|
||||||
|
const b = stack[--this.#sp];
|
||||||
|
stack[this.#sp - 1] = stack[this.#sp - 1] > b ? 1 : 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.ge: {
|
||||||
|
const b = stack[--this.#sp];
|
||||||
|
stack[this.#sp - 1] = stack[this.#sp - 1] >= b ? 1 : 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.lt: {
|
||||||
|
const b = stack[--this.#sp];
|
||||||
|
stack[this.#sp - 1] = stack[this.#sp - 1] < b ? 1 : 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.le: {
|
||||||
|
const b = stack[--this.#sp];
|
||||||
|
stack[this.#sp - 1] = stack[this.#sp - 1] <= b ? 1 : 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.and: {
|
||||||
|
const b = stack[--this.#sp] | 0;
|
||||||
|
stack[this.#sp - 1] = (stack[this.#sp - 1] | 0) & b;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.or: {
|
||||||
|
const b = stack[--this.#sp] | 0;
|
||||||
|
stack[this.#sp - 1] = stack[this.#sp - 1] | 0 | b;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.xor: {
|
||||||
|
const b = stack[--this.#sp] | 0;
|
||||||
|
stack[this.#sp - 1] = (stack[this.#sp - 1] | 0) ^ b;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.bitshift: {
|
||||||
|
const amt = stack[--this.#sp] | 0;
|
||||||
|
const v = stack[this.#sp - 1] | 0;
|
||||||
|
stack[this.#sp - 1] = amt > 0 ? v << amt : v >> -amt;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.min: {
|
||||||
|
const b = stack[--this.#sp];
|
||||||
|
stack[this.#sp - 1] = Math.min(stack[this.#sp - 1], b);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.max: {
|
||||||
|
const b = stack[--this.#sp];
|
||||||
|
stack[this.#sp - 1] = Math.max(stack[this.#sp - 1], b);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.dup:
|
||||||
|
this.#push(stack[this.#sp - 1]);
|
||||||
|
break;
|
||||||
|
case TOKEN.exch: {
|
||||||
|
const a = stack[--this.#sp];
|
||||||
|
const b = stack[--this.#sp];
|
||||||
|
this.#push(a);
|
||||||
|
this.#push(b);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.pop:
|
||||||
|
this.#sp--;
|
||||||
|
break;
|
||||||
|
case TOKEN.copy: {
|
||||||
|
const n = Math.trunc(stack[--this.#sp]);
|
||||||
|
const base = this.#sp - n;
|
||||||
|
for (let k = 0; k < n; k++) {
|
||||||
|
this.#push(stack[base + k]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.index: {
|
||||||
|
const i = Math.trunc(stack[--this.#sp]);
|
||||||
|
this.#push(stack[this.#sp - 1 - i]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOKEN.roll: {
|
||||||
|
// Rotate top n elements by j positions toward the top.
|
||||||
|
const j = Math.trunc(stack[--this.#sp]);
|
||||||
|
const n = Math.trunc(stack[--this.#sp]);
|
||||||
|
if (n > 1 && j !== 0) {
|
||||||
|
const mod = ((j % n) + n) % n;
|
||||||
|
if (mod !== 0) {
|
||||||
|
const base = this.#sp - n;
|
||||||
|
const sub = stack.slice(base, this.#sp);
|
||||||
|
for (let k = 0; k < n; k++) {
|
||||||
|
stack[base + k] = sub[(k - mod + n) % n];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static #execBlock(instructions) {
|
||||||
|
for (const instr of instructions) {
|
||||||
|
switch (instr.type) {
|
||||||
|
case PS_NODE.number:
|
||||||
|
this.#push(instr.value);
|
||||||
|
break;
|
||||||
|
case PS_NODE.operator:
|
||||||
|
this.#execOp(instr.op);
|
||||||
|
break;
|
||||||
|
case PS_NODE.if:
|
||||||
|
if (this.#stack[--this.#sp] !== 0) {
|
||||||
|
this.#execBlock(instr.then.instructions);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PS_NODE.ifelse:
|
||||||
|
if (this.#stack[--this.#sp] !== 0) {
|
||||||
|
this.#execBlock(instr.then.instructions);
|
||||||
|
} else {
|
||||||
|
this.#execBlock(instr.otherwise.instructions);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("./ast.js").PsProgram} program
|
||||||
|
* @param {number[]} domain – flat [min0,max0, …]
|
||||||
|
* @param {number[]} range – flat [min0,max0, …]
|
||||||
|
* @returns {Function} – `(src, srcOffset, dest, destOffset) => void`
|
||||||
|
*/
|
||||||
|
static build(program, domain, range) {
|
||||||
|
const nIn = domain.length >> 1;
|
||||||
|
const nOut = range.length >> 1;
|
||||||
|
const { instructions } = program.body;
|
||||||
|
return (src, srcOffset, dest, destOffset) => {
|
||||||
|
this.#sp = 0;
|
||||||
|
for (let i = 0; i < nIn; i++) {
|
||||||
|
this.#push(src[srcOffset + i]);
|
||||||
|
}
|
||||||
|
this.#execBlock(instructions);
|
||||||
|
// Outputs: first at bottom, last at top.
|
||||||
|
const base = this.#sp - nOut;
|
||||||
|
for (let i = 0; i < nOut; i++) {
|
||||||
|
const v = base + i >= 0 ? this.#stack[base + i] : 0;
|
||||||
|
dest[destOffset + i] = Math.max(
|
||||||
|
range[i * 2],
|
||||||
|
Math.min(range[i * 2 + 1], v)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries PSStackToTree-optimized IR first; falls back to direct interpreter.
|
||||||
*
|
*
|
||||||
* @param {string} source
|
* @param {string} source
|
||||||
* @param {number[]} domain – flat [min0,max0, …]
|
* @param {number[]} domain – flat [min0,max0, …]
|
||||||
* @param {number[]} range – flat [min0,max0, …]
|
* @param {number[]} range – flat [min0,max0, …]
|
||||||
* @returns {Function|null}
|
* @returns {Function} – `(src, srcOffset, dest, destOffset) => void`
|
||||||
*/
|
*/
|
||||||
function buildPostScriptJsFunction(source, domain, range) {
|
function buildPostScriptJsFunction(source, domain, range) {
|
||||||
const ir = compilePostScriptToIR(source, domain, range);
|
const program = parsePostScriptFunction(source);
|
||||||
if (!ir) {
|
const ir = new PsJsCompiler(domain, range).compile(program);
|
||||||
return null;
|
if (ir) {
|
||||||
|
return (src, srcOffset, dest, destOffset) => {
|
||||||
|
PsJsCompiler.execute(ir, src, srcOffset, dest, destOffset);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
// Fall back to direct interpreter.
|
||||||
return (src, srcOffset, dest, destOffset) => {
|
return PSStackBasedInterpreter.build(program, domain, range);
|
||||||
PsJsCompiler.execute(ir, src, srcOffset, dest, destOffset);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { buildPostScriptJsFunction, compilePostScriptToIR };
|
/**
|
||||||
|
* @param {import("./ast.js").PsProgram} program
|
||||||
|
* @param {number[]} domain – flat [min0,max0, …]
|
||||||
|
* @param {number[]} range – flat [min0,max0, …]
|
||||||
|
* @returns {Function} – `(src, srcOffset, dest, destOffset) => void`
|
||||||
|
*/
|
||||||
|
function buildPostScriptProgramFunction(program, domain, range) {
|
||||||
|
return PSStackBasedInterpreter.build(program, domain, range);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
buildPostScriptJsFunction,
|
||||||
|
buildPostScriptProgramFunction,
|
||||||
|
compilePostScriptToIR,
|
||||||
|
};
|
||||||
|
|||||||
@ -13,6 +13,10 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
buildPostScriptJsFunction,
|
||||||
|
buildPostScriptProgramFunction,
|
||||||
|
} from "../../src/core/postscript/js_evaluator.js";
|
||||||
import {
|
import {
|
||||||
buildPostScriptWasmFunction,
|
buildPostScriptWasmFunction,
|
||||||
compilePostScriptToWasm,
|
compilePostScriptToWasm,
|
||||||
@ -35,7 +39,6 @@ import {
|
|||||||
PsTernaryNode,
|
PsTernaryNode,
|
||||||
PsUnaryNode,
|
PsUnaryNode,
|
||||||
} from "../../src/core/postscript/ast.js";
|
} from "../../src/core/postscript/ast.js";
|
||||||
import { buildPostScriptJsFunction } from "../../src/core/postscript/js_evaluator.js";
|
|
||||||
|
|
||||||
// Precision argument for toBeCloseTo() in trigonometric tests.
|
// Precision argument for toBeCloseTo() in trigonometric tests.
|
||||||
const TRIGONOMETRY_EPS = 1e-10;
|
const TRIGONOMETRY_EPS = 1e-10;
|
||||||
@ -192,25 +195,41 @@ describe("PostScript Type 4 lexer, parser, and Wasm compiler", function () {
|
|||||||
describe("PostScript Type 4 Wasm compiler", function () {
|
describe("PostScript Type 4 Wasm compiler", function () {
|
||||||
/**
|
/**
|
||||||
* Compile and instantiate a PostScript Type 4 function, then call it.
|
* Compile and instantiate a PostScript Type 4 function, then call it.
|
||||||
* Returns null if compilation returns null (unsupported program).
|
* Returns null if Wasm compilation returns null (unsupported program).
|
||||||
* For single-output functions returns a scalar; for multi-output an Array.
|
* For single-output functions returns a scalar; for multi-output an Array.
|
||||||
|
*
|
||||||
|
* Validates three implementations against each other:
|
||||||
|
* - Wasm compiler (PSStackToTree → Wasm binary)
|
||||||
|
* - JS IR compiler (PSStackToTree → flat IR interpreted in JS)
|
||||||
|
* - Direct program interpreter (raw PsProgram stack-machine interpreter)
|
||||||
*/
|
*/
|
||||||
function compileAndRun(src, domain, range, args) {
|
function compileAndRun(src, domain, range, args) {
|
||||||
const wasmFn = buildPostScriptWasmFunction(src, domain, range);
|
const wasmFn = buildPostScriptWasmFunction(src, domain, range);
|
||||||
|
// jsFn now always returns a function: PSStackToTree IR when possible,
|
||||||
|
// direct program interpreter otherwise.
|
||||||
const jsFn = buildPostScriptJsFunction(src, domain, range);
|
const jsFn = buildPostScriptJsFunction(src, domain, range);
|
||||||
|
// Direct interpreter: always available, never uses PSStackToTree.
|
||||||
|
const interpFn = buildPostScriptProgramFunction(
|
||||||
|
parsePostScriptFunction(src),
|
||||||
|
domain,
|
||||||
|
range
|
||||||
|
);
|
||||||
|
|
||||||
if (!wasmFn) {
|
if (!wasmFn) {
|
||||||
expect(jsFn).toBeNull();
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
expect(jsFn).not.toBeNull();
|
|
||||||
const nOut = range.length >> 1;
|
const nOut = range.length >> 1;
|
||||||
const srcBuf = new Float64Array(args);
|
const srcBuf = new Float64Array(args);
|
||||||
const wasmDest = new Float64Array(nOut);
|
const wasmDest = new Float64Array(nOut);
|
||||||
const jsDest = new Float64Array(nOut);
|
const jsDest = new Float64Array(nOut);
|
||||||
|
const interpDest = new Float64Array(nOut);
|
||||||
wasmFn(srcBuf, 0, wasmDest, 0);
|
wasmFn(srcBuf, 0, wasmDest, 0);
|
||||||
jsFn(srcBuf, 0, jsDest, 0);
|
jsFn(srcBuf, 0, jsDest, 0);
|
||||||
|
interpFn(srcBuf, 0, interpDest, 0);
|
||||||
for (let i = 0; i < nOut; i++) {
|
for (let i = 0; i < nOut; i++) {
|
||||||
expect(jsDest[i]).toBeCloseTo(wasmDest[i], 10);
|
expect(jsDest[i]).toBeCloseTo(wasmDest[i], 10);
|
||||||
|
expect(interpDest[i]).toBeCloseTo(wasmDest[i], 10);
|
||||||
}
|
}
|
||||||
return nOut === 1 ? wasmDest[0] : Array.from(wasmDest);
|
return nOut === 1 ? wasmDest[0] : Array.from(wasmDest);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user