Merge upstream main into main-custom

Change-Id: Ic7f3a36cdb40cddf04ed7d89c41c2719737cc834
Reviewed-on: https://chromium-review.googlesource.com/c/external/github.com/WebKit/JetStream/+/6651527
Reviewed-by: Daniel Lehmann <dlehmann@chromium.org>
This commit is contained in:
Camillo Bruni 2025-06-17 14:10:31 +02:00
parent d4820bdbee
commit de8d4c1101
90 changed files with 11487 additions and 4759 deletions

View file

@ -13,22 +13,38 @@ jobs:
build:
name: Build
runs-on: macos-latest
env:
GITHUB_ACTIONS_OUTPUT: ""
strategy:
matrix:
browser: [chrome, firefox, safari]
browser: [chrome, firefox, jsc, safari, spidermonkey, v8]
steps:
- name: Extract Week Number
run: echo "WEEK_NUMBER=$(date +%W)" >> $GITHUB_ENV
- name: Install Firefox
if: ${{ matrix.browser == 'firefox' }}
run: brew install --cask firefox
- name: Checkout Branch
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 18.13.0
- name: Install
run: npm install
- name: Run tests
node-version-file: package.json
cache: npm
- name: Install Node Packages
run: npm ci
- name: Cache jsvu Binaries
uses: actions/cache@v4
with:
path: ~/.jsvu
key: ${{ runner.os }}-jsvu-${{ matrix.browser }}-week-${{ env.WEEK_NUMBER }}
- name: Run Tests
run: |
echo "Running in $BROWSER"
npm run test:${{ matrix.browser }}

1
8bitbench/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/8bitbench/

70
8bitbench/benchmark.js Normal file
View file

@ -0,0 +1,70 @@
// Copyright 2025 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Simplified from `8bitbench/cli.mjs` and `8bitbench/js/bench.js`.
// See also `8bitbench/rust/src/main.rs` for the native version.
function log(str) { print(str); }
function getInput() { return "t"; }
globalThis.updateVideo = function(vec) { }
const frameWidth = 256;
const frameHeight = 240;
const channels = 4;
// For debugging only: Dump a rendered frame (as a linear vector of pixels) to
// to an ASCII-encoded PPM image file.
function dumpFrame(vec) {
const maxValue = 255;
let ppm = `P3\n${frameWidth} ${frameHeight}\n${maxValue}\n`
for (let y = 0; y < frameHeight; ++y) {
for (let x = 0; x < frameWidth; ++x) {
const r = vec[x*channels + y*frameWidth*channels];
const g = vec[1 + x*channels + y*frameWidth*channels];
const b = vec[2 + x*channels + y*frameWidth*channels];
const alpha = vec[3 + x*channels + y*frameWidth*channels];
ppm += `${r} ${g} ${b}\n`
if (alpha !== maxValue) {
throw new Error("Unexpected alpha channel value: " + alpha);
}
}
}
writeFile('last_frame.ppm', ppm);
}
class Benchmark {
isInstantiated = false;
async runIteration() {
if (!this.isInstantiated) {
await wasm_bindgen(Module.wasmBinary);
this.isInstantiated = true;
}
wasm_bindgen.loadRom(Module.romBinary);
const frameCount = 2 * 60;
for (let i = 0; i < frameCount; ++i) {
wasm_bindgen.js_tick();
}
}
validate() {
globalThis.updateVideo = function(vec) {
if (vec.length != frameWidth * frameHeight * channels) {
throw new Error("Wrong video vec length");
}
// dumpFrame(vec);
let videoSum = 0;
for (let i = 0; i < vec.length; ++i) {
videoSum += vec[i]
}
if (videoSum != 40792276) {
throw new Error("Wrong video sum, the picture is wrong: " + videoSum);
}
}
wasm_bindgen.js_tick();
}
}

11
8bitbench/build.log Normal file
View file

@ -0,0 +1,11 @@
Built on 2025-01-28T13:57:51Z
Toolchain versions
rustc 1.81.0 (eeb90cda1 2024-09-04)
cargo 1.81.0 (2dbb1af80 2024-08-20)
wasm-pack 0.13.1
Getting sources...
Cloning into '8bitbench'...
Updating files: 98% (4618/4667) Updating files: 99% (4621/4667) Updating files: 100% (4667/4667) Updating files: 100% (4667/4667), done.
Building...
Copying files from 8bitbench/ into build/
Build success

48
8bitbench/build.sh Executable file
View file

@ -0,0 +1,48 @@
#!/bin/bash
set -eo pipefail
# Cleanup old files.
rm -rf 8bitbench/
rm -rf build/
touch build.log
BUILD_LOG="$(realpath build.log)"
echo -e "Built on $(date -u '+%Y-%m-%dT%H:%M:%SZ')" | tee "$BUILD_LOG"
echo "Toolchain versions" | tee -a "$BUILD_LOG"
rustc --version | tee -a "$BUILD_LOG"
cargo --version | tee -a "$BUILD_LOG"
wasm-pack --version | tee -a "$BUILD_LOG"
echo -e "Getting sources..." | tee -a "$BUILD_LOG"
git clone https://github.com/justinmichaud/8bitbench.git |& tee -a "$BUILD_LOG"
echo "Building..." | tee -a "$BUILD_LOG"
pushd 8bitbench/
pushd rust/
# Emulator itself.
# NOTE: The 8bitbench README uses `--target web`, but that produces an ES6
# module, so we use the `no-modules` here (which produces almost the same code).
wasm-pack build --target no-modules --release
popd
pushd assets/
pushd cc65/
# Compiler, assembler, linker etc. for the program ROM.
make all
popd
pushd tutorial
# Program ROM to run on the emulator.
make
popd
popd
popd
echo "Copying files from 8bitbench/ into build/" | tee -a "$BUILD_LOG"
mkdir -p build/{lib/fast-text-encoding-1.0.3/,rust/pkg/,assets/}
cp 8bitbench/lib/fast-text-encoding-1.0.3/text.js build/lib/fast-text-encoding-1.0.3/
cp 8bitbench/rust/pkg/{emu_bench.js,emu_bench_bg.wasm} build/rust/pkg
cp 8bitbench/assets/tutorial/full_game.bin build/assets/program.bin
echo "Build success" | tee -a "$BUILD_LOG"

View file

@ -1,9 +1,11 @@
let wasm_bindgen;
(function() {
const __exports = {};
let wasm;
let wasm;
const heap = new Array(32).fill(undefined);
const heap = new Array(32).fill(undefined);
heap.push(undefined, null, true, false);
heap.push(undefined, null, true, false);
function getObject(idx) { return heap[idx]; }
@ -38,22 +40,6 @@ function getStringFromWasm0(ptr, len) {
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
}
function logError(f, args) {
try {
return f.apply(this, args);
} catch (e) {
let error = (function () {
try {
return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString();
} catch(_) {
return "<failed to stringify thrown value>";
}
}());
console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error);
throw e;
}
}
function addHeapObject(obj) {
if (heap_next === heap.length) heap.push(heap.length + 1);
const idx = heap_next;
@ -132,9 +118,9 @@ function getArrayU8FromWasm0(ptr, len) {
}
/**
*/
function main() {
__exports.main = function() {
wasm.main();
}
};
function passArray8ToWasm0(arg, malloc) {
const ptr = malloc(arg.length * 1);
@ -145,17 +131,17 @@ function passArray8ToWasm0(arg, malloc) {
/**
* @param {Uint8Array} file
*/
function loadRom(file) {
__exports.loadRom = function(file) {
const ptr0 = passArray8ToWasm0(file, wasm.__wbindgen_malloc);
const len0 = WASM_VECTOR_LEN;
wasm.loadRom(ptr0, len0);
}
};
/**
*/
function js_tick() {
__exports.js_tick = function() {
wasm.js_tick();
}
};
async function load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) {
@ -228,14 +214,12 @@ function getImports() {
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1));
};
imports.wbg.__wbindgen_rethrow = function(arg0) {
throw takeObject(arg0);
};
return imports;
}
function initMemory(imports, maybe_memory) {
}
function finalizeInit(instance, module) {
@ -263,6 +247,15 @@ function initSync(module) {
}
async function init(input) {
if (typeof input === 'undefined') {
let src;
if (typeof document === 'undefined') {
src = location.href;
} else {
src = document.currentScript.src;
}
input = src.replace(/\.js$/, '_bg.wasm');
}
const imports = getImports();
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
@ -274,4 +267,8 @@ async function init(input) {
const { instance, module } = await load(await input, imports);
return finalizeInit(instance, module);
}
}
wasm_bindgen = Object.assign(init, { initSync }, __exports);
})();

Binary file not shown.

View file

@ -1,50 +0,0 @@
async function doRun() {
function log(str) { }
globalObject.log = log
globalObject.getInput = function() {
return "t"
}
let verifyVideo = false
let videoSum = 0
globalObject.updateVideo = function(vec) {
if (vec.length != 4 * 256 * 240)
throw new Error("Wrong vec length")
if (!verifyVideo)
return
for (let i = 0, length = globalObject.video.length; i < length; ++i) {
videoSum += vec[i]
}
}
let ab = new ArrayBuffer(Int16Array.BYTES_PER_ELEMENT * 4 * 256 * 240)
globalObject.video = new Int16Array(ab)
let start = benchmarkTime()
if (!Module.wasmBinary.length || !Module.romBinary.length)
throw new Error("Needed binary not loaded");
await init(Module.wasmBinary)
loadRom(Module.romBinary)
js_tick()
reportCompileTime(benchmarkTime() - start)
start = benchmarkTime()
let frames = 40 * 60
for (let i = 0; i < frames; ++i) {
js_tick()
}
verifyVideo = true
js_tick()
if (video.length != 256 * 240 * 4)
throw "Length is wrong"
if (videoSum != 45833688)
throw "Wrong video sum, the picture is wrong: " + videoSum
reportRunTime(benchmarkTime() - start)
}

1
Dart/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/wasm_gc_benchmarks/

31
Dart/README.md Normal file
View file

@ -0,0 +1,31 @@
# Dart flute benchmark application
This is a WasmGC build of the `flute` benchmark, a Dart application using the
`Flutter` UI framework.
It performs the Wasm/compute-only part of rendering a certain number of frames.
Since JetStream runs in JS engine shells, the actual rendering to a Canvas is
stubbed out.
The upstream repository containing pre-built WebAssembly binaries (which we
use here) is at https://github.com/mkustermann/wasm_gc_benchmarks.
The Dart source code of the flute application is at https://github.com/dart-lang/flute.
## Build Instructions
See `build.sh` or just run it.
See `build.log` for the last build time, used sources, and toolchain versions.
## Running in JS shells
To run the unmodified upstream benchmark, without the JetStream driver, see the
upstream repo.
In short, the main runner is `build/run_wasm.js`, which takes as arguments a
application-specific generated JS and Wasm file, and the arguments passed to
the Dart main method.
Since different engines / shells resolve JS modules and parse command-line
arguments differently, the invocations are something like (from this directory):
```
path/to/d8 build/run_wasm.js -- flute.dart2wasm.mjs build/flute.dart2wasm.wasm -- $(date +%s.%N) 1000
path/to/spidermonkey/js build/run_wasm.js build/flute.dart2wasm.mjs flute.dart2wasm.wasm -- $(date +%s.%N) 1000
path/to/jsc build/run_wasm.js -- ./flute.dart2wasm.mjs build/flute.dart2wasm.wasm -- $(date +%s.%N) 1000
```

311
Dart/benchmark.js Normal file
View file

@ -0,0 +1,311 @@
// Copyright 2024 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Excerpt from `build/run_wasm.js` to add own task queue implementation, since
// `setTimeout` and `queueMicrotask` are not always available in shells.
function addTaskQueue(self) {
"use strict";
// Task queue as cyclic list queue.
var taskQueue = new Array(8); // Length is power of 2.
var head = 0;
var tail = 0;
var mask = taskQueue.length - 1;
function addTask(elem) {
taskQueue[head] = elem;
head = (head + 1) & mask;
if (head == tail) _growTaskQueue();
}
function removeTask() {
if (head == tail) return;
var result = taskQueue[tail];
taskQueue[tail] = undefined;
tail = (tail + 1) & mask;
return result;
}
function _growTaskQueue() {
// head == tail.
var length = taskQueue.length;
var split = head;
taskQueue.length = length * 2;
if (split * 2 < length) { // split < length / 2
for (var i = 0; i < split; i++) {
taskQueue[length + i] = taskQueue[i];
taskQueue[i] = undefined;
}
head += length;
} else {
for (var i = split; i < length; i++) {
taskQueue[length + i] = taskQueue[i];
taskQueue[i] = undefined;
}
tail += length;
}
mask = taskQueue.length - 1;
}
// Mapping from timer id to timer function.
// The timer id is written on the function as .$timerId.
// That field is cleared when the timer is cancelled, but it is not returned
// from the queue until its time comes.
var timerIds = {};
var timerIdCounter = 1; // Counter used to assign ids.
// Zero-timer queue as simple array queue using push/shift.
var zeroTimerQueue = [];
function addTimer(f, ms) {
ms = Math.max(0, ms);
var id = timerIdCounter++;
// A callback can be scheduled at most once.
// (console.assert is only available on D8)
// if (isD8) console.assert(f.$timerId === undefined);
f.$timerId = id;
timerIds[id] = f;
if (ms == 0 && !isNextTimerDue()) {
zeroTimerQueue.push(f);
} else {
addDelayedTimer(f, ms);
}
return id;
}
function nextZeroTimer() {
while (zeroTimerQueue.length > 0) {
var action = zeroTimerQueue.shift();
if (action.$timerId !== undefined) return action;
}
}
function nextEvent() {
var action = removeTask();
if (action) {
return action;
}
do {
action = nextZeroTimer();
if (action) break;
var nextList = nextDelayedTimerQueue();
if (!nextList) {
return;
}
var newTime = nextList.shift();
advanceTimeTo(newTime);
zeroTimerQueue = nextList;
} while (true)
var id = action.$timerId;
clearTimerId(action, id);
return action;
}
// Mocking time.
var timeOffset = 0;
var now = function() {
// Install the mock Date object only once.
// Following calls to "now" will just use the new (mocked) Date.now
// method directly.
installMockDate();
now = Date.now;
return Date.now();
};
var originalDate = Date;
var originalNow = originalDate.now;
function advanceTimeTo(time) {
var now = originalNow();
if (timeOffset < time - now) {
timeOffset = time - now;
}
}
function installMockDate() {
var NewDate = function Date(Y, M, D, h, m, s, ms) {
if (this instanceof Date) {
// Assume a construct call.
switch (arguments.length) {
case 0: return new originalDate(originalNow() + timeOffset);
case 1: return new originalDate(Y);
case 2: return new originalDate(Y, M);
case 3: return new originalDate(Y, M, D);
case 4: return new originalDate(Y, M, D, h);
case 5: return new originalDate(Y, M, D, h, m);
case 6: return new originalDate(Y, M, D, h, m, s);
default: return new originalDate(Y, M, D, h, m, s, ms);
}
}
return new originalDate(originalNow() + timeOffset).toString();
};
NewDate.UTC = originalDate.UTC;
NewDate.parse = originalDate.parse;
NewDate.now = function now() { return originalNow() + timeOffset; };
NewDate.prototype = originalDate.prototype;
originalDate.prototype.constructor = NewDate;
Date = NewDate;
}
// Heap priority queue with key index.
// Each entry is list of [timeout, callback1 ... callbackn].
var timerHeap = [];
var timerIndex = {};
function addDelayedTimer(f, ms) {
var timeout = now() + ms;
var timerList = timerIndex[timeout];
if (timerList == null) {
timerList = [timeout, f];
timerIndex[timeout] = timerList;
var index = timerHeap.length;
timerHeap.length += 1;
bubbleUp(index, timeout, timerList);
} else {
timerList.push(f);
}
}
function isNextTimerDue() {
if (timerHeap.length == 0) return false;
var head = timerHeap[0];
return head[0] < originalNow() + timeOffset;
}
function nextDelayedTimerQueue() {
if (timerHeap.length == 0) return null;
var result = timerHeap[0];
var last = timerHeap.pop();
if (timerHeap.length > 0) {
bubbleDown(0, last[0], last);
}
return result;
}
function bubbleUp(index, key, value) {
while (index != 0) {
var parentIndex = (index - 1) >> 1;
var parent = timerHeap[parentIndex];
var parentKey = parent[0];
if (key > parentKey) break;
timerHeap[index] = parent;
index = parentIndex;
}
timerHeap[index] = value;
}
function bubbleDown(index, key, value) {
while (true) {
var leftChildIndex = index * 2 + 1;
if (leftChildIndex >= timerHeap.length) break;
var minChildIndex = leftChildIndex;
var minChild = timerHeap[leftChildIndex];
var minChildKey = minChild[0];
var rightChildIndex = leftChildIndex + 1;
if (rightChildIndex < timerHeap.length) {
var rightChild = timerHeap[rightChildIndex];
var rightKey = rightChild[0];
if (rightKey < minChildKey) {
minChildIndex = rightChildIndex;
minChild = rightChild;
minChildKey = rightKey;
}
}
if (minChildKey > key) break;
timerHeap[index] = minChild;
index = minChildIndex;
}
timerHeap[index] = value;
}
function cancelTimer(id) {
var f = timerIds[id];
if (f == null) return;
clearTimerId(f, id);
}
function clearTimerId(f, id) {
f.$timerId = undefined;
delete timerIds[id];
}
async function eventLoop(action) {
while (action) {
try {
await action();
} catch (e) {
if (typeof onerror == "function") {
onerror(e, null, -1);
} else {
throw e;
}
}
action = nextEvent();
}
}
self.setTimeout = addTimer;
self.clearTimeout = cancelTimer;
self.queueMicrotask = addTask;
self.eventLoop = eventLoop;
}
function dartPrint(...args) { print(args); }
addTaskQueue(globalThis);
globalThis.window ??= globalThis;
class Benchmark {
dart2wasmJsModule;
compiledApp;
async init() {
// The generated JavaScript code from dart2wasm is an ES module, which we
// can only load with a dynamic import (since this file is not a module.)
// TODO: Support ES6 modules in the driver instead of this one-off solution.
// This probably requires a new `Benchmark` field called `modules` that
// is a map from module variable name (which will hold the resulting module
// namespace object) to relative module URL, which is resolved in the
// `preRunnerCode`, similar to this code here.
if (isInBrowser) {
// In browsers, relative imports don't work since we are not in a module.
// (`import.meta.url` is not defined.)
const pathname = location.pathname.match(/^(.*\/)(?:[^.]+(?:\.(?:[^\/]+))+)?$/)[1];
this.dart2wasmJsModule = await import(location.origin + pathname + "./Dart/build/flute.dart2wasm.mjs");
} else {
// In shells, relative imports require different paths, so try with and
// without the "./" prefix (e.g., JSC requires it).
try {
this.dart2wasmJsModule = await import("Dart/build/flute.dart2wasm.mjs");
} catch {
this.dart2wasmJsModule = await import("./Dart/build/flute.dart2wasm.mjs");
}
}
}
async runIteration() {
// Compile once in the first iteration.
if (!this.compiledApp) {
this.compiledApp = await this.dart2wasmJsModule.compile(Module.wasmBinary);
}
// Instantiate each iteration, since we can only `invokeMain()` with a
// freshly instantiated module.
const additionalImports = {};
const instantiatedApp = await this.compiledApp.instantiate(additionalImports);
const startTimeSinceEpochSeconds = new Date().getTime() / 1000;
// Reduce workload size for a single iteration.
// The default is 1000 frames, but that takes too long (>2s per iteration).
const framesToDraw = 100;
const initialFramesToSkip = 0;
const dartArgs = [
startTimeSinceEpochSeconds,
framesToDraw.toString(),
initialFramesToSkip.toString()
];
await eventLoop(async () => {
await instantiatedApp.invokeMain(...dartArgs)
});
}
}

5
Dart/build.log Normal file
View file

@ -0,0 +1,5 @@
Built on 2025-03-06 09:52:17+01:00
Cloning into 'wasm_gc_benchmarks'...
f80892d Update Dart SDK, switch wasm to -O2, recompile performance benchmarks
Copying files from wasm_gc_benchmarks/ into build/
Build success

27
Dart/build.sh Executable file
View file

@ -0,0 +1,27 @@
#!/bin/bash
set -eo pipefail
# Cleanup old files.
rm -rf wasm_gc_benchmarks/
rm -rf build/
BUILD_LOG="$(realpath build.log)"
echo -e "Built on $(date --rfc-3339=seconds)" | tee "$BUILD_LOG"
git clone https://github.com/mkustermann/wasm_gc_benchmarks |& tee -a "$BUILD_LOG"
pushd wasm_gc_benchmarks/
git log -1 --oneline | tee -a "$BUILD_LOG"
popd
echo "Copying files from wasm_gc_benchmarks/ into build/" | tee -a "$BUILD_LOG"
mkdir -p build/ | tee -a "$BUILD_LOG"
# Generic Dart2wasm runner.
cp wasm_gc_benchmarks/tools/run_wasm.js build/ | tee -a "$BUILD_LOG"
# "Flute Complex" benchmark application.
cp wasm_gc_benchmarks/benchmarks-out/flute.dart2wasm.{mjs,wasm} build/ | tee -a "$BUILD_LOG"
echo "Build success" | tee -a "$BUILD_LOG"
# TODO: We could actually build the application/benchmark from Dart sources with
# the dart2wasm compiler / Dart SDK. See `wasm_gc_benchmarks/compile.sh`

File diff suppressed because one or more lines are too long

Binary file not shown.

424
Dart/build/run_wasm.js Normal file
View file

@ -0,0 +1,424 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
//
// Runner V8/JSShell script for testing dart2wasm, takes ".wasm" files as
// arguments.
//
// Run as follows on D8:
//
// $> d8 run_wasm.js \
// -- /abs/path/to/<dart_module>.mjs <dart_module>.wasm [<ffi_module>.wasm] \
// [-- Dart commandline arguments...]
//
// Run as follows on JSC:
//
// $> jsc run_wasm.js -- <dart_module>.mjs <dart_module>.wasm [<ffi_module>.wasm] \
// [-- Dart commandline arguments...]
//
// Run as follows on JSShell:
//
// $> js run_wasm.js \
// /abs/path/to/<dart_module>.mjs <dart_module>.wasm [<ffi_module>.wasm] \
// [-- Dart commandline arguments...]
//
// (Notice the missing -- here!)
//
// Please note we require an absolute path for the JS runtime. This is a
// workaround for a discrepancy in D8. Specifically, `import`(used to load .mjs
// files) searches for imports relative to run_wasm.js, but `readbuffer`(used to
// read in .wasm files) searches relative to CWD. A path relative to
// `run_wasm.js` will also work.
//
// Or with the `run_dart2wasm_d8` helper script:
//
// $> sdk/bin/run_dart2wasm_d8 <dart_module>.wasm [<ffi_module>.wasm]
//
// If an FFI module is specified, it will be instantiated first, and its
// exports will be supplied as imports to the Dart module under the 'ffi'
// module name.
const jsRuntimeArg = 0;
const wasmArg = 1;
const ffiArg = 2;
// This script is intended to be used by D8, JSShell or JSC. We distinguish
// them by the functions they offer to read files:
//
// Engine | Shell | FileRead | Arguments
// --------------------------------------------------------------
// V8 | D8 | readbuffer | arguments (arg0 arg1)
// JavaScriptCore | JSC | readFile | arguments (arg0 arg1)
// SpiderMonkey | JSShell | readRelativeToScript | scriptArgs (-- arg0 arg1)
//
const isD8 = (typeof readbuffer === "function");
const isJSC = (typeof readFile === "function");
const isJSShell = (typeof readRelativeToScript === "function");
if (isD8) {
// D8's performance.measure is API incompatible with the browser version.
//
// (see also dart2js's `sdk/**/js_runtime/lib/preambles/d8.js`)
delete performance.measure;
}
function readFileContentsAsBytes(filename) {
var buffer;
if (isJSC) {
buffer = readFile(filename, "binary");
} else if (isD8) {
buffer = readbuffer(filename);
} else {
buffer = readRelativeToScript(filename, "binary");
}
return new Uint8Array(buffer, 0, buffer.byteLength);
}
var args = (isD8 || isJSC) ? arguments : scriptArgs;
var dartArgs = [];
const argsSplit = args.indexOf("--");
if (argsSplit != -1) {
dartArgs = args.slice(argsSplit + 1);
args = args.slice(0, argsSplit);
}
// d8's `setTimeout` doesn't work as expected (it doesn't wait before calling
// the callback), and d8 also doesn't have `setInterval` and `queueMicrotask`.
// So we define our own event loop with these functions.
//
// The code below is copied form dart2js, with some modifications:
// sdk/lib/_internal/js_runtime/lib/preambles/d8.js
(function(self, scriptArguments) {
// Using strict mode to avoid accidentally defining global variables.
"use strict"; // Should be first statement of this function.
// Task queue as cyclic list queue.
var taskQueue = new Array(8); // Length is power of 2.
var head = 0;
var tail = 0;
var mask = taskQueue.length - 1;
function addTask(elem) {
taskQueue[head] = elem;
head = (head + 1) & mask;
if (head == tail) _growTaskQueue();
}
function removeTask() {
if (head == tail) return;
var result = taskQueue[tail];
taskQueue[tail] = undefined;
tail = (tail + 1) & mask;
return result;
}
function _growTaskQueue() {
// head == tail.
var length = taskQueue.length;
var split = head;
taskQueue.length = length * 2;
if (split * 2 < length) { // split < length / 2
for (var i = 0; i < split; i++) {
taskQueue[length + i] = taskQueue[i];
taskQueue[i] = undefined;
}
head += length;
} else {
for (var i = split; i < length; i++) {
taskQueue[length + i] = taskQueue[i];
taskQueue[i] = undefined;
}
tail += length;
}
mask = taskQueue.length - 1;
}
// Mapping from timer id to timer function.
// The timer id is written on the function as .$timerId.
// That field is cleared when the timer is cancelled, but it is not returned
// from the queue until its time comes.
var timerIds = {};
var timerIdCounter = 1; // Counter used to assign ids.
// Zero-timer queue as simple array queue using push/shift.
var zeroTimerQueue = [];
function addTimer(f, ms) {
ms = Math.max(0, ms);
var id = timerIdCounter++;
// A callback can be scheduled at most once.
// (console.assert is only available on D8)
if (isD8) console.assert(f.$timerId === undefined);
f.$timerId = id;
timerIds[id] = f;
if (ms == 0 && !isNextTimerDue()) {
zeroTimerQueue.push(f);
} else {
addDelayedTimer(f, ms);
}
return id;
}
function nextZeroTimer() {
while (zeroTimerQueue.length > 0) {
var action = zeroTimerQueue.shift();
if (action.$timerId !== undefined) return action;
}
}
function nextEvent() {
var action = removeTask();
if (action) {
return action;
}
do {
action = nextZeroTimer();
if (action) break;
var nextList = nextDelayedTimerQueue();
if (!nextList) {
return;
}
var newTime = nextList.shift();
advanceTimeTo(newTime);
zeroTimerQueue = nextList;
} while (true)
var id = action.$timerId;
clearTimerId(action, id);
return action;
}
// Mocking time.
var timeOffset = 0;
var now = function() {
// Install the mock Date object only once.
// Following calls to "now" will just use the new (mocked) Date.now
// method directly.
installMockDate();
now = Date.now;
return Date.now();
};
var originalDate = Date;
var originalNow = originalDate.now;
function advanceTimeTo(time) {
var now = originalNow();
if (timeOffset < time - now) {
timeOffset = time - now;
}
}
function installMockDate() {
var NewDate = function Date(Y, M, D, h, m, s, ms) {
if (this instanceof Date) {
// Assume a construct call.
switch (arguments.length) {
case 0: return new originalDate(originalNow() + timeOffset);
case 1: return new originalDate(Y);
case 2: return new originalDate(Y, M);
case 3: return new originalDate(Y, M, D);
case 4: return new originalDate(Y, M, D, h);
case 5: return new originalDate(Y, M, D, h, m);
case 6: return new originalDate(Y, M, D, h, m, s);
default: return new originalDate(Y, M, D, h, m, s, ms);
}
}
return new originalDate(originalNow() + timeOffset).toString();
};
NewDate.UTC = originalDate.UTC;
NewDate.parse = originalDate.parse;
NewDate.now = function now() { return originalNow() + timeOffset; };
NewDate.prototype = originalDate.prototype;
originalDate.prototype.constructor = NewDate;
Date = NewDate;
}
// Heap priority queue with key index.
// Each entry is list of [timeout, callback1 ... callbackn].
var timerHeap = [];
var timerIndex = {};
function addDelayedTimer(f, ms) {
var timeout = now() + ms;
var timerList = timerIndex[timeout];
if (timerList == null) {
timerList = [timeout, f];
timerIndex[timeout] = timerList;
var index = timerHeap.length;
timerHeap.length += 1;
bubbleUp(index, timeout, timerList);
} else {
timerList.push(f);
}
}
function isNextTimerDue() {
if (timerHeap.length == 0) return false;
var head = timerHeap[0];
return head[0] < originalNow() + timeOffset;
}
function nextDelayedTimerQueue() {
if (timerHeap.length == 0) return null;
var result = timerHeap[0];
var last = timerHeap.pop();
if (timerHeap.length > 0) {
bubbleDown(0, last[0], last);
}
return result;
}
function bubbleUp(index, key, value) {
while (index != 0) {
var parentIndex = (index - 1) >> 1;
var parent = timerHeap[parentIndex];
var parentKey = parent[0];
if (key > parentKey) break;
timerHeap[index] = parent;
index = parentIndex;
}
timerHeap[index] = value;
}
function bubbleDown(index, key, value) {
while (true) {
var leftChildIndex = index * 2 + 1;
if (leftChildIndex >= timerHeap.length) break;
var minChildIndex = leftChildIndex;
var minChild = timerHeap[leftChildIndex];
var minChildKey = minChild[0];
var rightChildIndex = leftChildIndex + 1;
if (rightChildIndex < timerHeap.length) {
var rightChild = timerHeap[rightChildIndex];
var rightKey = rightChild[0];
if (rightKey < minChildKey) {
minChildIndex = rightChildIndex;
minChild = rightChild;
minChildKey = rightKey;
}
}
if (minChildKey > key) break;
timerHeap[index] = minChild;
index = minChildIndex;
}
timerHeap[index] = value;
}
function addInterval(f, ms) {
ms = Math.max(0, ms);
var id = timerIdCounter++;
function repeat() {
// Reactivate with the same id.
repeat.$timerId = id;
timerIds[id] = repeat;
addDelayedTimer(repeat, ms);
f();
}
repeat.$timerId = id;
timerIds[id] = repeat;
addDelayedTimer(repeat, ms);
return id;
}
function cancelTimer(id) {
var f = timerIds[id];
if (f == null) return;
clearTimerId(f, id);
}
function clearTimerId(f, id) {
f.$timerId = undefined;
delete timerIds[id];
}
async function eventLoop(action) {
while (action) {
try {
await action();
} catch (e) {
// JSC doesn't report/print uncaught async exceptions for some reason.
if (isJSC) {
print('Error: ' + e);
print('Stack: ' + e.stack);
}
if (typeof onerror == "function") {
onerror(e, null, -1);
} else {
throw e;
}
}
action = nextEvent();
}
}
// Global properties. "self" refers to the global object, so adding a
// property to "self" defines a global variable.
self.self = self;
self.dartMainRunner = function(main, ignored_args) {
// Initialize.
var action = async function() { await main(scriptArguments, null); }
eventLoop(action);
};
self.setTimeout = addTimer;
self.clearTimeout = cancelTimer;
self.setInterval = addInterval;
self.clearInterval = cancelTimer;
self.queueMicrotask = addTask;
self.readFileContentsAsBytes = readFileContentsAsBytes;
self.location = {}
self.location.href = 'file://' + args[wasmArg];
// Signals `Stopwatch._initTicker` to use `Date.now` to get ticks instead of
// `performance.now`, as it's not available in d8.
self.dartUseDateNowForTicks = true;
})(this, []);
// We would like this itself to be a ES module rather than a script, but
// unfortunately d8 does not return a failed error code if an unhandled
// exception occurs asynchronously in an ES module.
const main = async () => {
const dart2wasm = await import(args[jsRuntimeArg]);
/// Returns whether the `js-string` built-in is supported.
function detectImportedStrings() {
let bytes = [
0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0,
0, 2, 23, 1, 14, 119, 97, 115, 109, 58, 106, 115, 45,
115, 116, 114, 105, 110, 103, 4, 99, 97, 115, 116, 0, 0
];
return !WebAssembly.validate(
new Uint8Array(bytes), {builtins: ['js-string']});
}
function compile(filename, withJsStringBuiltins) {
// Create a Wasm module from the binary Wasm file.
return WebAssembly.compile(
readFileContentsAsBytes(filename),
withJsStringBuiltins ? {builtins: ['js-string']} : {}
);
}
globalThis.window ??= globalThis;
let importObject = {};
// Is an FFI module specified?
if (args.length > 2) {
// Instantiate FFI module.
var ffiInstance = await WebAssembly.instantiate(await compile(args[ffiArg], false), {});
// Make its exports available as imports under the 'ffi' module name.
importObject.ffi = ffiInstance.exports;
}
// Instantiate the Dart module, importing from the global scope.
var dartInstance = await dart2wasm.instantiate(
compile(args[wasmArg], detectImportedStrings()),
Promise.resolve(importObject),
);
// Call `main`. If tasks are placed into the event loop (by scheduling tasks
// explicitly or awaiting Futures), these will automatically keep the script
// alive even after `main` returns.
await dart2wasm.invoke(dartInstance, ...dartArgs);
};
dartMainRunner(main, []);

View file

@ -312,6 +312,7 @@ a.button {
background-image: none;
}
.benchmark-done h3, .benchmark-done h4, .benchmark-done .result, .benchmark-done label {
background-color: transparent;
background-image: none;
@ -322,6 +323,16 @@ a.button {
user-select: text;
}
.benchmark-error h4, .benchmark-error .result, .benchmark-error label {
color: #ff8686;
background-color: #ff8686;
background-image: none;
}
.benchmark-error h3 {
color: #ff8686;
}
.benchmark h3 {
font-weight: 400;
font-size: 1.6rem;

File diff suppressed because it is too large Load diff

View file

@ -39,7 +39,11 @@ function runGameboy() {
GameBoyAudioNode.run();
}
var resultHash = gameboy.canvas.resultHash;
resetGlobalVariables();
return resultHash;
}
function tearDownGameboy() {
@ -56,7 +60,7 @@ var expectedGameboyStateStr =
var GameBoyWindow = { };
function GameBoyContext() {
function GameBoyContext(canvas) {
this.createBuffer = function() {
return new Buffer();
}
@ -69,20 +73,21 @@ function GameBoyContext() {
this.putImageData = function (buffer, x, y) {
var sum = 0;
for (var i = 0; i < buffer.data.length; i++) {
sum += i * buffer.data[i];
sum = sum % 1000;
sum ^= (i * buffer.data[i]) | 0;
}
canvas.resultHash ^= sum;
}
this.drawImage = function () { }
};
function GameBoyCanvas() {
this.getContext = function() {
return new GameBoyContext();
return new GameBoyContext(this);
}
this.width = 160;
this.height = 144;
this.style = { visibility: "visibile" };
this.resultHash = 0x1a2b3c4f;
}
function cout(message, colorIndex) {

View file

@ -9782,7 +9782,15 @@ var gameboy_rom='r+BPyZiEZwA+AeBPySAobeEq6gAgKlYj5WJv6SRmZjjhKuXqACDJ///////////
setupGameboy();
class Benchmark {
EXPECTED_RESULT_HASH = 439041103;
runIteration() {
runGameboy();
this.result = runGameboy();
}
validate() {
if (this.result != this.EXPECTED_RESULT_HASH)
throw new Error(`Got unexpected result hash ${this.result} instead of ${this.EXPECTED_RESULT_HASH}`)
}
}

View file

@ -309,7 +309,7 @@ function isInteger(token)
function isString(token)
{
return /^".*"/.test(token);
return /^".*"/.test(token.string);
}
@ -426,7 +426,7 @@ class Parser
{
let result;
if (isRegister(this.tokens[this.idx])) {
if (fprPattern.test(this.tokens[this.idx]))
if (fprPattern.test(this.tokens[this.idx].string))
result = FPRegisterID.forName(this.tokens[this.idx].codeOrigin, this.tokens[this.idx].string);
else
result = RegisterID.forName(this.tokens[this.idx].codeOrigin, this.tokens[this.idx].string);
@ -813,7 +813,7 @@ class Parser
let codeOrigin = this.tokens[this.idx].codeOrigin;
let name = this.tokens[this.idx].string;
this.idx++;
if ((!final && this.idx == this.tokens.size) || (final && final.test(this.tokens[this.idx]))) {
if ((!final && this.idx == this.tokens.size) || (final && final.test(this.tokens[this.idx].string))) {
// Zero operand instruction, and it's the last one.
list.push(new Instruction(codeOrigin, name, [], this.annotation));
this.annotation = null;
@ -935,7 +935,7 @@ class Parser
let fileList = [];
fileList.push(this.tokens[this.idx].codeOrigin.fileName);
while (true) {
if ((this.idx == this.tokens.length && !final) || (final && final.test(this.tokens[this.idx])))
if ((this.idx == this.tokens.length && !final) || (final && final.test(this.tokens[this.idx].string)))
break;
else if (this.tokens[this.idx].isEqualTo("include")) {
this.idx++;

View file

@ -1,10 +1,3 @@
function arrayExists(array, x) {
for (var i = 0; i < array.length; i++) {
if (array[i] == x) return true;
}
return false;
}
Date.prototype.formatDate = function (input,time) {
// formatDate :
// a PHP date like function, for formatting date strings
@ -23,9 +16,6 @@ Date.prototype.formatDate = function (input,time) {
// unsupported:
// I (capital i), T, Z
var switches = ["a", "A", "B", "d", "D", "F", "g", "G", "h", "H",
"i", "j", "l", "L", "m", "M", "n", "O", "r", "s",
"S", "t", "U", "w", "W", "y", "Y", "z"];
var daysLong = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"];
var daysShort = ["Sun", "Mon", "Tue", "Wed",
@ -41,226 +31,249 @@ Date.prototype.formatDate = function (input,time) {
"th", "th", "th", "th", "th", "th", "st", // 15th - 21st
"nd", "rd", "th", "th", "th", "th", "th", // 22nd - 28th
"th", "th", "st"]; // 29th - 31st
var formatters = {
a() {
// Lowercase Ante meridiem and Post meridiem
return self.getHours() > 11? "pm" : "am";
},
A() {
// Uppercase Ante meridiem and Post meridiem
return self.getHours() > 11? "PM" : "AM";
},
function a() {
// Lowercase Ante meridiem and Post meridiem
return self.getHours() > 11? "pm" : "am";
}
function A() {
// Uppercase Ante meridiem and Post meridiem
return self.getHours() > 11? "PM" : "AM";
}
function B(){
// Swatch internet time. code simply grabbed from ppk,
// since I was feeling lazy:
// http://www.xs4all.nl/~ppk/js/beat.html
var off = (self.getTimezoneOffset() + 60)*60;
var theSeconds = (self.getHours() * 3600) +
(self.getMinutes() * 60) +
self.getSeconds() + off;
var beat = Math.floor(theSeconds/86.4);
if (beat > 1000) beat -= 1000;
if (beat < 0) beat += 1000;
if ((""+beat).length == 1) beat = "00"+beat;
if ((""+beat).length == 2) beat = "0"+beat;
return beat;
}
function d() {
// Day of the month, 2 digits with leading zeros
return new String(self.getDate()).length == 1?
"0"+self.getDate() : self.getDate();
}
function D() {
// A textual representation of a day, three letters
return daysShort[self.getDay()];
}
function F() {
// A full textual representation of a month
return monthsLong[self.getMonth()];
}
function g() {
// 12-hour format of an hour without leading zeros
return self.getHours() > 12? self.getHours()-12 : self.getHours();
}
function G() {
// 24-hour format of an hour without leading zeros
return self.getHours();
}
function h() {
// 12-hour format of an hour with leading zeros
if (self.getHours() > 12) {
var s = new String(self.getHours()-12);
return s.length == 1?
"0"+ (self.getHours()-12) : self.getHours()-12;
} else {
var s = new String(self.getHours());
return s.length == 1?
"0"+self.getHours() : self.getHours();
}
}
function H() {
// 24-hour format of an hour with leading zeros
return new String(self.getHours()).length == 1?
"0"+self.getHours() : self.getHours();
}
function i() {
// Minutes with leading zeros
return new String(self.getMinutes()).length == 1?
"0"+self.getMinutes() : self.getMinutes();
}
function j() {
// Day of the month without leading zeros
return self.getDate();
}
function l() {
// A full textual representation of the day of the week
return daysLong[self.getDay()];
}
function L() {
// leap year or not. 1 if leap year, 0 if not.
// the logic should match iso's 8601 standard.
var y_ = Y();
if (
(y_ % 4 == 0 && y_ % 100 != 0) ||
(y_ % 4 == 0 && y_ % 100 == 0 && y_ % 400 == 0)
) {
return 1;
} else {
return 0;
}
}
function m() {
// Numeric representation of a month, with leading zeros
return self.getMonth() < 9?
"0"+(self.getMonth()+1) :
self.getMonth()+1;
}
function M() {
// A short textual representation of a month, three letters
return monthsShort[self.getMonth()];
}
function n() {
// Numeric representation of a month, without leading zeros
return self.getMonth()+1;
}
function O() {
// Difference to Greenwich time (GMT) in hours
var os = Math.abs(self.getTimezoneOffset());
var h = ""+Math.floor(os/60);
var m = ""+(os%60);
h.length == 1? h = "0"+h:1;
m.length == 1? m = "0"+m:1;
return self.getTimezoneOffset() < 0 ? "+"+h+m : "-"+h+m;
}
function r() {
// RFC 822 formatted date
var r; // result
// Thu , 21 Dec 2000
r = D() + ", " + j() + " " + M() + " " + Y() +
// 16 : 01 : 07 +0200
" " + H() + ":" + i() + ":" + s() + " " + O();
return r;
}
function S() {
// English ordinal suffix for the day of the month, 2 characters
return daysSuffix[self.getDate()-1];
}
function s() {
// Seconds, with leading zeros
return new String(self.getSeconds()).length == 1?
"0"+self.getSeconds() : self.getSeconds();
}
function t() {
// thanks to Matt Bannon for some much needed code-fixes here!
var daysinmonths = [null,31,28,31,30,31,30,31,31,30,31,30,31];
if (L()==1 && n()==2) return 29; // leap day
return daysinmonths[n()];
}
function U() {
// Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)
return Math.round(self.getTime()/1000);
}
function W() {
// Weeknumber, as per ISO specification:
// http://www.cl.cam.ac.uk/~mgk25/iso-time.html
B(){
// Swatch internet time. code simply grabbed from ppk,
// since I was feeling lazy:
// http://www.xs4all.nl/~ppk/js/beat.html
var off = (self.getTimezoneOffset() + 60)*60;
var theSeconds = (self.getHours() * 3600) +
(self.getMinutes() * 60) +
self.getSeconds() + off;
var beat = Math.floor(theSeconds/86.4);
if (beat > 1000) beat -= 1000;
if (beat < 0) beat += 1000;
if ((""+beat).length == 1) beat = "00"+beat;
if ((""+beat).length == 2) beat = "0"+beat;
return beat;
},
// if the day is three days before newyears eve,
// there's a chance it's "week 1" of next year.
// here we check for that.
var beforeNY = 364+L() - z();
var afterNY = z();
var weekday = w()!=0?w()-1:6; // makes sunday (0), into 6.
if (beforeNY <= 2 && weekday <= 2-beforeNY) {
return 1;
}
// similarly, if the day is within threedays of newyears
// there's a chance it belongs in the old year.
var ny = new Date("January 1 " + Y() + " 00:00:00");
var nyDay = ny.getDay()!=0?ny.getDay()-1:6;
if (
(afterNY <= 2) &&
(nyDay >=4) &&
(afterNY >= (6-nyDay))
) {
// Since I'm not sure we can just always return 53,
// i call the function here again, using the last day
// of the previous year, as the date, and then just
// return that week.
var prevNY = new Date("December 31 " + (Y()-1) + " 00:00:00");
return prevNY.formatDate("W");
}
// week 1, is the week that has the first thursday in it.
// note that this value is not zero index.
if (nyDay <= 3) {
// first day of the year fell on a thursday, or earlier.
return 1 + Math.floor( ( z() + nyDay ) / 7 );
} else {
// first day of the year fell on a friday, or later.
return 1 + Math.floor( ( z() - ( 7 - nyDay ) ) / 7 );
}
}
function w() {
// Numeric representation of the day of the week
return self.getDay();
}
function Y() {
// A full numeric representation of a year, 4 digits
d() {
// Day of the month, 2 digits with leading zeros
return String(self.getDate()).length == 1?
"0"+self.getDate() : self.getDate();
},
// we first check, if getFullYear is supported. if it
// is, we just use that. ppks code is nice, but wont
// work with dates outside 1900-2038, or something like that
if (self.getFullYear) {
var newDate = new Date("January 1 2001 00:00:00 +0000");
var x = newDate .getFullYear();
if (x == 2001) {
// i trust the method now
return self.getFullYear();
D() {
// A textual representation of a day, three letters
return daysShort[self.getDay()];
},
F() {
// A full textual representation of a month
return monthsLong[self.getMonth()];
},
g() {
// 12-hour format of an hour without leading zeros
return self.getHours() > 12? self.getHours()-12 : self.getHours();
},
G() {
// 24-hour format of an hour without leading zeros
return self.getHours();
},
h() {
// 12-hour format of an hour with leading zeros
if (self.getHours() > 12) {
var s = String(self.getHours()-12);
return s.length == 1?
"0"+ (self.getHours()-12) : self.getHours()-12;
} else {
var s = String(self.getHours());
return s.length == 1?
"0"+self.getHours() : self.getHours();
}
}
// else, do this:
// codes thanks to ppk:
// http://www.xs4all.nl/~ppk/js/introdate.html
var x = self.getYear();
var y = x % 100;
y += (y < 38) ? 2000 : 1900;
return y;
}
function y() {
// A two-digit representation of a year
var y = Y()+"";
return y.substring(y.length-2,y.length);
}
function z() {
// The day of the year, zero indexed! 0 through 366
var t = new Date("January 1 " + Y() + " 00:00:00");
var diff = self.getTime() - t.getTime();
return Math.floor(diff/1000/60/60/24);
}
},
H() {
// 24-hour format of an hour with leading zeros
return String(self.getHours()).length == 1?
"0"+self.getHours() : self.getHours();
},
i() {
// Minutes with leading zeros
return String(self.getMinutes()).length == 1?
"0"+self.getMinutes() : self.getMinutes();
},
j() {
// Day of the month without leading zeros
return self.getDate();
},
l() {
// A full textual representation of the day of the week
return daysLong[self.getDay()];
},
L() {
// leap year or not. 1 if leap year, 0 if not.
// the logic should match iso's 8601 standard.
var y_ = formatters.Y();
if (
(y_ % 4 == 0 && y_ % 100 != 0) ||
(y_ % 4 == 0 && y_ % 100 == 0 && y_ % 400 == 0)
) {
return 1;
} else {
return 0;
}
},
m() {
// Numeric representation of a month, with leading zeros
return self.getMonth() < 9?
"0"+(self.getMonth()+1) :
self.getMonth()+1;
},
M() {
// A short textual representation of a month, three letters
return monthsShort[self.getMonth()];
},
n() {
// Numeric representation of a month, without leading zeros
return self.getMonth()+1;
},
O() {
// Difference to Greenwich time (GMT) in hours
var os = Math.abs(self.getTimezoneOffset());
var h = ""+Math.floor(os/60);
var m = ""+(os%60);
h.length == 1? h = "0"+h:1;
m.length == 1? m = "0"+m:1;
return self.getTimezoneOffset() < 0 ? "+"+h+m : "-"+h+m;
},
r() {
// RFC 822 formatted date
var r; // result
// Thu , 21 Dec 2000
r = formatters.D() + ", " + formatters.j() + " " + formatters.M() + " " + formatters.Y() +
// 16 : 01 : 07 +0200
" " + formatters.H() + ":" + formatters.i() + ":" + formatters.s() + " " + formatters.O();
return r;
},
S() {
// English ordinal suffix for the day of the month, 2 characters
return daysSuffix[self.getDate()-1];
},
s() {
// Seconds, with leading zeros
return String(self.getSeconds()).length == 1?
"0"+self.getSeconds() : self.getSeconds();
},
t() {
// thanks to Matt Bannon for some much needed code-fixes here!
var daysinmonths = [null,31,28,31,30,31,30,31,31,30,31,30,31];
if (formatters.L()==1 && n()==2) return 29; // leap day
return daysinmonths[n()];
},
U() {
// Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)
return Math.round(self.getTime()/1000);
},
W() {
// Weeknumber, as per ISO specification:
// http://www.cl.cam.ac.uk/~mgk25/iso-time.html
// if the day is three days before newyears eve,
// there's a chance it's "week 1" of next year.
// here we check for that.
var beforeNY = 364 + formatters.L() - formatters.z();
var afterNY = formatters.z();
var weekday = formatters.w() !=0 ? formatters.w()-1 : 6; // makes sunday (0), into 6.
if (beforeNY <= 2 && weekday <= 2-beforeNY) {
return 1;
}
// similarly, if the day is within threedays of newyears
// there's a chance it belongs in the old year.
var ny = new Date("January 1 " + Y() + " 00:00:00");
var nyDay = ny.getDay()!=0?ny.getDay()-1:6;
if (
(afterNY <= 2) &&
(nyDay >=4) &&
(afterNY >= (6-nyDay))
) {
// Since I'm not sure we can just always return 53,
// i call the function here again, using the last day
// of the previous year, as the date, and then just
// return that week.
var prevNY = new Date("December 31 " + (formatters.Y()-1) + " 00:00:00");
return prevNY.formatDate("W");
}
// week 1, is the week that has the first thursday in it.
// note that this value is not zero index.
if (nyDay <= 3) {
// first day of the year fell on a thursday, or earlier.
return 1 + Math.floor((formatters.z() + nyDay ) / 7 );
} else {
// first day of the year fell on a friday, or later.
return 1 + Math.floor((formatters.z() - ( 7 - nyDay ) ) / 7 );
}
},
w() {
// Numeric representation of the day of the week
return self.getDay();
},
Y() {
// A full numeric representation of a year, 4 digits
// we first check, if getFullYear is supported. if it
// is, we just use that. ppks code is nice, but wont
// work with dates outside 1900-2038, or something like that
if (self.getFullYear) {
var newDate = new Date("January 1 2001 00:00:00 +0000");
var x = newDate .getFullYear();
if (x == 2001) {
// i trust the method now
return self.getFullYear();
}
}
// else, do this:
// codes thanks to ppk:
// http://www.xs4all.nl/~ppk/js/introdate.html
var x = self.getYear();
var y = x % 100;
y += (y < 38) ? 2000 : 1900;
return y;
},
y() {
// A two-digit representation of a year
var y = formatters.Y()+"";
return y.substring(y.length-2,y.length);
},
z() {
// The day of the year, zero indexed! 0 through 366
var t = new Date("January 1 " + Y() + " 00:00:00");
var diff = self.getTime() - t.getTime();
return Math.floor(diff/1000/60/60/24);
},
};
var self = this;
if (time) {
@ -276,8 +289,9 @@ Date.prototype.formatDate = function (input,time) {
// this is our way of allowing users to escape stuff
ia.splice(ij,1);
} else {
if (arrayExists(switches,ia[ij])) {
ia[ij] = eval(ia[ij] + "()");
var formatter = formatters[ia[ij]];
if (formatter) {
ia[ij] = formatter();
}
}
ij++;
@ -291,21 +305,34 @@ Date.prototype.formatDate = function (input,time) {
function run() {
var date = new Date("1/1/2007 1:11:11");
var resultHash = 0x1a2b3c4d;
for (i = 0; i < 500; ++i) {
var shortFormat = date.formatDate("Y-m-d");
var longFormat = date.formatDate("l, F d, Y g:i:s A");
date.setTime(date.getTime() + 84266956);
// Assuming only ascii output.
resultHash ^= shortFormat.charCodeAt(6) | shortFormat.charCodeAt(8) << 8;
resultHash ^= longFormat.charCodeAt(10) << 16 | longFormat.charCodeAt(11) << 24;
}
// FIXME: Find a way to validate this test.
// https://bugs.webkit.org/show_bug.cgi?id=114849
return resultHash;
}
class Benchmark {
EXPECTED_RESULT_HASH = 439041101;
runIteration() {
this.resultHash = 0x1a2b3c4d;
for (let i = 0; i < 8; ++i)
run();
this.resultHash ^= run();
}
validate() {
if (this.resultHash != this.EXPECTED_RESULT_HASH)
throw new Error(`Got unexpected result hash ${this.resultHash} instead of ${this.EXPECTED_RESULT_HASH}`)
}
}

View file

@ -349,7 +349,7 @@ String.escape = function(string) {
}
String.leftPad = function (val, size, ch) {
var result = new String(val);
var result = String(val);
if (ch == null) {
ch = " ";
}
@ -410,21 +410,34 @@ Date.patterns = {
function run() {
var date = new Date("1/1/2007 1:11:11");
var resultHash = 0x1a2b3c4d;
for (i = 0; i < 4000; ++i) {
var shortFormat = date.dateFormat("Y-m-d");
var longFormat = date.dateFormat("l, F d, Y g:i:s A");
date.setTime(date.getTime() + 84266956);
// Assuming only ascii output.
resultHash ^= shortFormat.charCodeAt(6) | shortFormat.charCodeAt(8) << 8;
resultHash ^= longFormat.charCodeAt(10) << 16 | longFormat.charCodeAt(11) << 24;
}
// FIXME: Find a way to validate this test.
// https://bugs.webkit.org/show_bug.cgi?id=114849
return resultHash;
}
class Benchmark {
EXPECTED_RESULT_HASH = 439041101;
runIteration() {
this.resultHash = 0x1a2b3c4d;
for (let i = 0; i < 8; ++i)
run();
this.resultHash ^= run();
}
validate() {
if (this.resultHash != this.EXPECTED_RESULT_HASH)
throw new Error(`Got unexpected result hash ${this.resultHash} instead of ${this.EXPECTED_RESULT_HASH}`)
}
}

8
cli.js
View file

@ -25,7 +25,8 @@
const isInBrowser = false;
console = {
log: print
log: globalThis?.console?.log ?? print,
error: globalThis?.console?.error ?? print,
}
const isD8 = typeof Realm !== "undefined";
@ -57,8 +58,9 @@ async function runJetStream() {
await JetStream.initialize();
await JetStream.start();
} catch (e) {
console.log("JetStream3 failed: " + e);
console.log(e.stack);
console.error("JetStream3 failed: " + e);
console.error(e.stack);
throw e;
}
}
runJetStream();

1597
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@
"version": "3.0.0-alpha",
"description": "An open source repository for the JetStream benchmark.",
"engines": {
"node": ">=18.13.0",
"node": ">=22.0.0",
"npm": ">=8.19.3"
},
"repository": {
@ -22,7 +22,10 @@
"test:chrome": "node tests/run.mjs --browser chrome",
"test:firefox": "node tests/run.mjs --browser firefox",
"test:safari": "node tests/run.mjs --browser safari",
"test:edge": "node tests/run.mjs --browser edge"
"test:edge": "node tests/run.mjs --browser edge",
"test:v8": "node tests/run-shell.mjs --shell v8",
"test:jsc": "node tests/run-shell.mjs --shell jsc",
"test:spidermonkey": "node tests/run-shell.mjs --shell spidermonkey"
},
"devDependencies": {
"@babel/core": "^7.21.3",
@ -30,11 +33,13 @@
"@babel/plugin-proposal-decorators": "^7.21.0",
"command-line-args": "^5.2.1",
"command-line-usage": "^6.1.3",
"es-main": "^1.3.0",
"eslint": "^8.38.0",
"http-server": "^14.1.1",
"jsvu": "^2.5.1",
"local-web-server": "^5.4.0",
"prettier": "^2.8.3",
"selenium-webdriver": "^4.8.0",
"es-main":"^1.3.0",
"local-web-server": "^5.4.0"
"@actions/core": "^1.11.1"
}
}

View file

@ -85,7 +85,7 @@ class Benchmark {
//"--memdb", // note that memdb trumps the filename arg
"--nomemstat",
"--big-transactions" /*important for tests 410 and 510!*/,
"--size", "10", // To speedup, default is 100 (and takes about 4s).
"--size", "2", // To speedup, default is 100 (and takes about 4s).
"--vfs", vfs, // See VFS comment above.
];

188
tests/run-shell.mjs Normal file
View file

@ -0,0 +1,188 @@
#! /usr/bin/env node
import commandLineArgs from "command-line-args";
import commandLineUsage from "command-line-usage";
import { spawnSync } from "child_process";
import { fileURLToPath } from "url";
import { styleText } from "node:util";
import * as path from "path";
import * as fs from "fs";
import * as os from "os";
import core from "@actions/core"
const optionDefinitions = [
{ name: "shell", type: String, description: "Set the shell to test, choices are [jsc, v8, spidermonkey]." },
{ name: "help", alias: "h", description: "Print this help text." },
];
function printHelp(message = "") {
const usage = commandLineUsage([
{
header: "Run all tests",
},
{
header: "Options",
optionList: optionDefinitions,
},
]);
if (!message) {
console.log(usage);
process.exit(0);
} else {
console.error(message);
console.error();
console.error(usage);
process.exit(1);
}
}
const options = commandLineArgs(optionDefinitions);
if ("help" in options)
printHelp();
const JS_SHELL= options?.shell;
if (!JS_SHELL)
printHelp("No javascript shell specified, use --shell");
const SHELL_NAME = (function() {
switch (JS_SHELL) {
case "javascriptcore":
case "jsc": {
return "javascriptcore";
}
case "spidermonkey": {
return "spidermonkey";
}
case "v8": {
return "v8";
}
default: {
printHelp(`Invalid shell "${JS_SHELL}", choices are: "jsc", "spidermonkey" and "v8)`);
}
}
})();
const FILE_PATH = fileURLToPath(import.meta.url);
const SRC_DIR = path.dirname(path.dirname(FILE_PATH));
const CLI_PATH = path.join(SRC_DIR, "cli.js");
const BASE_CLI_ARGS_WITH_OPTIONS = [CLI_PATH];
if (SHELL_NAME != "spidermonkey")
BASE_CLI_ARGS_WITH_OPTIONS.push("--");
Object.freeze(BASE_CLI_ARGS_WITH_OPTIONS);
const GITHUB_ACTIONS_OUTPUT = "GITHUB_ACTIONS_OUTPUT" in process.env;
function log(...args) {
const text = args.join(" ")
if (GITHUB_ACTIONS_OUTPUT)
core.info(styleText("yellow", text))
else
console.log(styleText("yellow", text))
}
function logError(...args) {
const text = args.join(" ")
if (GITHUB_ACTIONS_OUTPUT)
core.error(styleText("red", text))
else
console.error(styleText("red", text))
}
function logGroup(name, body) {
if (GITHUB_ACTIONS_OUTPUT) {
core.startGroup(name);
} else {
log("=".repeat(80))
log(name);
log(".".repeat(80))
}
try {
return body();
} finally {
if (GITHUB_ACTIONS_OUTPUT)
core.endGroup();
}
}
const SPAWN_OPTIONS = {
stdio: ["inherit", "inherit", "inherit"]
};
function sh(binary, args) {
const cmd = `${binary} ${args.join(" ")}`;
if (GITHUB_ACTIONS_OUTPUT) {
core.startGroup(binary);
core.notice(styleText("blue", cmd));
} else {
console.log(styleText("blue", cmd));
}
try {
const result = spawnSync(binary, args, SPAWN_OPTIONS);
if (result.status || result.error) {
logError(result.error);
throw new Error(`Shell CMD failed: ${binary} ${args.join(" ")}`);
}
} finally {
if (GITHUB_ACTIONS_OUTPUT)
core.endGroup()
}
}
async function runTests() {
const shellBinary = logGroup(`Installing JavaScript Shell: ${SHELL_NAME}`, testSetup);
let success = true;
success &&= runTest("Run Complete Suite", () => sh(shellBinary, [CLI_PATH]));
success &&= runTest("Run Single Suite", () => {
const singleTestArgs = [...BASE_CLI_ARGS_WITH_OPTIONS, "proxy-mobx"];
sh(shellBinary, singleTestArgs);
});
if (!success) {
process.exit(1)
}
}
function jsvuOSName() {
const osName = () => {
switch (os.platform()) {
case "win32": return "win";
case "darwin": return "mac";
case "linux": return "linux";
default: throw new Error("Unsupported OS");
}
};
const osArch = () => {
switch (os.arch()) {
case "x64": return "64";
case "arm64": return "64arm";
default: throw new Error("Unsupported architecture");
}
};
return `${osName()}${osArch()}`
}
const DEFAULT_JSC_LOCATION = "/System/Library/Frameworks/JavaScriptCore.framework/Versions/Current/Helpers/jsc"
function testSetup() {
sh("jsvu", [`--engines=${SHELL_NAME}`, `--os=${jsvuOSName()}`]);
let shellBinary = path.join(os.homedir(), ".jsvu/bin", SHELL_NAME);
if (!fs.existsSync(shellBinary) && SHELL_NAME == "javascriptcore")
shellBinary = DEFAULT_JSC_LOCATION
if (!fs.existsSync(shellBinary))
throw new Error(`Could not find shell binary: ${shellBinary}`);
log(`Installed JavaScript Shell: ${shellBinary}`);
return shellBinary
}
function runTest(testName, test) {
try {
logGroup(testName, test)
} catch(e) {
logError("TEST FAILED")
return false
}
return true
}
setImmediate(runTests);

View file

@ -4,10 +4,9 @@ import serve from "./server.mjs";
import { Builder, Capabilities } from "selenium-webdriver";
import commandLineArgs from "command-line-args";
import commandLineUsage from "command-line-usage";
import assert from "assert";
const optionDefinitions = [
{ name: "browser", type: String, description: "Set the browser to test, choices are [safari, firefox, chrome]. By default the $BROWSER env variable is used." },
{ name: "browser", type: String, description: "Set the browser to test, choices are [safari, firefox, chrome, edge]. By default the $BROWSER env variable is used." },
{ name: "port", type: Number, defaultValue: 8010, description: "Set the test-server port, The default value is 8010." },
{ name: "help", alias: "h", description: "Print this help text." },
];
@ -81,7 +80,9 @@ async function testEnd2End() {
const driver = await new Builder().withCapabilities(capabilities).build();
let results;
try {
await driver.get(`http://localhost:${PORT}/index.html?worstCaseCount=2&iterationCount=3`);
const url = `http://localhost:${PORT}/index.html?worstCaseCount=2&iterationCount=3`;
console.log(`JetStream PREPARE ${url}`);
await driver.get(url);
await driver.executeAsyncScript((callback) => {
// callback() is explicitly called without the default event
// as argument to avoid serialization issues with chromedriver.
@ -91,14 +92,9 @@ async function testEnd2End() {
if (globalThis?.JetStream?.isReady)
callback()
});
await driver.manage().setTimeouts({ script: 3 * 60_000 });
results = await driver.executeAsyncScript((callback) => {
globalThis.addEventListener("JetStreamDone", event => callback(event.detail));
JetStream.start();
});
results = await benchmarkResults(driver);
// FIXME: validate results;
console.log("\n✅ Tests completed!");
console.log("RESULTS:")
console.log(results)
} catch(e) {
console.error("\n❌ Tests failed!");
console.error(e);
@ -109,4 +105,59 @@ async function testEnd2End() {
}
}
async function benchmarkResults(driver) {
console.log("JetStream START");
await driver.manage().setTimeouts({ script: 60_000 });
await driver.executeAsyncScript((callback) => {
globalThis.JetStream.start();
callback();
});
await new Promise((resolve, reject) => pollResultsUntilDone(driver, resolve, reject));
const resultsJSON = await driver.executeScript(() => {
return globalThis.JetStream.resultsJSON();
});
return JSON.parse(resultsJSON);
}
class JetStreamTestError extends Error {
constructor(errors) {
super(`Tests failed: ${errors.map(e => e.name).join(", ")}`);
this.errors = errors;
}
}
const UPDATE_INTERVAL = 250;
async function pollResultsUntilDone(driver, resolve, reject) {
const previousResults = new Set();
const intervalId = setInterval(async function logResult() {
const {done, errors, resultsJSON} = await driver.executeScript(() => {
return {
done: globalThis.JetStream.isDone,
errors: globalThis.JetStream.errors,
resultsJSON: JSON.stringify(globalThis.JetStream.resultsObject("simple")),
};
});
if (errors.length) {
clearInterval(intervalId);
reject(new JetStreamTestError(errors));
}
logIncrementalResult(previousResults, JSON.parse(resultsJSON));
if (done) {
clearInterval(intervalId);
resolve();
}
}, UPDATE_INTERVAL)
}
function logIncrementalResult(previousResults, benchmarkResults) {
for (const [testName, testResults] of Object.entries(benchmarkResults)) {
if (previousResults.has(testName))
continue;
console.log(testName, testResults);
previousResults.add(testName);
}
}
setImmediate(testEnd2End);

View file

@ -33,8 +33,9 @@ testList = [
"tfjs-wasm",
"tfjs-wasm-simd",
"argon2-wasm",
"argon2-wasm-simd",
"8bitbench-wasm",
"Dart-flute-wasm",
"zlib-wasm",
];
// Reuse the full CLI runner, just with the subset of Wasm line items above.

View file

@ -28,6 +28,6 @@ class Benchmark {
if (!Module._runIteration)
await setupModule(Module);
Module._runIteration(150);
Module._runIteration(50);
}
};

View file

@ -1,44 +0,0 @@
/*
* Copyright (C) 2023 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
async function doRun() {
let start = benchmarkTime();
if (!isInBrowser) {
globalThis.argon2WasmSimdBlob = Module.argon2WasmSimdBlob;
globalThis.argon2WasmBlob = Module.argon2WasmBlob;
}
await instantiateArgon2WasmInstance();
await testArgon2HashAndVerify();
reportCompileTime(benchmarkTime() - start);
start = benchmarkTime();
for (let i = 0; i < 5; ++i)
await testArgon2HashAndVerify();
reportRunTime(benchmarkTime() - start)
}

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -1,118 +0,0 @@
globalThis.instantiateArgon2WasmInstance = async function () {
await argon2.loadModule();
};
let randomStrings = [
'iqqdAsbeeb', 'B3tVB5h2AI',
'Z7ihQxGE93', 'ou2jsFcbur',
'0pjTjkrnsM', 'B8VRY9tCVY',
'6Kg3AmWnVc', '3jcEFqDAPT',
'JeehIKnWiO', '2mSSPZacYX',
'CGRhFvbj3M', 'aQPgDC7e5j',
'bSUE8duQ4u', 'Xp3FEiAEXi',
'tusLv0aHkH', '4GUC7Hx4yk',
'yJIS8ioQLH', 'OmS9931mEz',
'ekSwTfKgvC', '3kj2W1fy9F',
'1oXMME4xPV', 'UDDI3kCDqy',
'xglo5DvJm3', '2SWNQygbHm',
'Msfw59Go9X', 'X9vvXuoDM0',
'XUvEPoHvXC', 'pffHsNtWBg',
'EQPLyqD9U5', 'yBpDjiKHHj',
'2gStCWSzhD', 'k74wcxwve7',
'TU2BIQTMqE', 'bVaCWRL4l3',
'BMTlPabHpu', 'KXv8hmWApM',
'lpvttDdRli', 'M4WB62T2qy',
'FbSoElUBC0', 'QpsrcOC4C1'
];
async function hashAndVerify(params) {
let hash = await argon2.hash(params);
await argon2.verify({
pass: params.pass,
encoded: hash.encoded,
type: hash.type,
ad: params.ad,
secret: params.secret
}).catch(e => { throw new Error(e.message) });
}
globalThis.testArgon2HashAndVerify = async function () {
for (let i = 0; i < randomStrings.length; i += 2) {
await hashAndVerify({
pass: randomStrings[i],
salt: randomStrings[i + 1],
});
}
await hashAndVerify({
pass: 'p@ssw0rd',
salt: 'somesalt',
});
await hashAndVerify({
pass: 'p@ssw0rd',
salt: 'somesalt',
type: argon2.ArgonType.Argon2d,
});
await hashAndVerify({
pass: 'p@ssw0rd',
salt: 'somesalt',
type: argon2.ArgonType.Argon2i,
});
await hashAndVerify({
pass: 'p@ssw0rd',
salt: 'somesalt',
type: argon2.ArgonType.Argon2id,
});
await hashAndVerify({
pass: 'p@ssw0rd',
salt: 'somesalt',
time: 10,
});
await hashAndVerify({
pass: 'p@ssw0rd',
salt: 'somesalt',
mem: 2048,
});
await hashAndVerify({
pass: 'p@ssw0rd',
salt: 'somesalt',
hashLen: 32,
});
await hashAndVerify({
pass: 'p@ssw0rd',
salt: 'somesalt',
parallelism: 4,
});
await hashAndVerify({
pass: '汉字漢字',
salt: 'asdfasdfasdfasdf',
type: argon2.ArgonType.Argon2id,
hashLen: 32,
});
await hashAndVerify({
pass: 'p@ssw0rd',
salt: 'somesalt',
ad: new Uint8Array([1, 2, 3]),
});
await hashAndVerify({
pass: 'p@ssw0rd',
salt: 'somesalt',
secret: new Uint8Array([1, 2, 3]),
});
await hashAndVerify({
pass: 'p@ssw0rd',
salt: 'somesalt',
hashLen: 100000,
});
};

Binary file not shown.

View file

@ -1,33 +0,0 @@
cmake_minimum_required(VERSION 3.12)
project(agron2)
add_definitions(-DARGON2_NO_THREADS)
set(ARCHIVE_OUTPUT_DIRECTORY dist)
set(LIBRARY_OUTPUT_DIRECTORY dist)
set(RUNTIME_OUTPUT_DIRECTORY dist)
set(EXECUTABLE_OUTPUT_PATH dist)
include_directories(argon2/include)
set(sources
argon2/src/blake2/blake2b.c
argon2/src/argon2.c
argon2/src/core.c
argon2/src/encoding.c
argon2/src/thread.c
src/argon2_library.c
)
if(${CMAKE_C_FLAGS} MATCHES simd)
list(APPEND sources argon2/src/opt.c)
else()
list(APPEND sources argon2/src/ref.c)
endif()
add_executable(argon2 ${sources})
set_target_properties(argon2 PROPERTIES OUTPUT_NAME ${OUTPUT_NAME})

View file

@ -1,11 +0,0 @@
## Building
Set `EMCC_SDK_PATH` in `build.sh` first and then you can build argon2 wasm binary files with
```bash
./build.sh
```
Prerequisites:
- emscripten with WebAssembly support ([howto](http://webassembly.org/getting-started/developers-guide/))
- CMake

116
wasm/argon2/benchmark.js Normal file
View file

@ -0,0 +1,116 @@
/*
* Copyright (C) 2025 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
let passwordStrings = [
'123456',
'p@assw0rd',
'qwerty',
'letmein',
'汉字漢字',
'كلمة المرور',
'Z7ihQxGE93',
'0pjTjkrnsM',
'6Kg3AmWnVc',
];
// Emscripten doesn't have a way to manage your pointers for you so these will free them when the wrapper dies.
// If/when https://github.com/tc39/proposal-explicit-resource-management becomes a thing we should use that syntax/symbol instead of a dispose call.
class CString {
constructor(string) {
this.ptr = Module.stringToNewUTF8(string);
this.length = Module._strlen(this.ptr);
}
dispose() {
Module._free(this.ptr);
}
}
class MallocPtr {
constructor(size) {
this.ptr = Module._malloc(size);
this.size = size;
}
dispose() {
Module._free(this.ptr);
}
}
const tCost = 3;
const mCost = 1024;
const parallelism = 1;
// There are three argon2 types (modes), but they all exercise the same computational kernel,
// so we chose the recommended one from a security standpoint.
// See wasm/argon2/include/argon2.h for the enum:
// typedef enum Argon2_type {
// Argon2_d = 0,
// Argon2_i = 1,
// Argon2_id = 2
// } argon2_type;
const argon2idType = 2;
const version = 0x13;
const saltLength = 12;
class Benchmark {
async runIteration() {
// Instantiate the Wasm module before the first run.
if (!Module._argon2_hash) {
await setupModule(Module);
}
for (let i = 0; i < passwordStrings.length; ++i)
this.hashAndVerify(passwordStrings[i]);
}
randomSalt() {
let result = new MallocPtr(saltLength);
const numWords = saltLength / 4;
for (let i = 0; i < numWords; ++i)
Module.HEAPU32[result.ptr + i] = Math.floor(Math.random() * (2 ** 32));
return result;
}
hashAndVerify(password) {
password = new CString(password);
let salt = this.randomSalt();
let hashBuffer = new MallocPtr(24);
let encodedBuffer = new MallocPtr(Module._argon2_encodedlen(tCost, mCost, parallelism, salt.size, hashBuffer.size, argon2idType) + 1);
let status = Module._argon2_hash(tCost, mCost, parallelism, password.ptr, password.length, salt.ptr, salt.size, hashBuffer.ptr, hashBuffer.size, encodedBuffer.ptr, encodedBuffer.size, argon2idType, version);
if (status !== 0)
throw new Error(`argon2_hash exited with status: ${status} (${Module.UTF8ToString(Module._argon2_error_message(status))})`);
status = Module._argon2_verify(encodedBuffer.ptr, password.ptr, password.length, argon2idType);
if (status !== 0)
throw new Error(`argon2_verify exited with status: ${status} (${Module.UTF8ToString(Module._argon2_error_message(status))})`);
password.dispose();
salt.dispose();
hashBuffer.dispose();
encodedBuffer.dispose();
}
}

View file

@ -1,37 +0,0 @@
#!/usr/bin/env bash
set -e
set -o pipefail
ARGON_JS_EXTRA_C_FLAGS=""
if [[ "$ARGON_JS_BUILD_BUILD_WITH_SIMD" == "1" ]]; then
ARGON_JS_EXTRA_C_FLAGS="-msimd128 -msse2"
fi
cmake \
-DOUTPUT_NAME="argon2" \
-DCMAKE_TOOLCHAIN_FILE="$EMCC_SDK_PATH/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake" \
-DCMAKE_VERBOSE_MAKEFILE=OFF \
-DCMAKE_BUILD_TYPE=MinSizeRel \
-DCMAKE_C_FLAGS="-O $ARGON_JS_EXTRA_C_FLAGS" \
-DCMAKE_EXE_LINKER_FLAGS="-O3 \
-s NO_FILESYSTEM=1 \
-s 'EXPORTED_FUNCTIONS=[\"_argon2_hash\",\"_argon2_hash_ext\",\"_argon2_verify\",\"_argon2_verify_ext\",\"_argon2_error_message\",\"_argon2_encodedlen\",\"_malloc\",\"_free\"]' \
-s 'EXPORTED_RUNTIME_METHODS=[\"UTF8ToString\",\"allocate\",\"ALLOC_NORMAL\"]' \
-s DEMANGLE_SUPPORT=0 \
-s ASSERTIONS=0 \
-s NO_EXIT_RUNTIME=1 \
-s TOTAL_MEMORY=16MB \
-s BINARYEN_MEM_MAX=2147418112 \
-s ALLOW_MEMORY_GROWTH=1 \
-s WASM=1" \
.
cmake --build .
shasum dist/argon2.js
shasum dist/argon2.wasm
perl -pi -e 's/"argon2.js.mem"/null/g' dist/argon2.js
perl -pi -e 's/$/if(typeof module!=="undefined")module.exports=Module;Module.unloadRuntime=function(){if(typeof self!=="undefined"){delete self.Module}Module=jsModule=wasmMemory=wasmTable=asm=buffer=HEAP8=HEAPU8=HEAP16=HEAPU16=HEAP32=HEAPU32=HEAPF32=HEAPF64=undefined;if(typeof module!=="undefined"){delete module.exports}};/' dist/argon2.js
perl -pi -e 's/typeof Module!=="undefined"\?Module:\{};/typeof self!=="undefined"&&typeof self.Module!=="undefined"?self.Module:{};var jsModule=Module;/g' dist/argon2.js
perl -pi -e 's/receiveInstantiatedSource\(output\)\{/receiveInstantiatedSource(output){Module=jsModule;if(typeof self!=="undefined")self.Module=Module;/g' dist/argon2.js

5
wasm/argon2/build.log Normal file
View file

@ -0,0 +1,5 @@
Built on 2025-02-11T16:03:39Z
Toolchain versions
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 4.0.0-git
Building...
Building done

View file

@ -1,27 +1,43 @@
#!/usr/bin/env bash
set -e
set -o pipefail
set -euo pipefail
# Log emcc version
EMCC_SDK_PATH="/path/to/emsdk"
EMCC_PATH="$EMCC_SDK_PATH/upstream/emscripten/emcc"
$EMCC_PATH --version > emcc_version.txt
rm -rf build/
# Build start
rm -rf dist
mkdir dist
touch build.log
BUILD_LOG="$(realpath build.log)"
echo "Built on $(date -u '+%Y-%m-%dT%H:%M:%SZ')" | tee "$BUILD_LOG"
./clean-cmake.sh
EMCC_SDK_PATH=$EMCC_SDK_PATH ARGON_JS_BUILD_BUILD_WITH_SIMD=1 ./build-wasm.sh
mv dist/argon2.wasm ../argon2-simd.wasm
echo "Toolchain versions" | tee -a "$BUILD_LOG"
emcc --version | head -n1 | tee -a "$BUILD_LOG"
./clean-cmake.sh
EMCC_SDK_PATH=$EMCC_SDK_PATH ARGON_JS_BUILD_BUILD_WITH_SIMD=0 ./build-wasm.sh
mv dist/argon2.wasm ../argon2.wasm
# FIXME: Redownload the source (from https://github.com/P-H-C/phc-winner-argon2) if argon2 ever has source updates. At the time of writing it was last changed 5 years ago so this is probably not a high priority.
SOURCES=(
src/blake2/blake2b.c
./clean-cmake.sh
rm -rf dist
# Build end
src/argon2.c
src/core.c
src/encoding.c
src/thread.c
echo Done
src/opt.c
)
SIMD_FLAGS=(
-msimd128
-msse2
)
echo "Building..." | tee -a "$BUILD_LOG"
mkdir build/
emcc -o build/argon2.js \
-s WASM=1 -O2 \
${SIMD_FLAGS[@]} \
-g1 --emit-symbol-map \
-DARGON2_NO_THREADS \
-s MODULARIZE=1 -s EXPORT_NAME=setupModule -s EXPORTED_RUNTIME_METHODS=stringToNewUTF8,UTF8ToString -s EXPORTED_FUNCTIONS=_argon2_hash,_argon2_verify,_argon2_encodedlen,_argon2_error_message,_malloc,_free,_strlen \
-Iinclude \
${SOURCES[@]} | tee -a "$BUILD_LOG"
echo "Building done" | tee -a "$BUILD_LOG"
ls -lth build/

768
wasm/argon2/build/argon2.js Normal file
View file

@ -0,0 +1,768 @@
var setupModule = (() => {
var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined;
if (typeof __filename != 'undefined') _scriptName = _scriptName || __filename;
return (
async function(moduleArg = {}) {
var moduleRtn;
// include: shell.js
// The Module object: Our interface to the outside world. We import
// and export values on it. There are various ways Module can be used:
// 1. Not defined. We create it here
// 2. A function parameter, function(moduleArg) => Promise<Module>
// 3. pre-run appended it, var Module = {}; ..generated code..
// 4. External script tag defines var Module.
// We need to check if Module already exists (e.g. case 3 above).
// Substitution will be replaced with actual code on later stage of the build,
// this way Closure Compiler will not mangle it (e.g. case 4. above).
// Note that if you want to run closure, and also to use Module
// after the generated code, you will need to define var Module = {};
// before the code. Then that object will be used in the code, and you
// can continue to use Module afterwards as well.
var Module = moduleArg;
// Set up the promise that indicates the Module is initialized
var readyPromiseResolve, readyPromiseReject;
var readyPromise = new Promise((resolve, reject) => {
readyPromiseResolve = resolve;
readyPromiseReject = reject;
});
// Determine the runtime environment we are in. You can customize this by
// setting the ENVIRONMENT setting at compile time (see settings.js).
// Attempt to auto-detect the environment
var ENVIRONMENT_IS_WEB = typeof window == "object";
var ENVIRONMENT_IS_WORKER = typeof WorkerGlobalScope != "undefined";
// N.b. Electron.js environment is simultaneously a NODE-environment, but
// also a web environment.
var ENVIRONMENT_IS_NODE = typeof process == "object" && typeof process.versions == "object" && typeof process.versions.node == "string" && process.type != "renderer";
if (ENVIRONMENT_IS_NODE) {}
// --pre-jses are emitted after the Module integration code, so that they can
// refer to Module (if they choose; they can also define Module)
// Sometimes an existing Module object exists with properties
// meant to overwrite the default module functionality. Here
// we collect those properties and reapply _after_ we configure
// the current environment's defaults to avoid having to be so
// defensive during initialization.
var moduleOverrides = Object.assign({}, Module);
var arguments_ = [];
var thisProgram = "./this.program";
var quit_ = (status, toThrow) => {
throw toThrow;
};
// `/` should be present at the end if `scriptDirectory` is not empty
var scriptDirectory = "";
function locateFile(path) {
if (Module["locateFile"]) {
return Module["locateFile"](path, scriptDirectory);
}
return scriptDirectory + path;
}
// Hooks that are implemented differently in different runtime environments.
var readAsync, readBinary;
if (ENVIRONMENT_IS_NODE) {
// These modules will usually be used on Node.js. Load them eagerly to avoid
// the complexity of lazy-loading.
var fs = require("fs");
var nodePath = require("path");
scriptDirectory = __dirname + "/";
// include: node_shell_read.js
readBinary = filename => {
// We need to re-wrap `file://` strings to URLs.
filename = isFileURI(filename) ? new URL(filename) : filename;
var ret = fs.readFileSync(filename);
return ret;
};
readAsync = async (filename, binary = true) => {
// See the comment in the `readBinary` function.
filename = isFileURI(filename) ? new URL(filename) : filename;
var ret = fs.readFileSync(filename, binary ? undefined : "utf8");
return ret;
};
// end include: node_shell_read.js
if (!Module["thisProgram"] && process.argv.length > 1) {
thisProgram = process.argv[1].replace(/\\/g, "/");
}
arguments_ = process.argv.slice(2);
// MODULARIZE will export the module in the proper place outside, we don't need to export here
quit_ = (status, toThrow) => {
process.exitCode = status;
throw toThrow;
};
} else // Note that this includes Node.js workers when relevant (pthreads is enabled).
// Node.js workers are detected as a combination of ENVIRONMENT_IS_WORKER and
// ENVIRONMENT_IS_NODE.
if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {
if (ENVIRONMENT_IS_WORKER) {
// Check worker, not web, since window could be polyfilled
scriptDirectory = self.location.href;
} else if (typeof document != "undefined" && document.currentScript) {
// web
scriptDirectory = document.currentScript.src;
}
// When MODULARIZE, this JS may be executed later, after document.currentScript
// is gone, so we saved it, and we use it here instead of any other info.
if (_scriptName) {
scriptDirectory = _scriptName;
}
// blob urls look like blob:http://site.com/etc/etc and we cannot infer anything from them.
// otherwise, slice off the final part of the url to find the script directory.
// if scriptDirectory does not contain a slash, lastIndexOf will return -1,
// and scriptDirectory will correctly be replaced with an empty string.
// If scriptDirectory contains a query (starting with ?) or a fragment (starting with #),
// they are removed because they could contain a slash.
if (scriptDirectory.startsWith("blob:")) {
scriptDirectory = "";
} else {
scriptDirectory = scriptDirectory.substr(0, scriptDirectory.replace(/[?#].*/, "").lastIndexOf("/") + 1);
}
{
// include: web_or_worker_shell_read.js
if (ENVIRONMENT_IS_WORKER) {
readBinary = url => {
var xhr = new XMLHttpRequest;
xhr.open("GET", url, false);
xhr.responseType = "arraybuffer";
xhr.send(null);
return new Uint8Array(/** @type{!ArrayBuffer} */ (xhr.response));
};
}
readAsync = async url => {
// Fetch has some additional restrictions over XHR, like it can't be used on a file:// url.
// See https://github.com/github/fetch/pull/92#issuecomment-140665932
// Cordova or Electron apps are typically loaded from a file:// url.
// So use XHR on webview if URL is a file URL.
if (isFileURI(url)) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest;
xhr.open("GET", url, true);
xhr.responseType = "arraybuffer";
xhr.onload = () => {
if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) {
// file URLs can return 0
resolve(xhr.response);
return;
}
reject(xhr.status);
};
xhr.onerror = reject;
xhr.send(null);
});
}
var response = await fetch(url, {
credentials: "same-origin"
});
if (response.ok) {
return response.arrayBuffer();
}
throw new Error(response.status + " : " + response.url);
};
}
} else // end include: web_or_worker_shell_read.js
{}
var out = Module["print"] || console.log.bind(console);
var err = Module["printErr"] || console.error.bind(console);
// Merge back in the overrides
Object.assign(Module, moduleOverrides);
// Free the object hierarchy contained in the overrides, this lets the GC
// reclaim data used.
moduleOverrides = null;
// Emit code to handle expected values on the Module object. This applies Module.x
// to the proper local x. This has two benefits: first, we only emit it if it is
// expected to arrive, and second, by using a local everywhere else that can be
// minified.
if (Module["arguments"]) arguments_ = Module["arguments"];
if (Module["thisProgram"]) thisProgram = Module["thisProgram"];
// perform assertions in shell.js after we set up out() and err(), as otherwise if an assertion fails it cannot print the message
// end include: shell.js
// include: preamble.js
// === Preamble library stuff ===
// Documentation for the public APIs defined in this file must be updated in:
// site/source/docs/api_reference/preamble.js.rst
// A prebuilt local version of the documentation is available at:
// site/build/text/docs/api_reference/preamble.js.txt
// You can also build docs locally as HTML or other formats in site/
// An online HTML version (which may be of a different version of Emscripten)
// is up at http://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html
var wasmBinary = Module["wasmBinary"];
// Wasm globals
var wasmMemory;
//========================================
// Runtime essentials
//========================================
// whether we are quitting the application. no code should run after this.
// set in exit() and abort()
var ABORT = false;
// Memory management
var /** @type {!Int8Array} */ HEAP8, /** @type {!Uint8Array} */ HEAPU8, /** @type {!Int16Array} */ HEAP16, /** @type {!Uint16Array} */ HEAPU16, /** @type {!Int32Array} */ HEAP32, /** @type {!Uint32Array} */ HEAPU32, /** @type {!Float32Array} */ HEAPF32, /* BigInt64Array type is not correctly defined in closure
/** not-@type {!BigInt64Array} */ HEAP64, /* BigUint64Array type is not correctly defined in closure
/** not-t@type {!BigUint64Array} */ HEAPU64, /** @type {!Float64Array} */ HEAPF64;
var runtimeInitialized = false;
// include: URIUtils.js
// Prefix of data URIs emitted by SINGLE_FILE and related options.
var dataURIPrefix = "data:application/octet-stream;base64,";
/**
* Indicates whether filename is a base64 data URI.
* @noinline
*/ var isDataURI = filename => filename.startsWith(dataURIPrefix);
/**
* Indicates whether filename is delivered via file protocol (as opposed to http/https)
* @noinline
*/ var isFileURI = filename => filename.startsWith("file://");
// end include: URIUtils.js
// include: runtime_shared.js
// include: runtime_stack_check.js
// end include: runtime_stack_check.js
// include: runtime_exceptions.js
// end include: runtime_exceptions.js
// include: runtime_debug.js
// end include: runtime_debug.js
// include: memoryprofiler.js
// end include: memoryprofiler.js
function updateMemoryViews() {
var b = wasmMemory.buffer;
Module["HEAP8"] = HEAP8 = new Int8Array(b);
Module["HEAP16"] = HEAP16 = new Int16Array(b);
Module["HEAPU8"] = HEAPU8 = new Uint8Array(b);
Module["HEAPU16"] = HEAPU16 = new Uint16Array(b);
Module["HEAP32"] = HEAP32 = new Int32Array(b);
Module["HEAPU32"] = HEAPU32 = new Uint32Array(b);
Module["HEAPF32"] = HEAPF32 = new Float32Array(b);
Module["HEAPF64"] = HEAPF64 = new Float64Array(b);
Module["HEAP64"] = HEAP64 = new BigInt64Array(b);
Module["HEAPU64"] = HEAPU64 = new BigUint64Array(b);
}
// end include: runtime_shared.js
var __ATPRERUN__ = [];
// functions called before the runtime is initialized
var __ATINIT__ = [];
// functions called during shutdown
var __ATPOSTRUN__ = [];
// functions called after the main() is called
function preRun() {
if (Module["preRun"]) {
if (typeof Module["preRun"] == "function") Module["preRun"] = [ Module["preRun"] ];
while (Module["preRun"].length) {
addOnPreRun(Module["preRun"].shift());
}
}
callRuntimeCallbacks(__ATPRERUN__);
}
function initRuntime() {
runtimeInitialized = true;
callRuntimeCallbacks(__ATINIT__);
}
function postRun() {
if (Module["postRun"]) {
if (typeof Module["postRun"] == "function") Module["postRun"] = [ Module["postRun"] ];
while (Module["postRun"].length) {
addOnPostRun(Module["postRun"].shift());
}
}
callRuntimeCallbacks(__ATPOSTRUN__);
}
function addOnPreRun(cb) {
__ATPRERUN__.unshift(cb);
}
function addOnInit(cb) {
__ATINIT__.unshift(cb);
}
function addOnPostRun(cb) {
__ATPOSTRUN__.unshift(cb);
}
// A counter of dependencies for calling run(). If we need to
// do asynchronous work before running, increment this and
// decrement it. Incrementing must happen in a place like
// Module.preRun (used by emcc to add file preloading).
// Note that you can add dependencies in preRun, even though
// it happens right before run - run will be postponed until
// the dependencies are met.
var runDependencies = 0;
var dependenciesFulfilled = null;
function addRunDependency(id) {
runDependencies++;
Module["monitorRunDependencies"]?.(runDependencies);
}
function removeRunDependency(id) {
runDependencies--;
Module["monitorRunDependencies"]?.(runDependencies);
if (runDependencies == 0) {
if (dependenciesFulfilled) {
var callback = dependenciesFulfilled;
dependenciesFulfilled = null;
callback();
}
}
}
/** @param {string|number=} what */ function abort(what) {
Module["onAbort"]?.(what);
what = "Aborted(" + what + ")";
// TODO(sbc): Should we remove printing and leave it up to whoever
// catches the exception?
err(what);
ABORT = true;
what += ". Build with -sASSERTIONS for more info.";
// Use a wasm runtime error, because a JS error might be seen as a foreign
// exception, which means we'd run destructors on it. We need the error to
// simply make the program stop.
// FIXME This approach does not work in Wasm EH because it currently does not assume
// all RuntimeErrors are from traps; it decides whether a RuntimeError is from
// a trap or not based on a hidden field within the object. So at the moment
// we don't have a way of throwing a wasm trap from JS. TODO Make a JS API that
// allows this in the wasm spec.
// Suppress closure compiler warning here. Closure compiler's builtin extern
// definition for WebAssembly.RuntimeError claims it takes no arguments even
// though it can.
// TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure gets fixed.
/** @suppress {checkTypes} */ var e = new WebAssembly.RuntimeError(what);
readyPromiseReject(e);
// Throw the error whether or not MODULARIZE is set because abort is used
// in code paths apart from instantiation where an exception is expected
// to be thrown when abort is called.
throw e;
}
var wasmBinaryFile;
function findWasmBinary() {
var f = "argon2.wasm";
if (!isDataURI(f)) {
return locateFile(f);
}
return f;
}
function getBinarySync(file) {
if (file == wasmBinaryFile && wasmBinary) {
return new Uint8Array(wasmBinary);
}
if (readBinary) {
return readBinary(file);
}
throw "both async and sync fetching of the wasm failed";
}
async function getWasmBinary(binaryFile) {
// If we don't have the binary yet, load it asynchronously using readAsync.
if (!wasmBinary) {
// Fetch the binary using readAsync
try {
var response = await readAsync(binaryFile);
return new Uint8Array(response);
} catch {}
}
// Otherwise, getBinarySync should be able to get it synchronously
return getBinarySync(binaryFile);
}
async function instantiateArrayBuffer(binaryFile, imports) {
try {
var binary = await getWasmBinary(binaryFile);
var instance = await WebAssembly.instantiate(binary, imports);
return instance;
} catch (reason) {
err(`failed to asynchronously prepare wasm: ${reason}`);
abort(reason);
}
}
async function instantiateAsync(binary, binaryFile, imports) {
if (!binary && typeof WebAssembly.instantiateStreaming == "function" && !isDataURI(binaryFile) && // Don't use streaming for file:// delivered objects in a webview, fetch them synchronously.
!isFileURI(binaryFile) && // Avoid instantiateStreaming() on Node.js environment for now, as while
// Node.js v18.1.0 implements it, it does not have a full fetch()
// implementation yet.
// Reference:
// https://github.com/emscripten-core/emscripten/pull/16917
!ENVIRONMENT_IS_NODE) {
try {
var response = fetch(binaryFile, {
credentials: "same-origin"
});
var instantiationResult = await WebAssembly.instantiateStreaming(response, imports);
return instantiationResult;
} catch (reason) {
// We expect the most common failure cause to be a bad MIME type for the binary,
// in which case falling back to ArrayBuffer instantiation should work.
err(`wasm streaming compile failed: ${reason}`);
err("falling back to ArrayBuffer instantiation");
}
}
return instantiateArrayBuffer(binaryFile, imports);
}
function getWasmImports() {
// prepare imports
return {
"env": wasmImports,
"wasi_snapshot_preview1": wasmImports
};
}
// Create the wasm instance.
// Receives the wasm imports, returns the exports.
async function createWasm() {
// Load the wasm module and create an instance of using native support in the JS engine.
// handle a generated wasm instance, receiving its exports and
// performing other necessary setup
/** @param {WebAssembly.Module=} module*/ function receiveInstance(instance, module) {
wasmExports = instance.exports;
wasmMemory = wasmExports["memory"];
updateMemoryViews();
addOnInit(wasmExports["__wasm_call_ctors"]);
removeRunDependency("wasm-instantiate");
return wasmExports;
}
// wait for the pthread pool (if any)
addRunDependency("wasm-instantiate");
// Prefer streaming instantiation if available.
function receiveInstantiationResult(result) {
// 'result' is a ResultObject object which has both the module and instance.
// receiveInstance() will swap in the exports (to Module.asm) so they can be called
// TODO: Due to Closure regression https://github.com/google/closure-compiler/issues/3193, the above line no longer optimizes out down to the following line.
// When the regression is fixed, can restore the above PTHREADS-enabled path.
return receiveInstance(result["instance"]);
}
var info = getWasmImports();
// User shell pages can write their own Module.instantiateWasm = function(imports, successCallback) callback
// to manually instantiate the Wasm module themselves. This allows pages to
// run the instantiation parallel to any other async startup actions they are
// performing.
// Also pthreads and wasm workers initialize the wasm instance through this
// path.
if (Module["instantiateWasm"]) {
try {
return Module["instantiateWasm"](info, receiveInstance);
} catch (e) {
err(`Module.instantiateWasm callback failed with error: ${e}`);
// If instantiation fails, reject the module ready promise.
readyPromiseReject(e);
}
}
wasmBinaryFile ??= findWasmBinary();
try {
var result = await instantiateAsync(wasmBinary, wasmBinaryFile, info);
var exports = receiveInstantiationResult(result);
return exports;
} catch (e) {
// If instantiation fails, reject the module ready promise.
readyPromiseReject(e);
return Promise.reject(e);
}
}
// === Body ===
// end include: preamble.js
class ExitStatus {
name="ExitStatus";
constructor(status) {
this.message = `Program terminated with exit(${status})`;
this.status = status;
}
}
var callRuntimeCallbacks = callbacks => {
while (callbacks.length > 0) {
// Pass the module as the first argument.
callbacks.shift()(Module);
}
};
var noExitRuntime = Module["noExitRuntime"] || true;
var abortOnCannotGrowMemory = requestedSize => {
abort("OOM");
};
var _emscripten_resize_heap = requestedSize => {
var oldSize = HEAPU8.length;
// With CAN_ADDRESS_2GB or MEMORY64, pointers are already unsigned.
requestedSize >>>= 0;
abortOnCannotGrowMemory(requestedSize);
};
var lengthBytesUTF8 = str => {
var len = 0;
for (var i = 0; i < str.length; ++i) {
// Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code
// unit, not a Unicode code point of the character! So decode
// UTF16->UTF32->UTF8.
// See http://unicode.org/faq/utf_bom.html#utf16-3
var c = str.charCodeAt(i);
// possibly a lead surrogate
if (c <= 127) {
len++;
} else if (c <= 2047) {
len += 2;
} else if (c >= 55296 && c <= 57343) {
len += 4;
++i;
} else {
len += 3;
}
}
return len;
};
var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => {
// Parameter maxBytesToWrite is not optional. Negative values, 0, null,
// undefined and false each don't write out any bytes.
if (!(maxBytesToWrite > 0)) return 0;
var startIdx = outIdx;
var endIdx = outIdx + maxBytesToWrite - 1;
// -1 for string null terminator.
for (var i = 0; i < str.length; ++i) {
// Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code
// unit, not a Unicode code point of the character! So decode
// UTF16->UTF32->UTF8.
// See http://unicode.org/faq/utf_bom.html#utf16-3
// For UTF8 byte structure, see http://en.wikipedia.org/wiki/UTF-8#Description
// and https://www.ietf.org/rfc/rfc2279.txt
// and https://tools.ietf.org/html/rfc3629
var u = str.charCodeAt(i);
// possibly a lead surrogate
if (u >= 55296 && u <= 57343) {
var u1 = str.charCodeAt(++i);
u = 65536 + ((u & 1023) << 10) | (u1 & 1023);
}
if (u <= 127) {
if (outIdx >= endIdx) break;
heap[outIdx++] = u;
} else if (u <= 2047) {
if (outIdx + 1 >= endIdx) break;
heap[outIdx++] = 192 | (u >> 6);
heap[outIdx++] = 128 | (u & 63);
} else if (u <= 65535) {
if (outIdx + 2 >= endIdx) break;
heap[outIdx++] = 224 | (u >> 12);
heap[outIdx++] = 128 | ((u >> 6) & 63);
heap[outIdx++] = 128 | (u & 63);
} else {
if (outIdx + 3 >= endIdx) break;
heap[outIdx++] = 240 | (u >> 18);
heap[outIdx++] = 128 | ((u >> 12) & 63);
heap[outIdx++] = 128 | ((u >> 6) & 63);
heap[outIdx++] = 128 | (u & 63);
}
}
// Null-terminate the pointer to the buffer.
heap[outIdx] = 0;
return outIdx - startIdx;
};
var stringToUTF8 = (str, outPtr, maxBytesToWrite) => stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite);
var stringToNewUTF8 = str => {
var size = lengthBytesUTF8(str) + 1;
var ret = _malloc(size);
if (ret) stringToUTF8(str, ret, size);
return ret;
};
var UTF8Decoder = typeof TextDecoder != "undefined" ? new TextDecoder : undefined;
/**
* Given a pointer 'idx' to a null-terminated UTF8-encoded string in the given
* array that contains uint8 values, returns a copy of that string as a
* Javascript String object.
* heapOrArray is either a regular array, or a JavaScript typed array view.
* @param {number=} idx
* @param {number=} maxBytesToRead
* @return {string}
*/ var UTF8ArrayToString = (heapOrArray, idx = 0, maxBytesToRead = NaN) => {
var endIdx = idx + maxBytesToRead;
var endPtr = idx;
// TextDecoder needs to know the byte length in advance, it doesn't stop on
// null terminator by itself. Also, use the length info to avoid running tiny
// strings through TextDecoder, since .subarray() allocates garbage.
// (As a tiny code save trick, compare endPtr against endIdx using a negation,
// so that undefined/NaN means Infinity)
while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr;
if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) {
return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr));
}
var str = "";
// If building with TextDecoder, we have already computed the string length
// above, so test loop end condition against that
while (idx < endPtr) {
// For UTF8 byte structure, see:
// http://en.wikipedia.org/wiki/UTF-8#Description
// https://www.ietf.org/rfc/rfc2279.txt
// https://tools.ietf.org/html/rfc3629
var u0 = heapOrArray[idx++];
if (!(u0 & 128)) {
str += String.fromCharCode(u0);
continue;
}
var u1 = heapOrArray[idx++] & 63;
if ((u0 & 224) == 192) {
str += String.fromCharCode(((u0 & 31) << 6) | u1);
continue;
}
var u2 = heapOrArray[idx++] & 63;
if ((u0 & 240) == 224) {
u0 = ((u0 & 15) << 12) | (u1 << 6) | u2;
} else {
u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heapOrArray[idx++] & 63);
}
if (u0 < 65536) {
str += String.fromCharCode(u0);
} else {
var ch = u0 - 65536;
str += String.fromCharCode(55296 | (ch >> 10), 56320 | (ch & 1023));
}
}
return str;
};
/**
* Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the
* emscripten HEAP, returns a copy of that string as a Javascript String object.
*
* @param {number} ptr
* @param {number=} maxBytesToRead - An optional length that specifies the
* maximum number of bytes to read. You can omit this parameter to scan the
* string until the first 0 byte. If maxBytesToRead is passed, and the string
* at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the
* string will cut short at that byte index (i.e. maxBytesToRead will not
* produce a string of exact length [ptr, ptr+maxBytesToRead[) N.B. mixing
* frequent uses of UTF8ToString() with and without maxBytesToRead may throw
* JS JIT optimizations off, so it is worth to consider consistently using one
* @return {string}
*/ var UTF8ToString = (ptr, maxBytesToRead) => ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : "";
var wasmImports = {
/** @export */ emscripten_resize_heap: _emscripten_resize_heap
};
var wasmExports = await createWasm();
var ___wasm_call_ctors = wasmExports["__wasm_call_ctors"];
var _argon2_hash = Module["_argon2_hash"] = wasmExports["argon2_hash"];
var _malloc = Module["_malloc"] = wasmExports["malloc"];
var _free = Module["_free"] = wasmExports["free"];
var _argon2_verify = Module["_argon2_verify"] = wasmExports["argon2_verify"];
var _strlen = Module["_strlen"] = wasmExports["strlen"];
var _argon2_error_message = Module["_argon2_error_message"] = wasmExports["argon2_error_message"];
var _argon2_encodedlen = Module["_argon2_encodedlen"] = wasmExports["argon2_encodedlen"];
var __emscripten_stack_restore = wasmExports["_emscripten_stack_restore"];
var __emscripten_stack_alloc = wasmExports["_emscripten_stack_alloc"];
var _emscripten_stack_get_current = wasmExports["emscripten_stack_get_current"];
// include: postamble.js
// === Auto-generated postamble setup entry stuff ===
Module["UTF8ToString"] = UTF8ToString;
Module["stringToNewUTF8"] = stringToNewUTF8;
function run() {
if (runDependencies > 0) {
dependenciesFulfilled = run;
return;
}
preRun();
// a preRun added a dependency, run will be called later
if (runDependencies > 0) {
dependenciesFulfilled = run;
return;
}
function doRun() {
// run may have just been called through dependencies being fulfilled just in this very frame,
// or while the async setStatus time below was happening
Module["calledRun"] = true;
if (ABORT) return;
initRuntime();
readyPromiseResolve(Module);
Module["onRuntimeInitialized"]?.();
postRun();
}
if (Module["setStatus"]) {
Module["setStatus"]("Running...");
setTimeout(() => {
setTimeout(() => Module["setStatus"](""), 1);
doRun();
}, 1);
} else {
doRun();
}
}
if (Module["preInit"]) {
if (typeof Module["preInit"] == "function") Module["preInit"] = [ Module["preInit"] ];
while (Module["preInit"].length > 0) {
Module["preInit"].pop()();
}
}
run();
// end include: postamble.js
// include: postamble_modularize.js
// In MODULARIZE mode we wrap the generated code in a factory function
// and return either the Module itself, or a promise of the module.
// We assign to the `moduleRtn` global here and configure closure to see
// this as and extern so it won't get minified.
moduleRtn = readyPromise;
return moduleRtn;
}
);
})();
if (typeof exports === 'object' && typeof module === 'object') {
module.exports = setupModule;
// This default export looks redundant, but it allows TS to import this
// commonjs style module.
module.exports.default = setupModule;
} else if (typeof define === 'function' && define['amd'])
define([], () => setupModule);

View file

@ -0,0 +1,40 @@
0:emscripten_resize_heap
1:__wasm_call_ctors
2:blake2b_update
3:blake2b_compress
4:blake2b_final
5:blake2b_long
6:argon2_type2string
7:argon2_ctx
8:argon2_hash
9:argon2_verify
10:argon2_error_message
11:argon2_encodedlen
12:secure_wipe_memory
13:clear_internal_memory
14:validate_inputs
15:decode_decimal
16:from_base64
17:to_base64
18:b64len
19:numlen
20:fill_segment
21:fill_block
22:__memset
23:siprintf
24:strlen
25:__towrite
26:__memcpy
27:printf_core
28:out
29:getint
30:pop_arg
31:pad
32:sn_write
33:wctomb
34:emscripten_builtin_malloc
35:emscripten_builtin_free
36:sbrk
37:_emscripten_stack_restore
38:_emscripten_stack_alloc
39:emscripten_stack_get_current

BIN
wasm/argon2/build/argon2.wasm Executable file

Binary file not shown.

View file

@ -1,9 +0,0 @@
#!/usr/bin/env bash
set -e
set -o pipefail
rm -rf CMakeFiles &&
rm -f CMakeCache.txt &&
rm -f Makefile &&
rm -f cmake_install.cmake

View file

@ -1,5 +0,0 @@
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.64 (a1fe3902bf73a3802eae0357d273d0e37ea79898)
Copyright (C) 2014 the Emscripten authors (see AUTHORS.txt)
This is free and open source software under the MIT license.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

View file

@ -1,171 +0,0 @@
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "../argon2/include/argon2.h"
#include "../argon2/src/encoding.h"
#include "../argon2/src/core.h"
// Copied from argon2/src/argon2.c with additional parameters: "secret"+len and "ad"+len
int argon2_hash_ext(const uint32_t t_cost, const uint32_t m_cost,
const uint32_t parallelism, const void *pwd,
const size_t pwdlen, const void *salt, const size_t saltlen,
void *hash, const size_t hashlen, char *encoded,
const size_t encodedlen, argon2_type type,
/* argon2-browser: begin added parameters */
const void* secret, const size_t secretlen,
const void* ad, const size_t adlen,
/* argon2-browser: end added parameters */
const uint32_t version){
argon2_context context;
int result;
uint8_t *out;
if (pwdlen > ARGON2_MAX_PWD_LENGTH) {
return ARGON2_PWD_TOO_LONG;
}
if (saltlen > ARGON2_MAX_SALT_LENGTH) {
return ARGON2_SALT_TOO_LONG;
}
if (hashlen > ARGON2_MAX_OUTLEN) {
return ARGON2_OUTPUT_TOO_LONG;
}
if (hashlen < ARGON2_MIN_OUTLEN) {
return ARGON2_OUTPUT_TOO_SHORT;
}
out = malloc(hashlen);
if (!out) {
return ARGON2_MEMORY_ALLOCATION_ERROR;
}
context.out = (uint8_t *)out;
context.outlen = (uint32_t)hashlen;
context.pwd = CONST_CAST(uint8_t *)pwd;
context.pwdlen = (uint32_t)pwdlen;
context.salt = CONST_CAST(uint8_t *)salt;
context.saltlen = (uint32_t)saltlen;
/* argon2-browser: begin changed parameters */
context.secret = CONST_CAST(uint8_t *)secret;
context.secretlen = (uint32_t)secretlen;
context.ad = CONST_CAST(uint8_t *)ad;
context.adlen = (uint32_t)adlen;
/* argon2-browser: end changed parameters */
context.t_cost = t_cost;
context.m_cost = m_cost;
context.lanes = parallelism;
context.threads = parallelism;
context.allocate_cbk = NULL;
context.free_cbk = NULL;
context.flags = ARGON2_DEFAULT_FLAGS;
context.version = version;
result = argon2_ctx(&context, type);
if (result != ARGON2_OK) {
clear_internal_memory(out, hashlen);
free(out);
return result;
}
/* if raw hash requested, write it */
if (hash) {
memcpy(hash, out, hashlen);
}
/* if encoding requested, write it */
if (encoded && encodedlen) {
if (encode_string(encoded, encodedlen, &context, type) != ARGON2_OK) {
clear_internal_memory(out, hashlen); /* wipe buffers if error */
clear_internal_memory(encoded, encodedlen);
free(out);
return ARGON2_ENCODING_FAIL;
}
}
clear_internal_memory(out, hashlen);
free(out);
return ARGON2_OK;
}
int argon2_verify_ext(const char *encoded, const void *pwd, const size_t pwdlen,
/* argon2-browser: begin added parameters */
const void* secret, const size_t secretlen,
const void* ad, const size_t adlen,
/* argon2-browser: end added parameters */
argon2_type type) {
argon2_context ctx;
uint8_t *desired_result = NULL;
int ret = ARGON2_OK;
size_t encoded_len;
uint32_t max_field_len;
if (pwdlen > ARGON2_MAX_PWD_LENGTH) {
return ARGON2_PWD_TOO_LONG;
}
if (encoded == NULL) {
return ARGON2_DECODING_FAIL;
}
encoded_len = strlen(encoded);
if (encoded_len > UINT32_MAX) {
return ARGON2_DECODING_FAIL;
}
/* No field can be longer than the encoded length */
max_field_len = (uint32_t)encoded_len;
ctx.saltlen = max_field_len;
ctx.outlen = max_field_len;
ctx.salt = malloc(ctx.saltlen);
ctx.out = malloc(ctx.outlen);
if (!ctx.salt || !ctx.out) {
ret = ARGON2_MEMORY_ALLOCATION_ERROR;
goto fail;
}
ctx.pwd = (uint8_t *)pwd;
ctx.pwdlen = (uint32_t)pwdlen;
ret = decode_string(&ctx, encoded, type);
if (ret != ARGON2_OK) {
goto fail;
}
/* Set aside the desired result, and get a new buffer. */
desired_result = ctx.out;
ctx.out = malloc(ctx.outlen);
if (!ctx.out) {
ret = ARGON2_MEMORY_ALLOCATION_ERROR;
goto fail;
}
/* argon2-browser: begin changed parameters */
ctx.secret = CONST_CAST(uint8_t *)secret;
ctx.secretlen = (uint32_t)secretlen;
ctx.ad = CONST_CAST(uint8_t *)ad;
ctx.adlen = (uint32_t)adlen;
/* argon2-browser: end changed parameters */
ret = argon2_verify_ctx(&ctx, (char *)desired_result, type);
if (ret != ARGON2_OK) {
goto fail;
}
fail:
free(ctx.salt);
free(ctx.out);
free(desired_result);
return ret;
}

View file

@ -9,7 +9,8 @@ class Benchmark {
await setupModule(Module);
// Set-up the problem (fill the work queue) on each run.
Module._setup();
const taskCount = 200_000;
Module._setup(taskCount);
// Repeatedly call into Wasm to stress test JS-to-Wasm call performance.
// I (dlehmann) suppose this wrapper was added (originally in JetStream 2)
@ -21,7 +22,7 @@ class Benchmark {
}
validate() {
if (!Module._validate())
if (Module._getQpktcount() !== 465212 || Module._getHoldcount() !== 186084)
throw new Error("Bad richards result!");
}
}

View file

@ -1,4 +1,4 @@
Built on 2025-01-20T14:41:13Z
Built on 2025-02-10T17:28:11Z
Toolchain versions
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.73 (ac676d5e437525d15df5fd46bc2c208ec6d376a3)
Building...

View file

@ -15,7 +15,7 @@ emcc -o build/richards.js \
-s WASM=1 -O2 -s TOTAL_MEMORY=83886080 \
-g1 --emit-symbol-map \
-s MODULARIZE=1 \
-s EXPORT_NAME=setupModule -s EXPORTED_FUNCTIONS=_setup,_scheduleIter,_validate \
-s EXPORT_NAME=setupModule -s EXPORTED_FUNCTIONS=_setup,_scheduleIter,_getQpktcount,_getHoldcount \
richards.c | tee -a "$BUILD_LOG"
echo "Building done" | tee -a "$BUILD_LOG"

View file

@ -537,9 +537,11 @@ var ___wasm_call_ctors = () => (___wasm_call_ctors = wasmExports["__wasm_call_ct
var _scheduleIter = Module["_scheduleIter"] = () => (_scheduleIter = Module["_scheduleIter"] = wasmExports["scheduleIter"])();
var _setup = Module["_setup"] = () => (_setup = Module["_setup"] = wasmExports["setup"])();
var _setup = Module["_setup"] = a0 => (_setup = Module["_setup"] = wasmExports["setup"])(a0);
var _validate = Module["_validate"] = () => (_validate = Module["_validate"] = wasmExports["validate"])();
var _getQpktcount = Module["_getQpktcount"] = () => (_getQpktcount = Module["_getQpktcount"] = wasmExports["getQpktcount"])();
var _getHoldcount = Module["_getHoldcount"] = () => (_getHoldcount = Module["_getHoldcount"] = wasmExports["getHoldcount"])();
var __emscripten_stack_restore = a0 => (__emscripten_stack_restore = wasmExports["_emscripten_stack_restore"])(a0);

View file

@ -6,9 +6,10 @@
5:handlerfn
6:devfn
7:setup
8:validate
9:emscripten_builtin_malloc
10:sbrk
11:_emscripten_stack_restore
12:_emscripten_stack_alloc
13:emscripten_stack_get_current
8:getQpktcount
9:getHoldcount
10:emscripten_builtin_malloc
11:sbrk
12:_emscripten_stack_restore
13:_emscripten_stack_alloc
14:emscripten_stack_get_current

Binary file not shown.

View file

@ -1,4 +1,4 @@
// $ emcc -o richards.html -O2 -s TOTAL_MEMORY=83886080 -g1 -s "EXPORTED_FUNCTIONS=[_setup, _scheduleIter, _validate]" ./richards.c
// $ emcc -o richards.html -O2 -s TOTAL_MEMORY=83886080 -g1 -s "EXPORTED_FUNCTIONS=[_setup, _scheduleIter, _getQpktcount, _getHoldcount]" ./richards.c
#include <emscripten.h>
@ -15,12 +15,6 @@
#include <stdio.h>
#include <stdlib.h>
#if 1
#define Count 10000*100
#define Qpktcountval 2326410
#define Holdcountval 930563
#endif
#define TRUE 1
#define FALSE 0
#define MAXINT 32767
@ -335,11 +329,11 @@ void append(struct packet *pkt, struct packet *ptr)
ptr->p_link = pkt;
}
void setup()
void setup(int count)
{
struct packet *wkq = 0;
createtask(I_IDLE, 0, wkq, S_RUN, idlefn, 1, Count);
createtask(I_IDLE, 0, wkq, S_RUN, idlefn, 1, count);
wkq = pkt(0, 0, K_WORK);
wkq = pkt(wkq, 0, K_WORK);
@ -370,7 +364,12 @@ void setup()
layout = 0;
}
int validate()
int getQpktcount()
{
return qpktcount == Qpktcountval && holdcount == Holdcountval;
return qpktcount;
}
int getHoldcount()
{
return holdcount;
}

38
wasm/zlib/benchmark.js Normal file
View file

@ -0,0 +1,38 @@
// Copyright 2025 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
class Benchmark {
async runIteration() {
if (!Module._main)
await setupModule(Module);
Module.FS.writeFile('input', Module.wasmBinary);
const inputStr = Module.stringToNewUTF8('input');
const inputzStr = Module.stringToNewUTF8('input.z');
const inputzoutStr = Module.stringToNewUTF8('input.z.out');
for (let i = 0; i < 10; i++) {
Module._compressFile(inputStr, inputzStr);
Module._decompressFile(inputzStr, inputzoutStr);
if (Module.FS.stat('input').size !== Module.FS.stat('input.z.out').size) {
throw new Error("Length after decompression doesn't match");
}
}
Module._free(inputzoutStr);
Module._free(inputzStr);
Module._free(inputStr);
}
validate() {
const input = Module.FS.readFile('input');
const outputRoundtrip = Module.FS.readFile('input.z.out');
for (let i = 0; i < input.length; i++) {
if (input[i] !== outputRoundtrip[i]) {
throw new Error("Content after decompression doesn't match at offset " + i);
}
}
}
}

6
wasm/zlib/build.log Normal file
View file

@ -0,0 +1,6 @@
Built on 2025-02-12T12:38:44Z
Toolchain versions
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.73 (ac676d5e437525d15df5fd46bc2c208ec6d376a3)
Getting zpipe.c example source...
Building...
Building done

30
wasm/zlib/build.sh Executable file
View file

@ -0,0 +1,30 @@
#!/bin/bash
set -euo pipefail
rm -rf build/
rm -f src/zpipe.c
touch build.log
BUILD_LOG="$(realpath build.log)"
echo "Built on $(date -u '+%Y-%m-%dT%H:%M:%SZ')" | tee "$BUILD_LOG"
echo "Toolchain versions" | tee -a "$BUILD_LOG"
emcc --version | head -n1 | tee -a "$BUILD_LOG"
echo "Getting zpipe.c example source..." | tee -a "$BUILD_LOG"
curl -o "src/zpipe.c" https://www.zlib.net/zpipe.c
echo "Building..." | tee -a "$BUILD_LOG"
mkdir build/
emcc -o build/zlib.js \
-s WASM=1 -O2 \
-g1 --emit-symbol-map \
-s USE_ZLIB=1 -s FORCE_FILESYSTEM=1 \
-s MODULARIZE=1 -s EXPORT_NAME=setupModule -s EXPORTED_RUNTIME_METHODS=FS,stringToNewUTF8 -s EXPORTED_FUNCTIONS=_compressFile,_decompressFile,_free \
src/zpipe.c src/main.c | tee -a "$BUILD_LOG"
# If you want the native build for reference:
# clang -o build/zlib -O2 -lz src/zpipe.c src/main.c
echo "Building done" | tee -a "$BUILD_LOG"
ls -lth build/

3571
wasm/zlib/build/zlib.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,57 @@
0:__assert_fail
1:_emscripten_memcpy_js
2:__syscall_openat
3:__syscall_fcntl64
4:__syscall_ioctl
5:__wasi_fd_write
6:__wasi_fd_read
7:__wasi_fd_close
8:emscripten_resize_heap
9:legalimport$__wasi_fd_seek
10:__wasm_call_ctors
11:zerr
12:compressFile
13:decompressFile
14:adler32
15:crc32
16:deflateEnd
17:fill_window
18:flush_pending
19:deflate_stored
20:deflate_fast
21:longest_match
22:deflate_slow
23:inflate_table
24:init_block
25:_tr_stored_block
26:_tr_flush_bits
27:_tr_flush_block
28:build_tree
29:compress_block
30:send_tree
31:zcalloc
32:zcfree
33:__memcpy
34:__memset
35:fclose
36:ferror
37:fflush
38:__stdio_seek
39:__stdio_write
40:__stdio_read
41:__stdio_close
42:fopen
43:fread
44:__fwritex
45:fwrite
46:__emscripten_stdout_close
47:__emscripten_stdout_seek
48:strchr
49:__wasi_syscall_ret
50:emscripten_builtin_malloc
51:emscripten_builtin_free
52:sbrk
53:_emscripten_stack_restore
54:_emscripten_stack_alloc
55:emscripten_stack_get_current
56:legalstub$dynCall_jiji

BIN
wasm/zlib/build/zlib.wasm Executable file

Binary file not shown.

67
wasm/zlib/src/main.c Normal file
View file

@ -0,0 +1,67 @@
// Copyright 2025 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdio.h>
#include "zlib.h"
// To be compiled together with `zpipe.c` from zlib. Forward declarations:
int def(FILE *source, FILE *dest, int level);
int inf(FILE *source, FILE *dest);
void zerr(int ret);
// Exported to JavaScript:
int compressFile(const char* infilename, const char* outfilename)
{
FILE* infile = fopen(infilename, "rb");
if (infile == NULL) {
fputs("error opening input file", stderr);
return 1;
}
FILE* outfile = fopen(outfilename, "wb");
if (outfile == NULL) {
fputs("error opening output file", stderr);
return 1;
}
int ret = def(infile, outfile, Z_BEST_COMPRESSION);
if (ret != Z_OK)
zerr(ret);
if (fclose(infile)) {
fputs("error closing input file", stderr);
return 1;
}
if (fclose(outfile)) {
fputs("error closing output file", stderr);
return 1;
}
return ret;
}
int decompressFile(const char* infilename, const char* outfilename)
{
FILE* infile = fopen(infilename, "rb");
if (infile == NULL) {
fputs("error opening input file", stderr);
return 1;
}
FILE* outfile = fopen(outfilename, "wb");
if (outfile == NULL) {
fputs("error opening output file", stderr);
return 1;
}
int ret = inf(infile, outfile);
if (ret != Z_OK)
zerr(ret);
if (fclose(infile)) {
fputs("error closing input file", stderr);
return 1;
}
if (fclose(outfile)) {
fputs("error closing output file", stderr);
return 1;
}
return ret;
}

205
wasm/zlib/src/zpipe.c Normal file
View file

@ -0,0 +1,205 @@
/* zpipe.c: example of proper use of zlib's inflate() and deflate()
Not copyrighted -- provided to the public domain
Version 1.4 11 December 2005 Mark Adler */
/* Version history:
1.0 30 Oct 2004 First version
1.1 8 Nov 2004 Add void casting for unused return values
Use switch statement for inflate() return values
1.2 9 Nov 2004 Add assertions to document zlib guarantees
1.3 6 Apr 2005 Remove incorrect assertion in inf()
1.4 11 Dec 2005 Add hack to avoid MSDOS end-of-line conversions
Avoid some compiler warnings for input and output buffers
*/
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "zlib.h"
#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__)
# include <fcntl.h>
# include <io.h>
# define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY)
#else
# define SET_BINARY_MODE(file)
#endif
#define CHUNK 16384
/* Compress from file source to file dest until EOF on source.
def() returns Z_OK on success, Z_MEM_ERROR if memory could not be
allocated for processing, Z_STREAM_ERROR if an invalid compression
level is supplied, Z_VERSION_ERROR if the version of zlib.h and the
version of the library linked do not match, or Z_ERRNO if there is
an error reading or writing the files. */
int def(FILE *source, FILE *dest, int level)
{
int ret, flush;
unsigned have;
z_stream strm;
unsigned char in[CHUNK];
unsigned char out[CHUNK];
/* allocate deflate state */
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
ret = deflateInit(&strm, level);
if (ret != Z_OK)
return ret;
/* compress until end of file */
do {
strm.avail_in = fread(in, 1, CHUNK, source);
if (ferror(source)) {
(void)deflateEnd(&strm);
return Z_ERRNO;
}
flush = feof(source) ? Z_FINISH : Z_NO_FLUSH;
strm.next_in = in;
/* run deflate() on input until output buffer not full, finish
compression if all of source has been read in */
do {
strm.avail_out = CHUNK;
strm.next_out = out;
ret = deflate(&strm, flush); /* no bad return value */
assert(ret != Z_STREAM_ERROR); /* state not clobbered */
have = CHUNK - strm.avail_out;
if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
(void)deflateEnd(&strm);
return Z_ERRNO;
}
} while (strm.avail_out == 0);
assert(strm.avail_in == 0); /* all input will be used */
/* done when last data in file processed */
} while (flush != Z_FINISH);
assert(ret == Z_STREAM_END); /* stream will be complete */
/* clean up and return */
(void)deflateEnd(&strm);
return Z_OK;
}
/* Decompress from file source to file dest until stream ends or EOF.
inf() returns Z_OK on success, Z_MEM_ERROR if memory could not be
allocated for processing, Z_DATA_ERROR if the deflate data is
invalid or incomplete, Z_VERSION_ERROR if the version of zlib.h and
the version of the library linked do not match, or Z_ERRNO if there
is an error reading or writing the files. */
int inf(FILE *source, FILE *dest)
{
int ret;
unsigned have;
z_stream strm;
unsigned char in[CHUNK];
unsigned char out[CHUNK];
/* allocate inflate state */
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = 0;
strm.next_in = Z_NULL;
ret = inflateInit(&strm);
if (ret != Z_OK)
return ret;
/* decompress until deflate stream ends or end of file */
do {
strm.avail_in = fread(in, 1, CHUNK, source);
if (ferror(source)) {
(void)inflateEnd(&strm);
return Z_ERRNO;
}
if (strm.avail_in == 0)
break;
strm.next_in = in;
/* run inflate() on input until output buffer not full */
do {
strm.avail_out = CHUNK;
strm.next_out = out;
ret = inflate(&strm, Z_NO_FLUSH);
assert(ret != Z_STREAM_ERROR); /* state not clobbered */
switch (ret) {
case Z_NEED_DICT:
ret = Z_DATA_ERROR; /* and fall through */
case Z_DATA_ERROR:
case Z_MEM_ERROR:
(void)inflateEnd(&strm);
return ret;
}
have = CHUNK - strm.avail_out;
if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
(void)inflateEnd(&strm);
return Z_ERRNO;
}
} while (strm.avail_out == 0);
/* done when inflate() says it's done */
} while (ret != Z_STREAM_END);
/* clean up and return */
(void)inflateEnd(&strm);
return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
}
/* report a zlib or i/o error */
void zerr(int ret)
{
fputs("zpipe: ", stderr);
switch (ret) {
case Z_ERRNO:
if (ferror(stdin))
fputs("error reading stdin\n", stderr);
if (ferror(stdout))
fputs("error writing stdout\n", stderr);
break;
case Z_STREAM_ERROR:
fputs("invalid compression level\n", stderr);
break;
case Z_DATA_ERROR:
fputs("invalid or incomplete deflate data\n", stderr);
break;
case Z_MEM_ERROR:
fputs("out of memory\n", stderr);
break;
case Z_VERSION_ERROR:
fputs("zlib version mismatch!\n", stderr);
}
}
/* compress or decompress from stdin to stdout */
int main(int argc, char **argv)
{
int ret;
/* avoid end-of-line conversions */
SET_BINARY_MODE(stdin);
SET_BINARY_MODE(stdout);
/* do compression if no arguments */
if (argc == 1) {
ret = def(stdin, stdout, Z_DEFAULT_COMPRESSION);
if (ret != Z_OK)
zerr(ret);
return ret;
}
/* do decompression if -d specified */
else if (argc == 2 && strcmp(argv[1], "-d") == 0) {
ret = inf(stdin, stdout);
if (ret != Z_OK)
zerr(ret);
return ret;
}
/* otherwise, report usage */
else {
fputs("zpipe usage: zpipe [-d] < source > dest\n", stderr);
return 1;
}
}

View file

@ -69,7 +69,7 @@ Date.prototype.formatDate = function (input,time) {
function d() {
// Day of the month, 2 digits with leading zeros
return new String(self.getDate()).length == 1?
return String(self.getDate()).length == 1?
"0"+self.getDate() : self.getDate();
}
function D() {
@ -91,23 +91,23 @@ Date.prototype.formatDate = function (input,time) {
function h() {
// 12-hour format of an hour with leading zeros
if (self.getHours() > 12) {
var s = new String(self.getHours()-12);
var s = String(self.getHours()-12);
return s.length == 1?
"0"+ (self.getHours()-12) : self.getHours()-12;
} else {
var s = new String(self.getHours());
var s = String(self.getHours());
return s.length == 1?
"0"+self.getHours() : self.getHours();
}
}
function H() {
// 24-hour format of an hour with leading zeros
return new String(self.getHours()).length == 1?
return String(self.getHours()).length == 1?
"0"+self.getHours() : self.getHours();
}
function i() {
// Minutes with leading zeros
return new String(self.getMinutes()).length == 1?
return String(self.getMinutes()).length == 1?
"0"+self.getMinutes() : self.getMinutes();
}
function j() {
@ -169,7 +169,7 @@ Date.prototype.formatDate = function (input,time) {
}
function s() {
// Seconds, with leading zeros
return new String(self.getSeconds()).length == 1?
return String(self.getSeconds()).length == 1?
"0"+self.getSeconds() : self.getSeconds();
}
function t() {

View file

@ -349,7 +349,7 @@ String.escape = function(string) {
}
String.leftPad = function (val, size, ch) {
var result = new String(val);
var result = String(val);
if (ch == null) {
ch = " ";
}