Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,7 @@ elfconv-v*
!tests/browser/*.js
!tests/browser/*.html
!tests/browser/package.json
tests/browser/wasm-out/
tests/browser/wasm-out-bash/
tests/browser/wasm-out*/
tests/browser/test-results/

# AI
Expand Down
210 changes: 209 additions & 1 deletion browser/js-kernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -1793,6 +1793,195 @@ var Module = (() => {
processData(url)
}
};

// Expose helpers for loadPackage (preload mount support)
Module["addRunDependency"] = addRunDependency;
Module["removeRunDependency"] = removeRunDependency;
Module["FS_createPath"] = (parent, path, canRead, canWrite) => {
var fullPath = parent === "/" ? "/" + path : parent + "/" + path;
var parts = fullPath.split("/").filter(p => p);
var currentPath = "";
for (var i = 0; i < parts.length; i++) {
currentPath += "/" + parts[i];
try {
FS.mkdir(currentPath);
} catch (e) {
if (e.errno !== 20) throw e;
}
}
};
Module["FS_createDataFile"] = (parent, name, data, canRead, canWrite, canOwn) => {
var path = name;
if (parent) {
parent = typeof parent == "string" ? parent : FS.getPath(parent);
path = name ? PATH.join2(parent, name) : parent
}
var mode = FS_getMode(canRead, canWrite);
try {
var existing = FS.lookupPath(path);
if (existing && existing.node) {
FS.unlink(path);
}
} catch (e) { /* node doesn't exist, fine */ }
var node = FS.create(path, mode);
if (data) {
if (typeof data == "string") {
var arr = new Array(data.length);
for (var i = 0, len = data.length; i < len; ++i) arr[i] = data.charCodeAt(i);
data = arr
}
if (node.node_ops && node.node_ops.setattr) {
node.contents = new Uint8Array(data);
node.usedBytes = data.length;
node.timestamp = Date.now();
} else {
FS.chmod(node, mode | 146);
var stream = FS.open(node, 577);
FS.write(stream, data, 0, data.length, 0, canOwn);
FS.close(stream.fd);
FS.chmod(node, mode);
}
}
};

// loadPackage: fetch a .data file and extract preloaded files into MEMFS
Module["expectedDataFileDownloads"] ??= 0;
Module["loadPackage"] = function (metadata) {
var PACKAGE_NAME = metadata["packageDataName"];
var REMOTE_PACKAGE_BASE = PACKAGE_NAME;
var REMOTE_PACKAGE_NAME = Module["locateFile"] ? Module["locateFile"](REMOTE_PACKAGE_BASE, "") : REMOTE_PACKAGE_BASE;
var REMOTE_PACKAGE_SIZE = metadata["remote_package_size"];

function fetchRemotePackage(packageName, packageSize, callback, errback) {
Module["dataFileDownloads"] ??= {};
fetch(packageName).then(response => {
if (!response.ok) {
errback?.(new Error(`${response.status}: ${response.url}`));
return;
}
if (!response.body && response.arrayBuffer) {
return response.arrayBuffer().then(callback, errback)
}
const reader = response.body.getReader();
const chunks = [];
const headers = response.headers;
const total = Number(headers.get("Content-Length") ?? packageSize);
let loaded = 0;

const handleChunk = ({ done, value }) => {
if (!done) {
chunks.push(value);
loaded += value.length;
Module["dataFileDownloads"][packageName] = {
loaded,
total
};
let totalLoaded = 0;
let totalSize = 0;
for (const download of Object.values(Module["dataFileDownloads"])) {
totalLoaded += download.loaded;
totalSize += download.total
}
Module["setStatus"]?.(`Downloading data... (${totalLoaded}/${totalSize})`);
return reader.read().then(handleChunk, errback);
} else {
const packageData = new Uint8Array(chunks.map(c => c.length).reduce((a, b) => a + b, 0));
let offset = 0;
for (const chunk of chunks) {
packageData.set(chunk, offset);
offset += chunk.length
}
callback(packageData.buffer);
}
};

Module["setStatus"]?.("Downloading data...");
reader.read().then(handleChunk, errback);
}).catch(cause => {
errback?.(new Error(`Network Error: ${packageName}`, { cause }));
})
}

function handleError(error) {
console.error("package error:", error)
}
var fetchedCallback = null;
var fetched = Module["getPreloadedPackage"] ? Module["getPreloadedPackage"](REMOTE_PACKAGE_NAME, REMOTE_PACKAGE_SIZE) : null;
if (!fetched) fetchRemotePackage(REMOTE_PACKAGE_NAME, REMOTE_PACKAGE_SIZE, data => {
if (fetchedCallback) {
fetchedCallback(data);
fetchedCallback = null
} else {
fetched = data
}
}, handleError);

var runWithFSExecuted = false;
function runWithFS(Module) {
if (runWithFSExecuted) {
return;
}
runWithFSExecuted = true;

function assert(check, msg) {
if (!check) throw msg + (new Error).stack
}

function DataRequest(start, end, audio) {
this.start = start;
this.end = end;
this.audio = audio
}
DataRequest.prototype = {
requests: {},
open: function (mode, name) {
this.name = name;
this.requests[name] = this;
Module["addRunDependency"](`fp ${this.name}`)
},
send: function () { },
onload: function () {
var byteArray = this.byteArray.subarray(this.start, this.end);
this.finish(byteArray)
},
finish: function (byteArray) {
var that = this;
Module["FS_createDataFile"](this.name, null, byteArray, true, true, true);
Module["removeRunDependency"](`fp ${that.name}`);
this.requests[this.name] = null
}
};
var files = metadata["files"];
for (var i = 0; i < files.length; ++i) {
new DataRequest(files[i]["start"], files[i]["end"], files[i]["audio"] || 0).open("GET", files[i]["filename"])
}

function processPackageData(arrayBuffer) {
assert(arrayBuffer, "Loading data file failed.");
assert(arrayBuffer.constructor.name === ArrayBuffer.name, "bad input to processPackageData");
var byteArray = new Uint8Array(arrayBuffer);
DataRequest.prototype.byteArray = byteArray;
var files = metadata["files"];
for (var i = 0; i < files.length; ++i) {
DataRequest.prototype.requests[files[i].filename].onload()
}
Module["removeRunDependency"]("datafile_" + PACKAGE_NAME)
}
Module["addRunDependency"]("datafile_" + PACKAGE_NAME);
Module["preloadResults"] ??= {};
Module["preloadResults"][PACKAGE_NAME] = {
fromCache: false
};
if (fetched) {
processPackageData(fetched);
fetched = null
} else {
fetchedCallback = processPackageData
}
}
runWithFS(Module);
};

var FS_modeStringToFlags = str => {
var flagModes = {
r: 0,
Expand Down Expand Up @@ -5166,8 +5355,27 @@ var Module = (() => {
return
}

function doRun() {
async function doRun() {
initRuntime();
// Load preloaded files from manifest if present
try {
var manifestUrl = Module["preloadManifestUrl"] || "preload-manifest.json";
var resp = await fetch(manifestUrl);
if (resp.ok) {
var manifest = await resp.json();
for (var pkg of manifest) {
if (pkg.directories) {
for (var dir of pkg.directories) {
Module["FS_createPath"]("/", dir.substring(1), true, true);
}
}
Module["loadPackage"](pkg);
if (runDependencies > 0) {
await new Promise(resolve => { dependenciesFulfilled = resolve; });
}
}
}
} catch (e) { /* no manifest = no preload, normal */ }
preMain();
readyPromiseResolve(Module);
postRun()
Expand Down
2 changes: 0 additions & 2 deletions runtime/syscalls/SyscallBrowser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -753,8 +753,6 @@ EM_JS(uint32_t, ___syscall_sendfile,
- x86-64: syscall NR: rax, return: rax, args: rdi, rsi, rdx, r10, r8, r9
ref: https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/
*/
uint64_t CNT = 0;

void RuntimeManager::SVCBrowserCall(uint8_t *arena_ptr) {
errno = 0;
#if defined(ELFC_RUNTIME_SYSCALL_DEBUG)
Expand Down
25 changes: 13 additions & 12 deletions scripts/elfconv.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ setting() {
CLANGFLAGS="${OPTFLAGS} -std=c++20 -static -I${ROOT_DIR}/backend/remill/include -I${ROOT_DIR}"
# emscripten
EMCC=em++
EMCC_OPTION="-sASYNCIFY=0 -sINITIAL_MEMORY=536870912 -sSTACK_SIZE=16MB -sPTHREAD_POOL_SIZE=0 -pthread -sALLOW_MEMORY_GROWTH -sEXPORT_ES6 -sENVIRONMENT=web,worker $PRELOAD"
EMCC_OPTION="-sASYNCIFY=0 -sINITIAL_MEMORY=536870912 -sSTACK_SIZE=16MB -sPTHREAD_POOL_SIZE=0 -pthread -sALLOW_MEMORY_GROWTH -sEXPORT_ES6 -sENVIRONMENT=web,worker"
EMCCFLAGS="${OPTFLAGS} -I${ROOT_DIR}/backend/remill/include -I${ROOT_DIR}"
# wasi
WASISDKCC="${WASI_SDK_PATH}/bin/clang++"
Expand Down Expand Up @@ -154,9 +154,9 @@ prepare_js() {
exit 1
fi

# --preload-file generates the mapped data file `exe.data`.
if [[ -f "exe.data" ]]; then
cp -p exe.data ${BROWSER_DIR}
# copy preload manifest and data files if they exist
if [[ -f "${CUR_DIR}/preload-manifest.json" ]]; then
echo -e "[${GREEN}INFO${NC}] Preload manifest found, copying data files."
fi

rm "${CUR_DIR}/process.js"
Expand Down Expand Up @@ -233,22 +233,23 @@ main() {
*-wasm)
RUNTIME_MACRO="${RUNTIME_MACRO} -DTARGET_IS_BROWSER=1 -DELFNAME=\"${ELFNAME}\""
MAINOBJ="${CUR_DIR}/${ELFNAME}.wasm.o"
PRELOAD=
MAINGENJS="${CUR_DIR}/${ELFNAME}.generated.js"

if [[ -n "${MOUNT_SETTING}" ]]; then
PRELOAD="--preload-file ${MOUNT_SETTING}"
fi


if [[ -z "${NO_COMPILED}" ]]; then
${EMCC} ${EMCCFLAGS} ${RUNTIME_MACRO} -c ${MAINIR} -o ${MAINOBJ}
echo -e "[${GREEN}INFO${NC}] built ${MAINOBJ}"
else
echo -e "[${GREEN}INFO${NC}] NO_COPMILED is ON."
fi

# creates wasm
${EMCC} ${EMCCFLAGS} ${RUNTIME_MACRO} ${EMCC_OPTION} ${PRELOAD} -o ${MAINGENJS} ${MAINOBJ} ${ELFCONV_COMMON_RUNTIMES} ${RUNTIME_DIR}/syscalls/SyscallBrowser.cpp
# creates wasm (no --preload-file; preloading is handled by pack-preload.py + js-kernel.js)
${EMCC} ${EMCCFLAGS} ${RUNTIME_MACRO} ${EMCC_OPTION} -o ${MAINGENJS} ${MAINOBJ} ${ELFCONV_COMMON_RUNTIMES} ${RUNTIME_DIR}/syscalls/SyscallBrowser.cpp

# generate preload .data and manifest if MOUNT_SETTING is specified
if [[ -n "${MOUNT_SETTING}" ]]; then
echo -e "[${GREEN}INFO${NC}] Packing preload data for: ${MOUNT_SETTING}"
python3 "${ROOT_DIR}/scripts/pack-preload.py" ${MOUNT_SETTING} -o "${CUR_DIR}"
fi
echo -e "[${GREEN}INFO${NC}] built ${ELFNAME}.wasm and ${ELFNAME}.js and ${ELFNAME}.html."

# prepare Js and Wasm
Expand Down
Loading
Loading