Skip to content

Commit 9e75439

Browse files
build wasm, add sample page (#7)
* add wasm * fix * fix, multi run * update * update html * fix host --------- Co-authored-by: tang zhixiong <zhixiong.tang@momenta.ai>
1 parent 5fb10fa commit 9e75439

6 files changed

Lines changed: 293 additions & 0 deletions

File tree

Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,11 @@ test_install:
2626

2727
test:
2828
build/pocketpy.exe tests/test_numpy.py
29+
30+
build_wasm:
31+
bash build_wasm.sh
32+
.PHONY: build_wasm
33+
34+
serve_wasm:
35+
cd web && python3 -m http.server 8080 --bind 0.0.0.0
36+
.PHONY: serve_wasm

build_wasm.sh

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
set -e
2+
3+
# Run prebuild if _generated.c doesn't exist
4+
if [ ! -f pocketpy/src/common/_generated.c ]; then
5+
python3 pocketpy/prebuild.py
6+
fi
7+
8+
# Create output directory
9+
rm -rf web/lib
10+
mkdir -p web/lib
11+
12+
# Generate test_numpy.js from test source
13+
python3 -c "
14+
import json
15+
with open('tests/test_numpy.py') as f:
16+
src = f.read()
17+
print('var TEST_SOURCE = ' + json.dumps(src) + ';')
18+
" > web/test_numpy.js
19+
20+
# Common flags
21+
DEFINES="-DPK_ENABLE_OS=0 -DPK_ENABLE_THREADS=0 -DPK_ENABLE_DETERMINISM=0 \
22+
-DPK_ENABLE_WATCHDOG=0 -DPK_ENABLE_CUSTOM_SNAME=0 -DPK_ENABLE_MIMALLOC=0 \
23+
-DPK_BUILD_MODULE_LZ4 -DPK_BUILD_MODULE_MSGPACK -DNDEBUG"
24+
INCLUDES="-Ipocketpy/include -Iinclude -Ipocketpy/3rd/lz4 -Ipocketpy/3rd/lz4/lz4/lib -Ipocketpy/3rd/msgpack/include"
25+
WARNINGS="-Wno-sign-compare -Wno-conversion -Wno-unused-variable -Wno-unused-parameter"
26+
27+
# Generate a unity C file that includes all pocketpy sources
28+
TMPDIR=$(mktemp -d)
29+
UNITY_C="$TMPDIR/unity_pocketpy.c"
30+
31+
for f in $(find pocketpy/src/ -name "*.c" | sort); do
32+
echo "#include \"$(pwd)/$f\"" >> "$UNITY_C"
33+
done
34+
echo "#include \"$(pwd)/pocketpy/3rd/lz4/lz4/lib/lz4.c\"" >> "$UNITY_C"
35+
echo "#include \"$(pwd)/pocketpy/3rd/msgpack/src/mpack.c\"" >> "$UNITY_C"
36+
echo "#include \"$(pwd)/pocketpy/3rd/msgpack/src/bindings.c\"" >> "$UNITY_C"
37+
38+
# Compile unity C source with emcc
39+
emcc "$UNITY_C" -c -Os $DEFINES $INCLUDES $WARNINGS -o "$TMPDIR/pocketpy.o"
40+
41+
# Compile C++ source and link with em++
42+
em++ "$TMPDIR/pocketpy.o" \
43+
src/numpy.cpp \
44+
$INCLUDES \
45+
-std=c++17 -Os \
46+
$DEFINES \
47+
-DSUPPRESS_XTENSOR_WARNINGS \
48+
-DPY_DYNAMIC_MODULE \
49+
-sEXPORTED_FUNCTIONS=_py_initialize,_py_exec,_py_finalize,_py_printexc,_py_clearexc,_py_module_initialize \
50+
-sEXPORTED_RUNTIME_METHODS=ccall \
51+
-sALLOW_MEMORY_GROWTH=1 \
52+
-sSTACK_SIZE=1048576 \
53+
$WARNINGS \
54+
-o web/lib/pocketpy.js
55+
56+
rm -rf "$TMPDIR"
57+
58+
echo "Build complete: web/lib/pocketpy.js, web/lib/pocketpy.wasm"

web/index.html

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>pocket-numpy</title>
7+
<style>
8+
body {
9+
font-family: monospace;
10+
background-color: #1e1e1e;
11+
color: #d4d4d4;
12+
margin: 20px;
13+
}
14+
h1 { color: #569cd6; margin-bottom: 12px; }
15+
#status { color: #dcdcaa; margin-bottom: 10px; padding-top: 30px; }
16+
.editor-container {
17+
display: flex;
18+
width: 100%;
19+
min-height: 400px;
20+
background-color: #282a36;
21+
border: 1px solid #444;
22+
border-radius: 6px;
23+
position: relative;
24+
}
25+
.line-numbers {
26+
width: 50px;
27+
padding: 10px 8px 10px 5px;
28+
background-color: #1e1e1e;
29+
color: #858585;
30+
text-align: right;
31+
font-family: monospace;
32+
font-size: 14px;
33+
line-height: 1.4;
34+
border-right: 1px solid #444;
35+
user-select: none;
36+
overflow-y: hidden;
37+
overflow-x: hidden;
38+
height: 100%;
39+
box-sizing: border-box;
40+
white-space: pre;
41+
}
42+
#editor {
43+
flex: 1;
44+
min-height: 400px;
45+
background-color: #282a36;
46+
color: #f8f8f2;
47+
border: none;
48+
padding: 10px;
49+
font-family: monospace;
50+
font-size: 14px;
51+
line-height: 1.4;
52+
tab-size: 4;
53+
resize: none;
54+
outline: none;
55+
}
56+
#run-btn {
57+
margin-top: 8px;
58+
padding: 6px 24px;
59+
font-family: monospace;
60+
font-size: 14px;
61+
font-weight: bold;
62+
cursor: pointer;
63+
background-color: #007bff;
64+
color: #fff;
65+
border: none;
66+
border-radius: 4px;
67+
}
68+
#run-btn:hover { background-color: #0056b3; }
69+
#run-btn:active { background-color: #004085; transform: translateY(1px); }
70+
#run-btn:disabled { background-color: #555; cursor: not-allowed; }
71+
#output {
72+
margin-top: 10px;
73+
background-color: #282a36;
74+
padding: 15px;
75+
border-radius: 6px;
76+
white-space: pre-wrap;
77+
min-height: 50px;
78+
line-height: 1.4;
79+
}
80+
.pass { color: #4ec9b0; font-weight: bold; }
81+
.fail { color: #f44747; font-weight: bold; }
82+
</style>
83+
</head>
84+
<body>
85+
<h1>pocket-numpy</h1>
86+
GitHub: <a href="https://github.com/cubao/xtensor-numpy" style="color: #ffff00; text-decoration: none;" target="_blank">cubao/xtensor-numpy</a>
87+
<div class="editor-container">
88+
<div class="line-numbers" id="line-numbers">1</div>
89+
<textarea id="editor" spellcheck="false"></textarea>
90+
</div>
91+
<br>
92+
<button id="run-btn" disabled>Run</button>
93+
<div id="status">Loading WASM module...</div>
94+
<pre id="output"></pre>
95+
96+
<script src="test_numpy.js"></script>
97+
<script>
98+
var editorEl = document.getElementById('editor');
99+
var outputEl = document.getElementById('output');
100+
var statusEl = document.getElementById('status');
101+
var runBtn = document.getElementById('run-btn');
102+
var ready = false;
103+
104+
// Fill editor with bundled test source
105+
editorEl.value = TEST_SOURCE;
106+
107+
// Tab key inserts a tab instead of moving focus
108+
editorEl.addEventListener('keydown', function (e) {
109+
if (e.key === 'Tab') {
110+
e.preventDefault();
111+
var s = this.selectionStart, end = this.selectionEnd;
112+
this.value = this.value.substring(0, s) + ' ' + this.value.substring(end);
113+
this.selectionStart = this.selectionEnd = s + 4;
114+
}
115+
});
116+
117+
// Update line numbers
118+
function updateLineNumbers() {
119+
var lines = editorEl.value.split('\n');
120+
var lineNumbers = '';
121+
for (var i = 1; i <= lines.length; i++) {
122+
lineNumbers += i + '\n';
123+
}
124+
// Remove trailing newline
125+
lineNumbers = lineNumbers.replace(/\n$/, '');
126+
document.getElementById('line-numbers').textContent = lineNumbers;
127+
}
128+
129+
// Update line numbers on input and scroll
130+
editorEl.addEventListener('input', function() {
131+
updateLineNumbers();
132+
syncLineNumbersHeight();
133+
});
134+
135+
editorEl.addEventListener('scroll', function() {
136+
document.getElementById('line-numbers').scrollTop = this.scrollTop;
137+
});
138+
139+
// Ensure line numbers height matches editor height
140+
function syncLineNumbersHeight() {
141+
var editorHeight = editorEl.scrollHeight;
142+
document.getElementById('line-numbers').style.height = editorHeight + 'px';
143+
}
144+
145+
// Initial line numbers and height sync
146+
updateLineNumbers();
147+
syncLineNumbersHeight();
148+
149+
// Sync height on window resize
150+
window.addEventListener('resize', function() {
151+
setTimeout(syncLineNumbersHeight, 100);
152+
});
153+
154+
// Monitor editor changes for better synchronization
155+
var observer = new MutationObserver(function() {
156+
setTimeout(function() {
157+
updateLineNumbers();
158+
syncLineNumbersHeight();
159+
}, 10);
160+
});
161+
162+
// Observe changes to editor
163+
if (window.MutationObserver) {
164+
observer.observe(editorEl, {
165+
attributes: true,
166+
childList: true,
167+
characterData: true,
168+
subtree: true
169+
});
170+
}
171+
172+
function runCode() {
173+
if (!ready) return;
174+
outputEl.textContent = '';
175+
var allOutput = '';
176+
Module._print_buf = function (text) {
177+
allOutput += text + '\n';
178+
outputEl.textContent += text + '\n';
179+
};
180+
181+
var t0 = performance.now();
182+
183+
var ok = Module.ccall(
184+
'py_exec', 'boolean', ['string', 'string', 'number', 'number'],
185+
[editorEl.value, 'main.py', 0, 0]
186+
);
187+
188+
var elapsed = (performance.now() - t0).toFixed(3);
189+
190+
if (!ok) {
191+
Module.ccall('py_printexc', null, [], []);
192+
Module.ccall('py_clearexc', null, ['number'], [0]);
193+
}
194+
195+
if (allOutput.indexOf('ALL TESTS PASSED') !== -1) {
196+
statusEl.innerHTML = '<span class="pass">ALL TESTS PASSED</span> (' + elapsed + ' ms)';
197+
} else if (!ok) {
198+
statusEl.innerHTML = '<span class="fail">ERROR</span> (' + elapsed + ' ms)';
199+
} else {
200+
statusEl.textContent = 'Done (' + elapsed + ' ms)';
201+
}
202+
}
203+
204+
runBtn.onclick = runCode;
205+
206+
var Module = {
207+
onRuntimeInitialized: function () {
208+
Module.ccall('py_initialize', null, [], []);
209+
Module.ccall('py_module_initialize', 'boolean', [], []);
210+
ready = true;
211+
runBtn.disabled = false;
212+
statusEl.textContent = 'Ready';
213+
runCode();
214+
},
215+
print: function (text) {
216+
if (Module._print_buf) Module._print_buf(text);
217+
},
218+
printErr: function (text) {
219+
if (Module._print_buf) Module._print_buf(text);
220+
}
221+
};
222+
</script>
223+
<script src="lib/pocketpy.js"></script>
224+
</body>
225+
</html>

web/lib/pocketpy.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/lib/pocketpy.wasm

2.42 MB
Binary file not shown.

web/test_numpy.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)