Skip to content

Commit fe8cdcb

Browse files
committed
Add basic box-host for web
1 parent e2612f5 commit fe8cdcb

File tree

5 files changed

+244
-1
lines changed

5 files changed

+244
-1
lines changed

box/src/host/server/box_host.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/*TODO - for WAMR*/
2+

box/src/host/server/box_host.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
//TODO - for wasmtime

box/src/host/web/box_host.html

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8"/>
5+
<title>Marcotte in a Worker with Blocking Syscalls</title>
6+
7+
<!-- Include xterm.js (CSS + JS) -->
8+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/xterm.css" />
9+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/xterm.js"></script>
10+
11+
<style>
12+
html, body {
13+
margin: 0;
14+
padding: 0;
15+
width: 100%;
16+
height: 100%;
17+
overflow: hidden; /* No scrollbars */
18+
}
19+
#terminal {
20+
height: 100%;
21+
width: 100%;
22+
}
23+
</style>
24+
</head>
25+
<body>
26+
<!--<h1 style="display:none;">Marcotte + Worker + Blocking WebSocket + xterm.js</h1>-->
27+
28+
<!-- The terminal goes full screen in #terminal -->
29+
<div id="terminal"></div>
30+
31+
<!-- Optional button to start the WASM program -->
32+
<!--<button id="startBtn" style="display:none;">Start Wasm Program</button>-->
33+
<!--<pre id="log" style="display:none;"></pre> [> optional logging <]-->
34+
35+
<script>
36+
// Set up xterm in the main thread
37+
const term = new Terminal({
38+
rows: 100,
39+
cols: 100, //
40+
convertEol: true,
41+
cursorBlink: true,
42+
});
43+
term.open(document.getElementById('terminal'));
44+
term.write("loading boxer env... \n");
45+
46+
// Create the Worker
47+
const worker = new Worker('marcotte_worker.js');
48+
49+
worker.onmessage = (evt) => {
50+
const { cmd, text, returnValue } = evt.data;
51+
52+
switch (cmd) {
53+
case 'wasmReady':
54+
log("Wasm is ready! Starting main() in 2 seconds...");
55+
// Optionally auto-start the program
56+
setTimeout(() => {
57+
worker.postMessage({ cmd: 'callMain' });
58+
}, 2000);
59+
break;
60+
61+
case 'callMainDone':
62+
log("Wasm program finished with returnValue=" + returnValue);
63+
break;
64+
65+
case 'hostWriteStdout':
66+
// A line (or partial line) from stdout
67+
term.write(text);
68+
break;
69+
70+
default:
71+
console.log("Worker message:", evt.data);
72+
break;
73+
}
74+
};
75+
76+
// If you want a button to manually start the program, uncomment:
77+
/*
78+
document.getElementById('startBtn').style.display = 'inline';
79+
document.getElementById('startBtn').addEventListener('click', () => {
80+
worker.postMessage({ cmd: 'initWasm', payload: { wasmUrl: 'client.wasm' } });
81+
setTimeout(() => worker.postMessage({ cmd: 'callMain' }), 2000);
82+
});
83+
*/
84+
85+
// Immediately request the worker to load the wasm
86+
worker.postMessage({ cmd: 'initWasm', payload: { wasmUrl: 'client.wasm' } });
87+
88+
function log(msg) {
89+
const logDiv = document.getElementById('log');
90+
logDiv.style.display = 'block';
91+
logDiv.textContent += msg + "\n";
92+
}
93+
</script>
94+
</body>
95+
</html>
96+

box/src/host/web/host_functions.js

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
let wasmInstance = null;
2+
let memoryBuffer = null;
3+
4+
let stdoutBuffer = "";
5+
6+
let websocket = null;
7+
let nextRequestId = 1;
8+
const pendingRequests = new Map();
9+
10+
// Listen for messages from main thread
11+
onmessage = async (evt) => {
12+
const { cmd, payload } = evt.data;
13+
14+
switch (cmd) {
15+
case 'initWasm':
16+
await initWasm(payload.wasmUrl);
17+
postMessage({ cmd: 'wasmReady' });
18+
break;
19+
20+
case 'callMain':
21+
if (!wasmInstance) return;
22+
const ret = wasmInstance.exports._start ? wasmInstance.exports._start() : 0;
23+
postMessage({ cmd: 'callMainDone', returnValue: ret });
24+
break;
25+
26+
default:
27+
console.log("[Worker] Unknown cmd:", cmd);
28+
break;
29+
}
30+
};
31+
32+
function initWebSocket() {
33+
return new Promise((resolve, reject) => {
34+
websocket = new WebSocket('ws://127.0.0.1:9000');
35+
websocket.binaryType = 'arraybuffer';
36+
37+
websocket.onopen = () => {
38+
console.log('[Worker] WebSocket connected');
39+
resolve();
40+
};
41+
42+
websocket.onerror = (err) => {
43+
console.error('[Worker] WebSocket error:', err);
44+
reject(new Error('WebSocket connection error'));
45+
};
46+
47+
websocket.onmessage = (evt) => {
48+
// We assume each message is: [4 bytes requestId][... arbitrary data ...]
49+
const fullData = new Uint8Array(evt.data);
50+
if (fullData.length < 4) {
51+
console.warn('Received message too short');
52+
return;
53+
}
54+
const view = new DataView(fullData.buffer);
55+
const reqId = view.getInt32(0, true); // little-endian
56+
const payload = fullData.subarray(4);
57+
58+
const reqInfo = pendingRequests.get(reqId);
59+
if (!reqInfo) {
60+
console.warn('Received response for unknown request id', reqId);
61+
return;
62+
}
63+
// Store the payload in reqInfo, then do an Atomics.notify
64+
reqInfo.responseData = payload;
65+
Atomics.store(reqInfo.sab, 0, 1);
66+
Atomics.notify(reqInfo.sab, 0, 1);
67+
};
68+
69+
websocket.onclose = () => {
70+
console.log('[Worker] WebSocket closed');
71+
};
72+
});
73+
}
74+
75+
async function initwasm(wasmurl) {
76+
// create websocket (only if /net is needed)
77+
await initwebsocket();
78+
79+
const response = await fetch(wasmurl);
80+
const bytes = await response.arraybuffer();
81+
82+
const memory = new webassembly.memory({ initial: 256, maximum: 256 });
83+
memorybuffer = memory.buffer;
84+
85+
function box_host_write_stdout_line(ptr, len) {
86+
const bytes = new Uint8Array(memoryBuffer, ptr, len);
87+
const line = new TextDecoder('utf-8').decode(bytes);
88+
postMessage({ cmd: 'hostWriteStdout', text: line });
89+
}
90+
91+
function blockyr(reqptr, reqlen, outptr) {
92+
const requ8 = new uint8array(memorybuffer, reqptr, reqlen);
93+
const reqid = nextrequestid++;
94+
95+
// build [reqid + request data]
96+
const header = new uint8array(4);
97+
new dataview(header.buffer).setint32(0, reqid, true);
98+
const tosend = new uint8array(4 + reqlen);
99+
tosend.set(header, 0);
100+
tosend.set(requ8, 4);
101+
102+
const sab = new int32array(new sharedarraybuffer(4));
103+
sab[0] = 0; // 0 => not ready
104+
pendingrequests.set(reqid, { sab, responsedata: null });
105+
106+
websocket.send(tosend);
107+
108+
// "block" via atomics until the server replies
109+
while (atomics.load(sab, 0) === 0) {
110+
atomics.wait(sab, 0, 0);
111+
}
112+
113+
const { responsedata } = pendingrequests.get(reqid);
114+
pendingrequests.delete(reqid);
115+
116+
if (!responsedata) {
117+
new dataview(memorybuffer).setint32(outptr, 0, true);
118+
return 0;
119+
}
120+
121+
// we have responsedata; allocate space in wasm memory
122+
const resplen = responsedata.length;
123+
const malloc = wasminstance.exports.malloc; // assumed exported by your minimal libc
124+
const respptr = malloc(resplen);
125+
126+
const respmem = new uint8array(memorybuffer, respptr, resplen);
127+
respmem.set(responsedata);
128+
129+
new dataview(memorybuffer).setint32(outptr, resplen, true);
130+
return respptr;
131+
}
132+
133+
const env = {
134+
memory,
135+
box_host_write_stdout_line,
136+
r: blockyr
137+
};
138+
139+
const { instance } = await webassembly.instantiate(bytes, { env });
140+
wasminstance = instance;
141+
142+
console.log('[worker] wasm loaded & instance created.');
143+
}
144+

examples/c/c.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ WORKDIR /app
2626
# Copy the source code
2727
COPY . .
2828
29-
# Build the application - **this builds the golang to WebAssemblly, with the proper interception of system code in for use in a Wasm Box**
29+
# Build the application - **this builds the program to WebAssemblly, with the proper interception of system code in for use in a Wasm Box**
3030
RUN gcc -o myapp main.c
3131
3232
# Stage 2: Create the final image

0 commit comments

Comments
 (0)