emscripten-core / emscripten-core/emscripten
Code size regressions with WASI, environ, libc/filesystem
- Dominant language
- C++
- Stars
- 27.6k
- Forks
- 3.6k
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 105
Description
I've got a Flappy Bird game that I compiled last time in circa 2020, when I last optimized it for minimal code size.
It's been a while since I touched the codebase, though tonight I looked to update the code to take it through the latest Emscripten compiler version.
There are some code size creeps that have appeared in the build. Here are some examples:
```js
var noExitRuntime = Module["noExitRuntime"] || true;
var __abort_js = () => {
abort("")
};
var __emscripten_runtime_keepalive_clear = () => {
noExitRuntime = false
};
var timers = {};
var callUserCallback = func => func();
var __setitimer_js = (which, timeout_ms) => {
if (timers[which]) {
clearTimeout(timers[which].id);
delete timers[which]
}
if (!timeout_ms) return 0;
var id = setTimeout(() => {
delete timers[which];
callUserCallback(() => __emscripten_timeout(which, _emscripten_get_now()))
}, timeout_ms);
timers[which] = {
id: id,
timeout_ms: timeout_ms
};
return 0
};
var ENV = {};
var getExecutableName = () => "./this.program";
var getEnvStrings = () => {
if (!getEnvStrings.strings) {
var lang = (typeof navigator == "object" && navigator.languages && navigator.languages[0] || "C").replace("-", "_") + ".UTF-8";
var env = {
USER: "web_user",
LOGNAME: "web_user",
PATH: "/",
PWD: "/",
HOME: "/home/web_user",
LANG: lang,
_: getExecutableName()
};
for (var x in ENV) {
if (ENV[x] === undefined) delete env[x];
else env[x] = ENV[x]
}
var strings = [];
for (var x in env) {
strings.push(`${x}=${env[x]}`)
}
getEnvStrings.strings = strings
}
return getEnvStrings.strings
};
var stringToAscii = (str, buffer) => {
for (var i = 0; i < str.length; ++i) {
HEAP8[buffer++] = str.charCodeAt(i)
}
HEAP8[buffer] = 0
};
var _environ_get = (__environ, environ_buf) => {
var bufSize = 0;
getEnvStrings().forEach((string, i) => {
var ptr = environ_buf + bufSize;
HEAPU32[__environ + i * 4 >> 2] = ptr;
stringToAscii(string, ptr);
bufSize += string.length + 1
});
return 0
};
var _environ_sizes_get = (penviron_count, penviron_buf_size) => {
var strings = getEnvStrings();
HEAPU32[penviron_count >> 2] = strings.length;
var bufSize = 0;
strings.forEach(string => bufSize += string.length + 1);
HEAPU32[penviron_buf_size >> 2] = bufSize;
return 0
};
var _fd_close = fd => 52;
var convertI32PairToI53Checked = (lo, hi) => hi + 2097152 >>> 0 < 4194305 - !!lo ? (lo >>> 0) + hi * 4294967296 : NaN;
function _fd_seek(fd, offset_low, offset_high, whence, newOffset) {
var offset = convertI32PairToI53Checked(offset_low, offset_high);
return 70
}
var _fd_write = (fd, iov, iovcnt, pnum) => {
var num = 0;
for (var i = 0; i < iovcnt; i++) {
var ptr = HEAPU32[iov >> 2];
var len = HEAPU32[iov + 4 >> 2];
iov += 8;
for (var j = 0; j < len; j++) {
printChar(fd, HEAPU8[ptr + j])
}
num += len
}
HEAPU32[pnum >> 2] = num;
return 0
};
var _proc_exit = code => {
throw `exit(${code})`
};
var wasmImports = {
nb: __emscripten_runtime_keepalive_clear,
mb: __setitimer_js,
tb: _environ_get,
sb: _environ_sizes_get,
rb: _fd_close,
K: _fd_seek,
qb: _fd_write,
pb: _proc_exit
};
```
and in Wasm:
```
FlappyBird.wasm/function __wasi_syscall_ret: 23
FlappyBird.wasm/function action_terminate: 14
FlappyBird.wasm/function __stdio_close: 13
FlappyBird.wasm/function _emscripten_timeout: 108
FlappyBird.wasm/function action_abort: 7
```
The project itself does not use WASI, libc filesystem, or most other parts of libc much at all (e.g. no `printf()` and no `environ()` or related code), and it is compiled with `-sEXIT_RUNTIME=0` in MINIMAL_RUNTIME build mode.
When I look at why these code functions are present, I get odd circular trails:
- `__setitimer_js` from JS is imported to Wasm. In Wasm, it is called by `_emscripten_timeout`. `_emscripten_timeout` is exported out to JS. In JS, `_emscripten_timeout` is only used by `__setitimer_js`.
- `_environ_get` from JS is imported to Wasm. In Wasm, it is called by `__wasm_call_ctors`, and nothing else.
- `_proc_exit` from JS is imported to Wasm. In Wasm, it is called by `action_terminate`, which is not called directly, but exists only as part of `(table $0 37 37 funcref)`. Searching Emscripten codebase, I find `system\lib\libc\raise.c` references these via a signal table, but the codebase does not use `raise` or signaling.
It seems that the code size tests aren't catching real-world use case regressions as much as I would have hoped. I wonder if there are past changes in these areas that would come to mind?
Contributor guide
Assessment
This issue has not been assessed yet.