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:
parent
d4820bdbee
commit
de8d4c1101
90 changed files with 11487 additions and 4759 deletions
30
.github/workflows/test.yml
vendored
30
.github/workflows/test.yml
vendored
|
|
@ -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
1
8bitbench/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/8bitbench/
|
||||
70
8bitbench/benchmark.js
Normal file
70
8bitbench/benchmark.js
Normal 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
11
8bitbench/build.log
Normal 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
48
8bitbench/build.sh
Executable 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"
|
||||
|
|
@ -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);
|
||||
|
||||
})();
|
||||
BIN
8bitbench/build/rust/pkg/emu_bench_bg.wasm
Normal file
BIN
8bitbench/build/rust/pkg/emu_bench_bg.wasm
Normal file
Binary file not shown.
|
|
@ -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)
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
1
Dart/.gitignore
vendored
Normal file
1
Dart/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/wasm_gc_benchmarks/
|
||||
31
Dart/README.md
Normal file
31
Dart/README.md
Normal 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
311
Dart/benchmark.js
Normal 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
5
Dart/build.log
Normal 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
27
Dart/build.sh
Executable 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`
|
||||
3101
Dart/build/flute.dart2wasm.mjs
Normal file
3101
Dart/build/flute.dart2wasm.mjs
Normal file
File diff suppressed because one or more lines are too long
BIN
Dart/build/flute.dart2wasm.wasm
Normal file
BIN
Dart/build/flute.dart2wasm.wasm
Normal file
Binary file not shown.
424
Dart/build/run_wasm.js
Normal file
424
Dart/build/run_wasm.js
Normal 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, []);
|
||||
|
|
@ -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
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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}`)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
|
|
|
|||
|
|
@ -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}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
8
cli.js
|
|
@ -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
1597
package-lock.json
generated
File diff suppressed because it is too large
Load diff
13
package.json
13
package.json
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
188
tests/run-shell.mjs
Normal 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);
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -28,6 +28,6 @@ class Benchmark {
|
|||
if (!Module._runIteration)
|
||||
await setupModule(Module);
|
||||
|
||||
Module._runIteration(150);
|
||||
Module._runIteration(50);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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.
118
wasm/argon2.js
118
wasm/argon2.js
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
BIN
wasm/argon2.wasm
BIN
wasm/argon2.wasm
Binary file not shown.
|
|
@ -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})
|
||||
|
|
@ -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
116
wasm/argon2/benchmark.js
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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
5
wasm/argon2/build.log
Normal 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
|
||||
|
|
@ -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
768
wasm/argon2/build/argon2.js
Normal 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);
|
||||
40
wasm/argon2/build/argon2.js.symbols
Normal file
40
wasm/argon2/build/argon2.js.symbols
Normal 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
BIN
wasm/argon2/build/argon2.wasm
Executable file
Binary file not shown.
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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!");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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...
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
|
@ -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
38
wasm/zlib/benchmark.js
Normal 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
6
wasm/zlib/build.log
Normal 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
30
wasm/zlib/build.sh
Executable 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
3571
wasm/zlib/build/zlib.js
Normal file
File diff suppressed because it is too large
Load diff
57
wasm/zlib/build/zlib.js.symbols
Normal file
57
wasm/zlib/build/zlib.js.symbols
Normal 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
BIN
wasm/zlib/build/zlib.wasm
Executable file
Binary file not shown.
67
wasm/zlib/src/main.c
Normal file
67
wasm/zlib/src/main.c
Normal 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
205
wasm/zlib/src/zpipe.c
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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 = " ";
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue