From f9b340ab913b2ade8027d149c74182bb4ac2f638 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Thu, 2 Apr 2026 16:43:07 -0700 Subject: [PATCH 1/9] Add a test for libtty --- src/lib/libtty.js | 4 +- test/other/tty.c | 208 +++++++++++++++++++++++++++++++++++++++++++++ test/other/tty.out | 60 +++++++++++++ test/test_other.py | 3 + 4 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 test/other/tty.c create mode 100644 test/other/tty.out diff --git a/src/lib/libtty.js b/src/lib/libtty.js index ba392b12c32ff..69addab8fab80 100644 --- a/src/lib/libtty.js +++ b/src/lib/libtty.js @@ -54,10 +54,10 @@ addToLibrary({ }, close(stream) { // flush any pending line data - stream.tty.ops.fsync(stream.tty); + stream.tty.ops.fsync?.(stream.tty); }, fsync(stream) { - stream.tty.ops.fsync(stream.tty); + stream.tty.ops.fsync?.(stream.tty); }, read(stream, buffer, offset, length, pos /* ignored */) { if (!stream.tty || !stream.tty.ops.get_char) { diff --git a/test/other/tty.c b/test/other/tty.c new file mode 100644 index 0000000000000..85f84a972be14 --- /dev/null +++ b/test/other/tty.c @@ -0,0 +1,208 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +EM_JS_DEPS(main, "$TTY"); + +// clang-format off +EM_JS(void, init, (void), { + var major = 100; + var tty_ops = { + get_char: function (tty) { + if (tty.input.length > 0) { + return tty.input.shift(); + } + return undefined; + }, + put_char: function (tty, val) { + if (val !== 0 && val !== 10) { + tty.output.push(val); + } + }, + fsync: function (tty) { + console.log('fsync called'); + tty.output = []; + }, + ioctl_tcgets: function (tty) { + return { + c_iflag: 0, + c_oflag: 0, + c_cflag: 0, + c_lflag: 0, + c_cc: new Array(32).fill(0), + }; + }, + ioctl_tcsets: function (tty, optional_actions, data) { + return 0; + }, + ioctl_tiocgwinsz: function (tty) { + return [25, 80]; + }, + }; + var device = FS.makedev(major, 0); + TTY.register(device, tty_ops); + FS.mkdev('/custom_tty', device); + // Populate the TTY input buffer with test data "ABCD" + TTY.ttys[device].input = [65, 66, 67, 68]; + + // TTY without get_char - should cause ENXIO on read + var tty_no_getchar = {put_char: function (tty, val) {}}; + var device_no_getchar = FS.makedev(major + 1, 0); + TTY.register(device_no_getchar, tty_no_getchar); + FS.mkdev('/tty_no_getchar', device_no_getchar); + + // TTY without put_char - should cause ENXIO on write + var tty_no_putchar = { + get_char: function (tty) { + return 0; + }, + }; + var device_no_putchar = FS.makedev(major + 2, 0); + TTY.register(device_no_putchar, tty_no_putchar); + FS.mkdev('/tty_no_putchar', device_no_putchar); + + // TTY with throwing get_char - should cause EIO on read + var tty_throw_getchar = { + get_char: function (tty) { + throw new Error('get_char error'); + }, + put_char: function (tty, val) {}, + }; + var device_throw_getchar = FS.makedev(major + 3, 0); + TTY.register(device_throw_getchar, tty_throw_getchar); + FS.mkdev('/tty_throw_getchar', device_throw_getchar); + + // TTY with throwing put_char - should cause EIO on write + var tty_throw_putchar = { + get_char: function (tty) { + return 0; + }, + put_char: function (tty, val) { + throw new Error('put_char error'); + }, + }; + var device_throw_putchar = FS.makedev(major + 4, 0); + TTY.register(device_throw_putchar, tty_throw_putchar); + FS.mkdev('/tty_throw_putchar', device_throw_putchar); + + // TTY with empty input (returns undefined immediately) - should cause EAGAIN on + // read + var tty_empty = { + get_char: function (tty) { + return undefined; + }, + put_char: function (tty, val) {}, + }; + var device_empty = FS.makedev(major + 5, 0); + TTY.register(device_empty, tty_empty); + FS.mkdev('/tty_empty', device_empty); +}); +// clang-format on + +int main() { + init(); + char readBuffer[256] = {0}; + char writeBuffer[] = "Test"; + struct winsize ws; + struct termios term; + + printf("\nTest 1: open custom TTY device and check isatty\n"); + int fd = open("/custom_tty", O_RDWR); + printf("open custom_tty: %d\n", fd >= 0 ? 1 : 0); + printf("isatty: %d\n", isatty(fd)); + printf("errno after open: %s\n", strerror(errno)); + errno = 0; + + printf("\nTest 2: read from TTY with data\n"); + ssize_t bytesRead = read(fd, readBuffer, sizeof(readBuffer)); + printf("read bytes: %zd\n", bytesRead); + printf("read data: %s\n", bytesRead > 0 ? readBuffer : "(none)"); + printf("errno after read: %s\n", strerror(errno)); + errno = 0; + + printf("\nTest 3: write to TTY\n"); + ssize_t bytesWritten = write(fd, writeBuffer, strlen(writeBuffer)); + printf("write bytes: %zd\n", bytesWritten); + printf("errno after write: %s\n", strerror(errno)); + errno = 0; + + printf("\nTest 4: ioctl TIOCGWINSZ\n"); + int result = ioctl(fd, TIOCGWINSZ, &ws); + printf("ioctl TIOCGWINSZ: %d\n", result); + printf("ws_row: %d ws_col: %d\n", ws.ws_row, ws.ws_col); + printf("errno after ioctl: %s\n", strerror(errno)); + errno = 0; + + printf("\nTest 5: ioctl TCGETS\n"); + result = ioctl(fd, TCGETS, &term); + printf("ioctl TCGETS: %d\n", result); + printf("errno after TCGETS: %s\n", strerror(errno)); + errno = 0; + + printf("\nTest 6: ioctl TCSETS\n"); + result = ioctl(fd, TCSETS, &term); + printf("ioctl TCSETS: %d\n", result); + printf("errno after TCSETS: %s\n", strerror(errno)); + errno = 0; + + printf("\nTest 7: fsync\n"); + result = fsync(fd); + printf("fsync: %d\n", result); + printf("errno after fsync: %s\n", strerror(errno)); + errno = 0; + + close(fd); + + printf("\nTest 8: no put_char\n"); + fd = open("/tty_no_putchar", O_WRONLY); + printf("open: %d\n", fd >= 0 ? 1 : 0); + bytesWritten = write(fd, writeBuffer, strlen(writeBuffer)); + printf("write: %zd\n", bytesWritten); + printf("errno: %s\n", strerror(errno)); + errno = 0; + close(fd); + + printf("\nTest 9: no get_char\n"); + fd = open("/tty_no_getchar", O_RDONLY); + printf("open: %d\n", fd >= 0 ? 1 : 0); + bytesRead = read(fd, readBuffer, sizeof(readBuffer)); + printf("read: %zd\n", bytesRead); + printf("errno: %s\n", strerror(errno)); + errno = 0; + close(fd); + + printf("\nTest 10: put_char throws\n"); + fd = open("/tty_throw_putchar", O_WRONLY); + printf("open: %d\n", fd >= 0 ? 1 : 0); + bytesWritten = write(fd, writeBuffer, strlen(writeBuffer)); + printf("write: %zd\n", bytesWritten); + printf("errno: %s\n", strerror(errno)); + errno = 0; + close(fd); + + printf("\nTest 11: get_char throws\n"); + fd = open("/tty_throw_getchar", O_RDONLY); + printf("open: %d\n", fd >= 0 ? 1 : 0); + bytesRead = read(fd, readBuffer, sizeof(readBuffer)); + printf("read: %zd\n", bytesRead); + printf("errno: %s\n", strerror(errno)); + errno = 0; + close(fd); + + printf("\nTest 12: get_char returns undefined\n"); + fd = open("/tty_empty", O_RDONLY); + printf("open: %d\n", fd >= 0 ? 1 : 0); + bytesRead = read(fd, readBuffer, sizeof(readBuffer)); + printf("read: %zd\n", bytesRead); + printf("errno: %s\n", strerror(errno)); + errno = 0; + close(fd); + + printf("\ndone\n"); + return 0; +} diff --git a/test/other/tty.out b/test/other/tty.out new file mode 100644 index 0000000000000..63f5a05a4dcf8 --- /dev/null +++ b/test/other/tty.out @@ -0,0 +1,60 @@ + +Test 1: open custom TTY device and check isatty +open custom_tty: 1 +isatty: 1 +errno after open: Success + +Test 2: read from TTY with data +read bytes: 4 +read data: ABCD +errno after read: Success + +Test 3: write to TTY +write bytes: 4 +errno after write: Success + +Test 4: ioctl TIOCGWINSZ +ioctl TIOCGWINSZ: 0 +ws_row: 25 ws_col: 80 +errno after ioctl: Success + +Test 5: ioctl TCGETS +ioctl TCGETS: 0 +errno after TCGETS: Success + +Test 6: ioctl TCSETS +ioctl TCSETS: 0 +errno after TCSETS: Success + +Test 7: fsync +fsync called +fsync: 0 +errno after fsync: Success +fsync called + +Test 8: no put_char +open: 1 +write: -1 +errno: No such device or address + +Test 9: no get_char +open: 1 +read: -1 +errno: No such device or address + +Test 10: put_char throws +open: 1 +write: -1 +errno: I/O error + +Test 11: get_char throws +open: 1 +read: -1 +errno: I/O error + +Test 12: get_char returns undefined +open: 1 +read: -1 +errno: Resource temporarily unavailable + +done diff --git a/test/test_other.py b/test/test_other.py index f5fab92327817..05bbbeacaa17c 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -13175,6 +13175,9 @@ def test_unistd_isatty(self): self.skipTest('depends on /dev filesystem') self.do_runf('unistd/isatty.c', 'success') + def test_libtty(self): + self.do_other_test('tty.c') + def test_unistd_login(self): self.do_run_in_out_file_test('unistd/login.c') From e9a03c0f8d8502509d3f05dceb946d172db16521 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Fri, 3 Apr 2026 08:48:01 -0700 Subject: [PATCH 2/9] Address sbc100 review comments --- test/other/{tty.c => libtty.c} | 13 +++++++------ test/other/{tty.out => libtty.out} | 6 ------ test/test_other.py | 2 +- 3 files changed, 8 insertions(+), 13 deletions(-) rename test/other/{tty.c => libtty.c} (95%) rename test/other/{tty.out => libtty.out} (93%) diff --git a/test/other/tty.c b/test/other/libtty.c similarity index 95% rename from test/other/tty.c rename to test/other/libtty.c index 85f84a972be14..8146dbef65176 100644 --- a/test/other/tty.c +++ b/test/other/libtty.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -113,7 +114,7 @@ int main() { printf("\nTest 1: open custom TTY device and check isatty\n"); int fd = open("/custom_tty", O_RDWR); - printf("open custom_tty: %d\n", fd >= 0 ? 1 : 0); + assert(fd >= 0); printf("isatty: %d\n", isatty(fd)); printf("errno after open: %s\n", strerror(errno)); errno = 0; @@ -160,7 +161,7 @@ int main() { printf("\nTest 8: no put_char\n"); fd = open("/tty_no_putchar", O_WRONLY); - printf("open: %d\n", fd >= 0 ? 1 : 0); + assert(fd >= 0); bytesWritten = write(fd, writeBuffer, strlen(writeBuffer)); printf("write: %zd\n", bytesWritten); printf("errno: %s\n", strerror(errno)); @@ -169,7 +170,7 @@ int main() { printf("\nTest 9: no get_char\n"); fd = open("/tty_no_getchar", O_RDONLY); - printf("open: %d\n", fd >= 0 ? 1 : 0); + assert(fd >= 0); bytesRead = read(fd, readBuffer, sizeof(readBuffer)); printf("read: %zd\n", bytesRead); printf("errno: %s\n", strerror(errno)); @@ -178,7 +179,7 @@ int main() { printf("\nTest 10: put_char throws\n"); fd = open("/tty_throw_putchar", O_WRONLY); - printf("open: %d\n", fd >= 0 ? 1 : 0); + assert(fd >= 0); bytesWritten = write(fd, writeBuffer, strlen(writeBuffer)); printf("write: %zd\n", bytesWritten); printf("errno: %s\n", strerror(errno)); @@ -187,7 +188,7 @@ int main() { printf("\nTest 11: get_char throws\n"); fd = open("/tty_throw_getchar", O_RDONLY); - printf("open: %d\n", fd >= 0 ? 1 : 0); + assert(fd >= 0); bytesRead = read(fd, readBuffer, sizeof(readBuffer)); printf("read: %zd\n", bytesRead); printf("errno: %s\n", strerror(errno)); @@ -196,7 +197,7 @@ int main() { printf("\nTest 12: get_char returns undefined\n"); fd = open("/tty_empty", O_RDONLY); - printf("open: %d\n", fd >= 0 ? 1 : 0); + assert(fd >= 0); bytesRead = read(fd, readBuffer, sizeof(readBuffer)); printf("read: %zd\n", bytesRead); printf("errno: %s\n", strerror(errno)); diff --git a/test/other/tty.out b/test/other/libtty.out similarity index 93% rename from test/other/tty.out rename to test/other/libtty.out index 63f5a05a4dcf8..0cd5c04e16eb2 100644 --- a/test/other/tty.out +++ b/test/other/libtty.out @@ -1,6 +1,5 @@ Test 1: open custom TTY device and check isatty -open custom_tty: 1 isatty: 1 errno after open: Success @@ -33,27 +32,22 @@ errno after fsync: Success fsync called Test 8: no put_char -open: 1 write: -1 errno: No such device or address Test 9: no get_char -open: 1 read: -1 errno: No such device or address Test 10: put_char throws -open: 1 write: -1 errno: I/O error Test 11: get_char throws -open: 1 read: -1 errno: I/O error Test 12: get_char returns undefined -open: 1 read: -1 errno: Resource temporarily unavailable diff --git a/test/test_other.py b/test/test_other.py index 05bbbeacaa17c..cad477c74a019 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -13176,7 +13176,7 @@ def test_unistd_isatty(self): self.do_runf('unistd/isatty.c', 'success') def test_libtty(self): - self.do_other_test('tty.c') + self.do_other_test('libtty.c') def test_unistd_login(self): self.do_run_in_out_file_test('unistd/login.c') From d8831202b43c5a4b1375dde9a7687508149fa881 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Thu, 2 Apr 2026 23:03:42 +0200 Subject: [PATCH 3/9] Add libdev as a more powerful replacement for libtty Work in progress. --- src/lib/libdev.js | 144 ++++++++++++++++++++++++++ src/lib/libfs.js | 29 +++++- src/lib/libtty.js | 99 ++++-------------- src/modules.mjs | 1 + test/other/test_node_stdio_isatty.c | 17 +++ test/other/test_node_stdio_isatty.mjs | 30 ++++++ test/other/test_node_term_size.c | 23 ++++ test/other/test_node_term_size.mjs | 15 +++ test/test_other.py | 15 ++- 9 files changed, 293 insertions(+), 80 deletions(-) create mode 100644 src/lib/libdev.js create mode 100644 test/other/test_node_stdio_isatty.c create mode 100644 test/other/test_node_stdio_isatty.mjs create mode 100644 test/other/test_node_term_size.c create mode 100644 test/other/test_node_term_size.mjs diff --git a/src/lib/libdev.js b/src/lib/libdev.js new file mode 100644 index 0000000000000..da89cb8d1f034 --- /dev/null +++ b/src/lib/libdev.js @@ -0,0 +1,144 @@ +/** + * @license + * Copyright 2013 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +addToLibrary({ + $DEV__deps: [ + '$FS', + '$ERRNO_CODES', +#if ENVIRONMENT_MAY_BE_NODE + '$nodeTTY', + '$nodeFsync', +#endif + ], + $DEV: { + readWriteHelper: (stream, cb, method) => { + try { + var nbytes = cb(); + } catch (e) { + // Convert Node errors into ErrnoError + if (e && e.code && ERRNO_CODES[e.code]) { + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + if (e?.errno) { + // propagate errno + throw e; + } + // Other errors converted to EIO. +#if ASSERTIONS + console.error(`Error thrown in ${method}:`); + console.error(e); +#endif + throw new FS.ErrnoError({{{ cDefs.EIO }}}); + } + if (nbytes === undefined) { + // Prevent an infinite loop caused by incorrect code that doesn't return a + // value + // Maybe we should set nbytes = buffer.length here instead? +#if ASSERTIONS + console.warn( + `${method} returned undefined; a correct implementation must return a number`, + ); +#endif + throw new FS.ErrnoError({{{ cDefs.EIO }}}); + } + if (nbytes !== 0) { + stream.node.timestamp = Date.now(); + } + return nbytes; + }, + devs: [], + register(dev, ops) { + DEV.devs[dev] = ops; + FS.registerDevice(dev, DEV.stream_ops); + }, + TTY_OPS: { + ioctl_tiocgwinsz(tty) { + const { rows = 24, columns = 80 } = tty.devops.getTerminalSize?.() ?? {}; + return [rows, columns]; + }, + }, + stream_ops: { + open(stream) { + var devops = DEV.devs[stream.node.rdev]; + if (!devops) { + throw new FS.ErrnoError({{{ cDefs.ENODEV }}}); + } + stream.devops = devops; + stream.seekable = false; + stream.tty = + stream.devops.tty ?? + (stream.devops.isatty + ? { + ops: DEV.TTY_OPS, + devops, + } + : undefined); + devops.open?.(stream); + }, + close(stream) { + // flush any pending line data + stream.stream_ops.fsync(stream); + }, + fsync(stream) { + stream.devops.fsync?.(stream.devops); + }, + read: function (stream, buffer, offset, length, pos /* ignored */) { + buffer = buffer.subarray(offset, offset + length); + return DEV.readWriteHelper(stream, () => stream.devops.read(stream.devops, buffer), "read"); + }, + write: function (stream, buffer, offset, length, pos /* ignored */) { + buffer = buffer.subarray(offset, offset + length); + return DEV.readWriteHelper(stream, () => stream.devops.write(stream.devops, buffer), "write"); + }, + }, +#if ENVIRONMENT_MAY_BE_NODE + nodeInputDevice: (nodeStream) => ({ + isatty: nodeTTY.isatty(nodeStream.fd), + fsync() { + nodeFsync(nodeStream.fd); + }, + read(ops, buffer) { + return fs.readSync(nodeStream.fd, buffer) + }, + }), + nodeOutputDevice: (nodeStream) => ({ + isatty: nodeTTY.isatty(nodeStream.fd), + fsync() { + nodeFsync(nodeStream.fd); + }, + write(ops, buffer) { + return fs.writeSync(nodeStream.fd, buffer) + }, + getTerminalSize() { + return nodeStream; + } + }), +#endif + }, +#if ENVIRONMENT_MAY_BE_NODE + $nodeTTY: "require('node:tty');", + $nodeFsync: (fd) => { + try { + fs.fsyncSync(fd); + } catch (e) { + if (e?.code === "EINVAL") { + return; + } + // On Mac, calling fsync when not isatty returns ENOTSUP + // On Windows, stdin/stdout/stderr may be closed, returning EBADF or EPERM + const isStdStream = fd === 0 || fd === 1 || fd === 2; + if ( + isStdStream && + (e?.code === "ENOTSUP" || e?.code === "EBADF" || e?.code === "EPERM") + ) { + return; + } + + throw e; + } + } +#endif +}); diff --git a/src/lib/libfs.js b/src/lib/libfs.js index d148c84f5a2d2..59ad63306d11b 100644 --- a/src/lib/libfs.js +++ b/src/lib/libfs.js @@ -1465,17 +1465,40 @@ FS.staticInit();`; // them instead. if (input) { FS.createDevice('/dev', 'stdin', input); - } else { + } else +#if ENVIRONMENT_MAY_BE_NODE + if (ENVIRONMENT_IS_NODE) { + DEV.register(FS.makedev(7, 0), DEV.nodeInputDevice(process.stdin)); + FS.mkdev('/dev/stdin', FS.makedev(7, 0)); + } else +#endif + { FS.symlink('/dev/tty', '/dev/stdin'); } + if (output) { FS.createDevice('/dev', 'stdout', null, output); - } else { + } else +#if ENVIRONMENT_MAY_BE_NODE + if (ENVIRONMENT_IS_NODE) { + DEV.register(FS.makedev(7, 1), DEV.nodeOutputDevice(process.stdout)); + FS.mkdev('/dev/stdout', FS.makedev(7, 1)); + } else +#endif + { FS.symlink('/dev/tty', '/dev/stdout'); } + if (error) { FS.createDevice('/dev', 'stderr', null, error); - } else { + } else +#if ENVIRONMENT_MAY_BE_NODE + if (ENVIRONMENT_IS_NODE) { + DEV.register(FS.makedev(7, 2), DEV.nodeOutputDevice(process.stderr)); + FS.mkdev('/dev/stderr', FS.makedev(7, 2)); + } else +#endif + { FS.symlink('/dev/tty1', '/dev/stderr'); } diff --git a/src/lib/libtty.js b/src/lib/libtty.js index 69addab8fab80..d23634de1da2d 100644 --- a/src/lib/libtty.js +++ b/src/lib/libtty.js @@ -6,99 +6,46 @@ addToLibrary({ $TTY__deps: [ + '$DEV', '$FS', '$UTF8ArrayToString', - '$FS_stdin_getChar' + '$FS_stdin_getChar', ], -#if !MINIMAL_RUNTIME - $TTY__postset: () => { - addAtInit('TTY.init();'); - addAtExit('TTY.shutdown();'); - }, -#endif $TTY: { - ttys: [], - init() { - // https://github.com/emscripten-core/emscripten/pull/1555 - // if (ENVIRONMENT_IS_NODE) { - // // currently, FS.init does not distinguish if process.stdin is a file or TTY - // // device, it always assumes it's a TTY device. because of this, we're forcing - // // process.stdin to UTF8 encoding to at least make stdin reading compatible - // // with text files until FS.init can be refactored. - // process.stdin.setEncoding('utf8'); - // } - }, - shutdown() { - // https://github.com/emscripten-core/emscripten/pull/1555 - // if (ENVIRONMENT_IS_NODE) { - // // inolen: any idea as to why node -e 'process.stdin.read()' wouldn't exit immediately (with process.stdin being a tty)? - // // isaacs: because now it's reading from the stream, you've expressed interest in it, so that read() kicks off a _read() which creates a ReadReq operation - // // inolen: I thought read() in that case was a synchronous operation that just grabbed some amount of buffered data if it exists? - // // isaacs: it is. but it also triggers a _read() call, which calls readStart() on the handle - // // isaacs: do process.stdin.pause() and i'd think it'd probably close the pending call - // process.stdin.pause(); - // } - }, + ttys: {}, register(dev, ops) { - TTY.ttys[dev] = { input: [], output: [], ops: ops }; - FS.registerDevice(dev, TTY.stream_ops); - }, - stream_ops: { - open(stream) { - var tty = TTY.ttys[stream.node.rdev]; - if (!tty) { - throw new FS.ErrnoError({{{ cDefs.ENODEV }}}); + const tty = { input: [], output: [], ops }; + TTY.ttys[dev] = tty; + const devops = { tty }; + devops.write = (devops, buffer) => { + if (!ops.put_char) { + throw new FS.ErrnoError({{{ cDefs.ENXIO }}}); } - stream.tty = tty; - stream.seekable = false; - }, - close(stream) { - // flush any pending line data - stream.tty.ops.fsync?.(stream.tty); - }, - fsync(stream) { - stream.tty.ops.fsync?.(stream.tty); - }, - read(stream, buffer, offset, length, pos /* ignored */) { - if (!stream.tty || !stream.tty.ops.get_char) { + for (var i = 0; i < buffer.length; i++) { + ops.put_char(tty, buffer[i]); + } + return i; + }; + devops.read = (devops, buffer) => { + if (!ops.get_char) { throw new FS.ErrnoError({{{ cDefs.ENXIO }}}); } var bytesRead = 0; - for (var i = 0; i < length; i++) { - var result; - try { - result = stream.tty.ops.get_char(stream.tty); - } catch (e) { - throw new FS.ErrnoError({{{ cDefs.EIO }}}); - } + for (var i = 0; i < buffer.length; i++) { + var result = ops.get_char(tty); if (result === undefined && bytesRead === 0) { throw new FS.ErrnoError({{{ cDefs.EAGAIN }}}); } if (result === null || result === undefined) break; bytesRead++; - buffer[offset+i] = result; - } - if (bytesRead) { - stream.node.atime = Date.now(); + buffer[i] = result; } return bytesRead; - }, - write(stream, buffer, offset, length, pos) { - if (!stream.tty || !stream.tty.ops.put_char) { - throw new FS.ErrnoError({{{ cDefs.ENXIO }}}); - } - try { - for (var i = 0; i < length; i++) { - stream.tty.ops.put_char(stream.tty, buffer[offset+i]); - } - } catch (e) { - throw new FS.ErrnoError({{{ cDefs.EIO }}}); - } - if (length) { - stream.node.mtime = stream.node.ctime = Date.now(); - } - return i; + }; + if (ops.fsync) { + devops.fsync = (devops) => ops.fsync(tty) } + DEV.register(dev, devops); }, default_tty_ops: { get_char(tty) { diff --git a/src/modules.mjs b/src/modules.mjs index 83fcf1d86d8b3..8b261f0d68a64 100644 --- a/src/modules.mjs +++ b/src/modules.mjs @@ -111,6 +111,7 @@ function calculateLibraries() { 'libfs.js', 'libmemfs.js', 'libtty.js', + 'libdev.js', 'libpipefs.js', // ok to include it by default since it's only used if the syscall is used 'libsockfs.js', // ok to include it by default since it's only used if the syscall is used ); diff --git a/test/other/test_node_stdio_isatty.c b/test/other/test_node_stdio_isatty.c new file mode 100644 index 0000000000000..2f672fadc385a --- /dev/null +++ b/test/other/test_node_stdio_isatty.c @@ -0,0 +1,17 @@ +#include "emscripten.h" +#include "stdio.h" +#include "unistd.h" + +EM_JS(int, init, (void), { + var dev = FS.makedev(FS.createDevice.major++, 0); + DEV.register(dev, DEV.nodeOutputDevice({fd: Module.outFd})); + FS.mkdev('/dev/origout', dev); + return FS.open('/dev/origout', 1).fd; +}); + +int main(void) { + int outfd = init(); + FILE* outfile = fdopen(outfd, "w"); + + fprintf(outfile, "%d%d%d\n", isatty(0), isatty(1), isatty(2)); +} diff --git a/test/other/test_node_stdio_isatty.mjs b/test/other/test_node_stdio_isatty.mjs new file mode 100644 index 0000000000000..307eaa9857f4f --- /dev/null +++ b/test/other/test_node_stdio_isatty.mjs @@ -0,0 +1,30 @@ +import createModule from '../../out/test/out.mjs'; +import * as fs from 'node:fs'; +const outFd = fs.openSync('/proc/self/fd/1', 'w'); + +const arg = process.argv.at(-1); +if (arg[0] === "1") { + fs.closeSync(0); + fs.openSync("/dev/tty", 'r'); +} else { + fs.closeSync(0); + fs.openSync("/dev/null", 'r'); +} + +if (arg[1] === "1") { + fs.closeSync(1); + fs.openSync("/dev/tty", 'w'); +} else { + fs.closeSync(1); + fs.openSync("/dev/null", 'w'); +} + +if (arg[2] === "1") { + fs.closeSync(2); + fs.openSync("/dev/tty", 'w'); +} else { + fs.closeSync(2); + fs.openSync("/dev/null", 'w'); +} + +await createModule({ outFd }); diff --git a/test/other/test_node_term_size.c b/test/other/test_node_term_size.c new file mode 100644 index 0000000000000..a8f4d63bcf2a4 --- /dev/null +++ b/test/other/test_node_term_size.c @@ -0,0 +1,23 @@ +#include "emscripten.h" +#include "stdio.h" +#include "sys/ioctl.h" +#include "unistd.h" + +EM_JS(int, init, (void), { + var dev = FS.makedev(FS.createDevice.major++, 0); + DEV.register(dev, DEV.nodeOutputDevice({fd: Module.outFd})); + FS.mkdev('/dev/origout', dev); + return FS.open('/dev/origout', 1).fd; +}); + +int main(void) { + int outfd = init(); + FILE* outfile = fdopen(outfd, "w"); + + struct winsize w; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + + fprintf (outfile, "rows %d\n", w.ws_row); + fprintf (outfile, "columns %d\n", w.ws_col); + return 0; +} diff --git a/test/other/test_node_term_size.mjs b/test/other/test_node_term_size.mjs new file mode 100644 index 0000000000000..d53514f766f29 --- /dev/null +++ b/test/other/test_node_term_size.mjs @@ -0,0 +1,15 @@ +import createModule from '../../out/test/out.mjs'; +import * as fs from 'node:fs'; +import * as tty from 'node:tty'; + +const outFd = fs.openSync('/proc/self/fd/1', 'w'); + +fs.closeSync(1); +fs.openSync("/dev/tty", 1); + +globalThis.process = Object.assign({}, process, {stdout: new tty.WriteStream(1)}); +process.stdout.fd = 1; +process.stdout.columns = 180; +process.stdout.rows = 50; + +await createModule({ outFd }); diff --git a/test/test_other.py b/test/test_other.py index 0afc15ac07f78..33e27562a1609 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -13177,7 +13177,7 @@ def test_unistd_isatty(self): self.do_runf('unistd/isatty.c', 'success') def test_libtty(self): - self.do_other_test('libtty.c') + self.do_other_test('libtty.c', cflags=["-O2"]) def test_unistd_login(self): self.do_run_in_out_file_test('unistd/login.c') @@ -15446,3 +15446,16 @@ def test_logReadFiles(self): create_file('pre.js', 'Module.logReadFiles = 1;') output = self.do_runf('checksummer.c', args=['test.txt'], cflags=['--pre-js=pre.js']) self.assertContained('read file: /test.txt', output) + + @crossplatform + @no_windows('opens /proc/self/fd/1') + def test_node_stdio_isatty(self): + self.run_process([EMCC, test_file('other/test_node_stdio_isatty.c'), '-o', 'out.mjs']) + for arg in ['111', '011', '101', '110', '100', '010', '001', '000']: + self.assertEqual(self.run_js(test_file('other/test_node_stdio_isatty.mjs'), args = [arg]), arg + '\n') + + @crossplatform + @no_windows('opens /proc/self/fd/1') + def test_node_term_size(self): + self.run_process([EMCC, test_file('other/test_node_term_size.c'), '-o', 'out.mjs']) + self.assertEqual(self.run_js(test_file('other/test_node_term_size.mjs')), 'rows 50\ncolumns 180\n') From 96d740bc5c1aa4b3067da9a1d298f6d63718ce6c Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Fri, 3 Apr 2026 09:06:50 -0700 Subject: [PATCH 4/9] Tidy --- src/lib/libtty.js | 50 ++++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/lib/libtty.js b/src/lib/libtty.js index d23634de1da2d..0568c5084cad0 100644 --- a/src/lib/libtty.js +++ b/src/lib/libtty.js @@ -16,31 +16,33 @@ addToLibrary({ register(dev, ops) { const tty = { input: [], output: [], ops }; TTY.ttys[dev] = tty; - const devops = { tty }; - devops.write = (devops, buffer) => { - if (!ops.put_char) { - throw new FS.ErrnoError({{{ cDefs.ENXIO }}}); - } - for (var i = 0; i < buffer.length; i++) { - ops.put_char(tty, buffer[i]); - } - return i; - }; - devops.read = (devops, buffer) => { - if (!ops.get_char) { - throw new FS.ErrnoError({{{ cDefs.ENXIO }}}); - } - var bytesRead = 0; - for (var i = 0; i < buffer.length; i++) { - var result = ops.get_char(tty); - if (result === undefined && bytesRead === 0) { - throw new FS.ErrnoError({{{ cDefs.EAGAIN }}}); + const devops = { + tty, + write(devops, buffer) { + if (!ops.put_char) { + throw new FS.ErrnoError({{{ cDefs.ENXIO }}}); } - if (result === null || result === undefined) break; - bytesRead++; - buffer[i] = result; - } - return bytesRead; + for (var i = 0; i < buffer.length; i++) { + ops.put_char(tty, buffer[i]); + } + return i; + }, + read(devops, buffer) { + if (!ops.get_char) { + throw new FS.ErrnoError({{{ cDefs.ENXIO }}}); + } + var bytesRead = 0; + for (var i = 0; i < buffer.length; i++) { + var result = ops.get_char(tty); + if (result === undefined && bytesRead === 0) { + throw new FS.ErrnoError({{{ cDefs.EAGAIN }}}); + } + if (result === null || result === undefined) break; + bytesRead++; + buffer[i] = result; + } + return bytesRead; + }, }; if (ops.fsync) { devops.fsync = (devops) => ops.fsync(tty) From 5881c3622eb9388549b6c7cebddf4aed2a069d7f Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Fri, 3 Apr 2026 09:37:04 -0700 Subject: [PATCH 5/9] Fix format --- test/test_other.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_other.py b/test/test_other.py index 33e27562a1609..b2862361c0915 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -15452,7 +15452,7 @@ def test_logReadFiles(self): def test_node_stdio_isatty(self): self.run_process([EMCC, test_file('other/test_node_stdio_isatty.c'), '-o', 'out.mjs']) for arg in ['111', '011', '101', '110', '100', '010', '001', '000']: - self.assertEqual(self.run_js(test_file('other/test_node_stdio_isatty.mjs'), args = [arg]), arg + '\n') + self.assertEqual(self.run_js(test_file('other/test_node_stdio_isatty.mjs'), args=[arg]), arg + '\n') @crossplatform @no_windows('opens /proc/self/fd/1') From a6d0dedfbfa352fdf4dd7e6e3577fa6cddb4a067 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Fri, 3 Apr 2026 11:41:49 -0700 Subject: [PATCH 6/9] Rebaseline --- .../test_codesize_file_preload.expected.js | 331 +++++++++++++++--- test/codesize/test_codesize_hello_O0.json | 8 +- .../test_codesize_minimal_O0.expected.js | 3 + test/codesize/test_codesize_minimal_O0.json | 8 +- test/codesize/test_unoptimized_code_size.json | 12 +- 5 files changed, 290 insertions(+), 72 deletions(-) diff --git a/test/codesize/test_codesize_file_preload.expected.js b/test/codesize/test_codesize_file_preload.expected.js index 785751e68113b..5f483b81190c1 100644 --- a/test/codesize/test_codesize_file_preload.expected.js +++ b/test/codesize/test_codesize_file_preload.expected.js @@ -350,7 +350,6 @@ function initRuntime() { runtimeInitialized = true; // Begin ATINITS hooks if (!Module["noFSInit"] && !FS.initialized) FS.init(); - TTY.init(); // End ATINITS hooks wasmExports["c"](); // Begin ATPOSTCTORS hooks @@ -686,6 +685,240 @@ var PATH_FS = { } }; +var ERRNO_CODES = { + "EPERM": 63, + "ENOENT": 44, + "ESRCH": 71, + "EINTR": 27, + "EIO": 29, + "ENXIO": 60, + "E2BIG": 1, + "ENOEXEC": 45, + "EBADF": 8, + "ECHILD": 12, + "EAGAIN": 6, + "EWOULDBLOCK": 6, + "ENOMEM": 48, + "EACCES": 2, + "EFAULT": 21, + "ENOTBLK": 105, + "EBUSY": 10, + "EEXIST": 20, + "EXDEV": 75, + "ENODEV": 43, + "ENOTDIR": 54, + "EISDIR": 31, + "EINVAL": 28, + "ENFILE": 41, + "EMFILE": 33, + "ENOTTY": 59, + "ETXTBSY": 74, + "EFBIG": 22, + "ENOSPC": 51, + "ESPIPE": 70, + "EROFS": 69, + "EMLINK": 34, + "EPIPE": 64, + "EDOM": 18, + "ERANGE": 68, + "ENOMSG": 49, + "EIDRM": 24, + "ECHRNG": 106, + "EL2NSYNC": 156, + "EL3HLT": 107, + "EL3RST": 108, + "ELNRNG": 109, + "EUNATCH": 110, + "ENOCSI": 111, + "EL2HLT": 112, + "EDEADLK": 16, + "ENOLCK": 46, + "EBADE": 113, + "EBADR": 114, + "EXFULL": 115, + "ENOANO": 104, + "EBADRQC": 103, + "EBADSLT": 102, + "EDEADLOCK": 16, + "EBFONT": 101, + "ENOSTR": 100, + "ENODATA": 116, + "ETIME": 117, + "ENOSR": 118, + "ENONET": 119, + "ENOPKG": 120, + "EREMOTE": 121, + "ENOLINK": 47, + "EADV": 122, + "ESRMNT": 123, + "ECOMM": 124, + "EPROTO": 65, + "EMULTIHOP": 36, + "EDOTDOT": 125, + "EBADMSG": 9, + "ENOTUNIQ": 126, + "EBADFD": 127, + "EREMCHG": 128, + "ELIBACC": 129, + "ELIBBAD": 130, + "ELIBSCN": 131, + "ELIBMAX": 132, + "ELIBEXEC": 133, + "ENOSYS": 52, + "ENOTEMPTY": 55, + "ENAMETOOLONG": 37, + "ELOOP": 32, + "EOPNOTSUPP": 138, + "EPFNOSUPPORT": 139, + "ECONNRESET": 15, + "ENOBUFS": 42, + "EAFNOSUPPORT": 5, + "EPROTOTYPE": 67, + "ENOTSOCK": 57, + "ENOPROTOOPT": 50, + "ESHUTDOWN": 140, + "ECONNREFUSED": 14, + "EADDRINUSE": 3, + "ECONNABORTED": 13, + "ENETUNREACH": 40, + "ENETDOWN": 38, + "ETIMEDOUT": 73, + "EHOSTDOWN": 142, + "EHOSTUNREACH": 23, + "EINPROGRESS": 26, + "EALREADY": 7, + "EDESTADDRREQ": 17, + "EMSGSIZE": 35, + "EPROTONOSUPPORT": 66, + "ESOCKTNOSUPPORT": 137, + "EADDRNOTAVAIL": 4, + "ENETRESET": 39, + "EISCONN": 30, + "ENOTCONN": 53, + "ETOOMANYREFS": 141, + "EUSERS": 136, + "EDQUOT": 19, + "ESTALE": 72, + "ENOTSUP": 138, + "ENOMEDIUM": 148, + "EILSEQ": 25, + "EOVERFLOW": 61, + "ECANCELED": 11, + "ENOTRECOVERABLE": 56, + "EOWNERDEAD": 62, + "ESTRPIPE": 135 +}; + +var nodeTTY = require("node:tty"); + +var nodeFsync = fd => { + try { + fs.fsyncSync(fd); + } catch (e) { + if (e?.code === "EINVAL") { + return; + } + // On Mac, calling fsync when not isatty returns ENOTSUP + // On Windows, stdin/stdout/stderr may be closed, returning EBADF or EPERM + const isStdStream = fd === 0 || fd === 1 || fd === 2; + if (isStdStream && (e?.code === "ENOTSUP" || e?.code === "EBADF" || e?.code === "EPERM")) { + return; + } + throw e; + } +}; + +var DEV = { + readWriteHelper: (stream, cb, method) => { + try { + var nbytes = cb(); + } catch (e) { + // Convert Node errors into ErrnoError + if (e && e.code && ERRNO_CODES[e.code]) { + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + if (e?.errno) { + // propagate errno + throw e; + } + // Other errors converted to EIO. + throw new FS.ErrnoError(29); + } + if (nbytes === undefined) { + // Prevent an infinite loop caused by incorrect code that doesn't return a + // value + // Maybe we should set nbytes = buffer.length here instead? + throw new FS.ErrnoError(29); + } + if (nbytes !== 0) { + stream.node.timestamp = Date.now(); + } + return nbytes; + }, + devs: [], + register(dev, ops) { + DEV.devs[dev] = ops; + FS.registerDevice(dev, DEV.stream_ops); + }, + TTY_OPS: { + ioctl_tiocgwinsz(tty) { + const {rows = 24, columns = 80} = tty.devops.getTerminalSize?.() ?? {}; + return [ rows, columns ]; + } + }, + stream_ops: { + open(stream) { + var devops = DEV.devs[stream.node.rdev]; + if (!devops) { + throw new FS.ErrnoError(43); + } + stream.devops = devops; + stream.seekable = false; + stream.tty = stream.devops.tty ?? (stream.devops.isatty ? { + ops: DEV.TTY_OPS, + devops + } : undefined); + devops.open?.(stream); + }, + close(stream) { + // flush any pending line data + stream.stream_ops.fsync(stream); + }, + fsync(stream) { + stream.devops.fsync?.(stream.devops); + }, + read: function(stream, buffer, offset, length, pos) { + buffer = buffer.subarray(offset, offset + length); + return DEV.readWriteHelper(stream, () => stream.devops.read(stream.devops, buffer), "read"); + }, + write: function(stream, buffer, offset, length, pos) { + buffer = buffer.subarray(offset, offset + length); + return DEV.readWriteHelper(stream, () => stream.devops.write(stream.devops, buffer), "write"); + } + }, + nodeInputDevice: nodeStream => ({ + isatty: nodeTTY.isatty(nodeStream.fd), + fsync() { + nodeFsync(nodeStream.fd); + }, + read(ops, buffer) { + return fs.readSync(nodeStream.fd, buffer); + } + }), + nodeOutputDevice: nodeStream => ({ + isatty: nodeTTY.isatty(nodeStream.fd), + fsync() { + nodeFsync(nodeStream.fd); + }, + write(ops, buffer) { + return fs.writeSync(nodeStream.fd, buffer); + }, + getTerminalSize() { + return nodeStream; + } + }) +}; + var UTF8Decoder = globalThis.TextDecoder && new TextDecoder; var findStringEnd = (heapOrArray, idx, maxBytesToRead, ignoreNul) => { @@ -862,73 +1095,46 @@ var FS_stdin_getChar = () => { }; var TTY = { - ttys: [], - init() {}, - shutdown() {}, + ttys: {}, register(dev, ops) { - TTY.ttys[dev] = { + const tty = { input: [], output: [], ops }; - FS.registerDevice(dev, TTY.stream_ops); - }, - stream_ops: { - open(stream) { - var tty = TTY.ttys[stream.node.rdev]; - if (!tty) { - throw new FS.ErrnoError(43); - } - stream.tty = tty; - stream.seekable = false; - }, - close(stream) { - // flush any pending line data - stream.tty.ops.fsync(stream.tty); - }, - fsync(stream) { - stream.tty.ops.fsync(stream.tty); - }, - read(stream, buffer, offset, length, pos) { - if (!stream.tty || !stream.tty.ops.get_char) { - throw new FS.ErrnoError(60); - } - var bytesRead = 0; - for (var i = 0; i < length; i++) { - var result; - try { - result = stream.tty.ops.get_char(stream.tty); - } catch (e) { - throw new FS.ErrnoError(29); + TTY.ttys[dev] = tty; + const devops = { + tty, + write(devops, buffer) { + if (!ops.put_char) { + throw new FS.ErrnoError(60); } - if (result === undefined && bytesRead === 0) { - throw new FS.ErrnoError(6); + for (var i = 0; i < buffer.length; i++) { + ops.put_char(tty, buffer[i]); } - if (result === null || result === undefined) break; - bytesRead++; - buffer[offset + i] = result; - } - if (bytesRead) { - stream.node.atime = Date.now(); - } - return bytesRead; - }, - write(stream, buffer, offset, length, pos) { - if (!stream.tty || !stream.tty.ops.put_char) { - throw new FS.ErrnoError(60); - } - try { - for (var i = 0; i < length; i++) { - stream.tty.ops.put_char(stream.tty, buffer[offset + i]); + return i; + }, + read(devops, buffer) { + if (!ops.get_char) { + throw new FS.ErrnoError(60); } - } catch (e) { - throw new FS.ErrnoError(29); - } - if (length) { - stream.node.mtime = stream.node.ctime = Date.now(); + var bytesRead = 0; + for (var i = 0; i < buffer.length; i++) { + var result = ops.get_char(tty); + if (result === undefined && bytesRead === 0) { + throw new FS.ErrnoError(6); + } + if (result === null || result === undefined) break; + bytesRead++; + buffer[i] = result; + } + return bytesRead; } - return i; + }; + if (ops.fsync) { + devops.fsync = devops => ops.fsync(tty); } + DEV.register(dev, devops); }, default_tty_ops: { get_char(tty) { @@ -2612,16 +2818,25 @@ var FS = { // them instead. if (input) { FS.createDevice("/dev", "stdin", input); + } else if (ENVIRONMENT_IS_NODE) { + DEV.register(FS.makedev(7, 0), DEV.nodeInputDevice(process.stdin)); + FS.mkdev("/dev/stdin", FS.makedev(7, 0)); } else { FS.symlink("/dev/tty", "/dev/stdin"); } if (output) { FS.createDevice("/dev", "stdout", null, output); + } else if (ENVIRONMENT_IS_NODE) { + DEV.register(FS.makedev(7, 1), DEV.nodeOutputDevice(process.stdout)); + FS.mkdev("/dev/stdout", FS.makedev(7, 1)); } else { FS.symlink("/dev/tty", "/dev/stdout"); } if (error) { FS.createDevice("/dev", "stderr", null, error); + } else if (ENVIRONMENT_IS_NODE) { + DEV.register(FS.makedev(7, 2), DEV.nodeOutputDevice(process.stderr)); + FS.mkdev("/dev/stderr", FS.makedev(7, 2)); } else { FS.symlink("/dev/tty1", "/dev/stderr"); } diff --git a/test/codesize/test_codesize_hello_O0.json b/test/codesize/test_codesize_hello_O0.json index ed7a13ce715c6..77120e9d757c3 100644 --- a/test/codesize/test_codesize_hello_O0.json +++ b/test/codesize/test_codesize_hello_O0.json @@ -1,10 +1,10 @@ { - "a.out.js": 24261, - "a.out.js.gz": 8714, + "a.out.js": 24283, + "a.out.js.gz": 8726, "a.out.nodebug.wasm": 14850, "a.out.nodebug.wasm.gz": 7311, - "total": 39111, - "total_gz": 16025, + "total": 39133, + "total_gz": 16037, "sent": [ "fd_write" ], diff --git a/test/codesize/test_codesize_minimal_O0.expected.js b/test/codesize/test_codesize_minimal_O0.expected.js index ad72f50b73f66..9738b0c9ecd50 100644 --- a/test/codesize/test_codesize_minimal_O0.expected.js +++ b/test/codesize/test_codesize_minimal_O0.expected.js @@ -1067,6 +1067,7 @@ Module['FS_createPreloadedFile'] = FS.createPreloadedFile; 'FS_fileDataToTypedArray', 'FS_stdin_getChar', 'FS_mkdirTree', + 'nodeFsync', '_setNetworkCallback', ]; missingLibrarySymbols.forEach(missingLibrarySymbol) @@ -1254,6 +1255,8 @@ missingLibrarySymbols.forEach(missingLibrarySymbol) 'FS_createLazyFile', 'MEMFS', 'TTY', + 'DEV', + 'nodeTTY', 'PIPEFS', 'SOCKFS', ]; diff --git a/test/codesize/test_codesize_minimal_O0.json b/test/codesize/test_codesize_minimal_O0.json index 8d4cc47adfaba..044b8d4c8028e 100644 --- a/test/codesize/test_codesize_minimal_O0.json +++ b/test/codesize/test_codesize_minimal_O0.json @@ -1,10 +1,10 @@ { - "a.out.js": 19452, - "a.out.js.gz": 6998, + "a.out.js": 19474, + "a.out.js.gz": 7010, "a.out.nodebug.wasm": 1015, "a.out.nodebug.wasm.gz": 602, - "total": 20467, - "total_gz": 7600, + "total": 20489, + "total_gz": 7612, "sent": [], "imports": [], "exports": [ diff --git a/test/codesize/test_unoptimized_code_size.json b/test/codesize/test_unoptimized_code_size.json index 3fd577e8caa13..ed3525208d196 100644 --- a/test/codesize/test_unoptimized_code_size.json +++ b/test/codesize/test_unoptimized_code_size.json @@ -1,16 +1,16 @@ { - "hello_world.js": 57078, - "hello_world.js.gz": 17753, + "hello_world.js": 57115, + "hello_world.js.gz": 17768, "hello_world.wasm": 14850, "hello_world.wasm.gz": 7311, "no_asserts.js": 26654, "no_asserts.js.gz": 8901, "no_asserts.wasm": 12010, "no_asserts.wasm.gz": 5880, - "strict.js": 54896, - "strict.js.gz": 17061, + "strict.js": 54933, + "strict.js.gz": 17077, "strict.wasm": 14850, "strict.wasm.gz": 7311, - "total": 180338, - "total_gz": 64217 + "total": 180412, + "total_gz": 64248 } From e6a79cd9972840b94a067e4f6a1fdc509c566ddd Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Fri, 3 Apr 2026 12:14:21 -0700 Subject: [PATCH 7/9] Fix --- src/lib/libdev.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/libdev.js b/src/lib/libdev.js index da89cb8d1f034..05847b7fcb5c0 100644 --- a/src/lib/libdev.js +++ b/src/lib/libdev.js @@ -101,7 +101,7 @@ addToLibrary({ nodeFsync(nodeStream.fd); }, read(ops, buffer) { - return fs.readSync(nodeStream.fd, buffer) + return fs.readSync(nodeStream.fd, buffer, 0, buffer.length); }, }), nodeOutputDevice: (nodeStream) => ({ @@ -110,7 +110,7 @@ addToLibrary({ nodeFsync(nodeStream.fd); }, write(ops, buffer) { - return fs.writeSync(nodeStream.fd, buffer) + return fs.writeSync(nodeStream.fd, buffer, 0, buffer.length); }, getTerminalSize() { return nodeStream; From 1cb0c911eb28222ca8a0b07ee8a5c665e9b54178 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Fri, 3 Apr 2026 12:15:11 -0700 Subject: [PATCH 8/9] Automatic rebaseline of codesize expectations. NFC This is an automatic change generated by tools/maint/rebaseline_tests.py. The following (13) test expectation files were updated by running the tests with `--rebaseline`: ``` codesize/test_codesize_cxx_ctors1.json: 151832 => 154179 [+2347 bytes / +1.55%] codesize/test_codesize_cxx_ctors2.json: 151235 => 153582 [+2347 bytes / +1.55%] codesize/test_codesize_cxx_except.json: 195690 => 198039 [+2349 bytes / +1.20%] codesize/test_codesize_cxx_except_wasm.json: 166948 => 169296 [+2348 bytes / +1.41%] codesize/test_codesize_cxx_except_wasm_legacy.json: 164829 => 167177 [+2348 bytes / +1.42%] codesize/test_codesize_cxx_lto.json: 120519 => 122869 [+2350 bytes / +1.95%] codesize/test_codesize_cxx_mangle.json: 262181 => 264530 [+2349 bytes / +0.90%] codesize/test_codesize_cxx_noexcept.json: 153855 => 156202 [+2347 bytes / +1.53%] test/codesize/test_codesize_file_preload.expected.js updated codesize/test_codesize_file_preload.json: 23789 => 26139 [+2350 bytes / +9.88%] codesize/test_codesize_files_js_fs.json: 18215 => 20561 [+2346 bytes / +12.88%] codesize/test_codesize_hello_dylink.json: 43853 => 46204 [+2351 bytes / +5.36%] codesize/test_codesize_hello_dylink_all.json: 821790 => 824164 [+2374 bytes / +0.29%] Average change: +3.33% (+0.29% - +12.88%) ``` --- test/codesize/test_codesize_cxx_ctors1.json | 8 ++++---- test/codesize/test_codesize_cxx_ctors2.json | 8 ++++---- test/codesize/test_codesize_cxx_except.json | 8 ++++---- test/codesize/test_codesize_cxx_except_wasm.json | 8 ++++---- .../codesize/test_codesize_cxx_except_wasm_legacy.json | 8 ++++---- test/codesize/test_codesize_cxx_lto.json | 8 ++++---- test/codesize/test_codesize_cxx_mangle.json | 10 +++++----- test/codesize/test_codesize_cxx_noexcept.json | 8 ++++---- test/codesize/test_codesize_file_preload.expected.js | 4 ++-- test/codesize/test_codesize_file_preload.json | 8 ++++---- test/codesize/test_codesize_files_js_fs.json | 8 ++++---- test/codesize/test_codesize_hello_dylink.json | 8 ++++---- test/codesize/test_codesize_hello_dylink_all.json | 6 +++--- 13 files changed, 50 insertions(+), 50 deletions(-) diff --git a/test/codesize/test_codesize_cxx_ctors1.json b/test/codesize/test_codesize_cxx_ctors1.json index dff56a4063fb0..ce2a799f79cba 100644 --- a/test/codesize/test_codesize_cxx_ctors1.json +++ b/test/codesize/test_codesize_cxx_ctors1.json @@ -1,10 +1,10 @@ { - "a.out.js": 19194, - "a.out.js.gz": 7969, + "a.out.js": 21541, + "a.out.js.gz": 9245, "a.out.nodebug.wasm": 132638, "a.out.nodebug.wasm.gz": 49927, - "total": 151832, - "total_gz": 57896, + "total": 154179, + "total_gz": 59172, "sent": [ "__cxa_throw", "_abort_js", diff --git a/test/codesize/test_codesize_cxx_ctors2.json b/test/codesize/test_codesize_cxx_ctors2.json index fb2e77ec4ae92..9d28e6c55f4f7 100644 --- a/test/codesize/test_codesize_cxx_ctors2.json +++ b/test/codesize/test_codesize_cxx_ctors2.json @@ -1,10 +1,10 @@ { - "a.out.js": 19171, - "a.out.js.gz": 7957, + "a.out.js": 21518, + "a.out.js.gz": 9227, "a.out.nodebug.wasm": 132064, "a.out.nodebug.wasm.gz": 49586, - "total": 151235, - "total_gz": 57543, + "total": 153582, + "total_gz": 58813, "sent": [ "__cxa_throw", "_abort_js", diff --git a/test/codesize/test_codesize_cxx_except.json b/test/codesize/test_codesize_cxx_except.json index c29dbd2b26b46..663a6d9bd7f04 100644 --- a/test/codesize/test_codesize_cxx_except.json +++ b/test/codesize/test_codesize_cxx_except.json @@ -1,10 +1,10 @@ { - "a.out.js": 23174, - "a.out.js.gz": 8960, + "a.out.js": 25523, + "a.out.js.gz": 10247, "a.out.nodebug.wasm": 172516, "a.out.nodebug.wasm.gz": 57438, - "total": 195690, - "total_gz": 66398, + "total": 198039, + "total_gz": 67685, "sent": [ "__cxa_begin_catch", "__cxa_end_catch", diff --git a/test/codesize/test_codesize_cxx_except_wasm.json b/test/codesize/test_codesize_cxx_except_wasm.json index cb737cf73678b..c2669158a3a0b 100644 --- a/test/codesize/test_codesize_cxx_except_wasm.json +++ b/test/codesize/test_codesize_cxx_except_wasm.json @@ -1,10 +1,10 @@ { - "a.out.js": 19026, - "a.out.js.gz": 7904, + "a.out.js": 21374, + "a.out.js.gz": 9183, "a.out.nodebug.wasm": 147922, "a.out.nodebug.wasm.gz": 55312, - "total": 166948, - "total_gz": 63216, + "total": 169296, + "total_gz": 64495, "sent": [ "_abort_js", "_tzset_js", diff --git a/test/codesize/test_codesize_cxx_except_wasm_legacy.json b/test/codesize/test_codesize_cxx_except_wasm_legacy.json index 71fbc6b06eb6d..8d6f586f60387 100644 --- a/test/codesize/test_codesize_cxx_except_wasm_legacy.json +++ b/test/codesize/test_codesize_cxx_except_wasm_legacy.json @@ -1,10 +1,10 @@ { - "a.out.js": 19100, - "a.out.js.gz": 7929, + "a.out.js": 21448, + "a.out.js.gz": 9206, "a.out.nodebug.wasm": 145729, "a.out.nodebug.wasm.gz": 54945, - "total": 164829, - "total_gz": 62874, + "total": 167177, + "total_gz": 64151, "sent": [ "_abort_js", "_tzset_js", diff --git a/test/codesize/test_codesize_cxx_lto.json b/test/codesize/test_codesize_cxx_lto.json index 7bea9337c40d2..93b17ae068d85 100644 --- a/test/codesize/test_codesize_cxx_lto.json +++ b/test/codesize/test_codesize_cxx_lto.json @@ -1,10 +1,10 @@ { - "a.out.js": 18563, - "a.out.js.gz": 7666, + "a.out.js": 20913, + "a.out.js.gz": 8928, "a.out.nodebug.wasm": 101956, "a.out.nodebug.wasm.gz": 39460, - "total": 120519, - "total_gz": 47126, + "total": 122869, + "total_gz": 48388, "sent": [ "a (emscripten_resize_heap)", "b (_setitimer_js)", diff --git a/test/codesize/test_codesize_cxx_mangle.json b/test/codesize/test_codesize_cxx_mangle.json index cd1eeff3bb99d..616b0282bb35b 100644 --- a/test/codesize/test_codesize_cxx_mangle.json +++ b/test/codesize/test_codesize_cxx_mangle.json @@ -1,10 +1,10 @@ { - "a.out.js": 23224, - "a.out.js.gz": 8983, + "a.out.js": 25573, + "a.out.js.gz": 10269, "a.out.nodebug.wasm": 238957, - "a.out.nodebug.wasm.gz": 79842, - "total": 262181, - "total_gz": 88825, + "a.out.nodebug.wasm.gz": 79847, + "total": 264530, + "total_gz": 90116, "sent": [ "__cxa_begin_catch", "__cxa_end_catch", diff --git a/test/codesize/test_codesize_cxx_noexcept.json b/test/codesize/test_codesize_cxx_noexcept.json index 4881c4a93cac7..b683c6634442f 100644 --- a/test/codesize/test_codesize_cxx_noexcept.json +++ b/test/codesize/test_codesize_cxx_noexcept.json @@ -1,10 +1,10 @@ { - "a.out.js": 19194, - "a.out.js.gz": 7969, + "a.out.js": 21541, + "a.out.js.gz": 9245, "a.out.nodebug.wasm": 134661, "a.out.nodebug.wasm.gz": 50777, - "total": 153855, - "total_gz": 58746, + "total": 156202, + "total_gz": 60022, "sent": [ "__cxa_throw", "_abort_js", diff --git a/test/codesize/test_codesize_file_preload.expected.js b/test/codesize/test_codesize_file_preload.expected.js index 5f483b81190c1..00825b17fefdd 100644 --- a/test/codesize/test_codesize_file_preload.expected.js +++ b/test/codesize/test_codesize_file_preload.expected.js @@ -902,7 +902,7 @@ var DEV = { nodeFsync(nodeStream.fd); }, read(ops, buffer) { - return fs.readSync(nodeStream.fd, buffer); + return fs.readSync(nodeStream.fd, buffer, 0, buffer.length); } }), nodeOutputDevice: nodeStream => ({ @@ -911,7 +911,7 @@ var DEV = { nodeFsync(nodeStream.fd); }, write(ops, buffer) { - return fs.writeSync(nodeStream.fd, buffer); + return fs.writeSync(nodeStream.fd, buffer, 0, buffer.length); }, getTerminalSize() { return nodeStream; diff --git a/test/codesize/test_codesize_file_preload.json b/test/codesize/test_codesize_file_preload.json index a84ee7c65dad3..753e7ec743b86 100644 --- a/test/codesize/test_codesize_file_preload.json +++ b/test/codesize/test_codesize_file_preload.json @@ -1,10 +1,10 @@ { - "a.out.js": 22141, - "a.out.js.gz": 9184, + "a.out.js": 24491, + "a.out.js.gz": 10462, "a.out.nodebug.wasm": 1648, "a.out.nodebug.wasm.gz": 939, - "total": 23789, - "total_gz": 10123, + "total": 26139, + "total_gz": 11401, "sent": [ "a (fd_write)" ], diff --git a/test/codesize/test_codesize_files_js_fs.json b/test/codesize/test_codesize_files_js_fs.json index 78a3cf7f21ebf..0623026e7bd6f 100644 --- a/test/codesize/test_codesize_files_js_fs.json +++ b/test/codesize/test_codesize_files_js_fs.json @@ -1,10 +1,10 @@ { - "a.out.js": 17834, - "a.out.js.gz": 7308, + "a.out.js": 20180, + "a.out.js.gz": 8565, "a.out.nodebug.wasm": 381, "a.out.nodebug.wasm.gz": 260, - "total": 18215, - "total_gz": 7568, + "total": 20561, + "total_gz": 8825, "sent": [ "a (fd_write)", "b (fd_read)", diff --git a/test/codesize/test_codesize_hello_dylink.json b/test/codesize/test_codesize_hello_dylink.json index efb0f7d1c501f..6cde14e13d265 100644 --- a/test/codesize/test_codesize_hello_dylink.json +++ b/test/codesize/test_codesize_hello_dylink.json @@ -1,10 +1,10 @@ { - "a.out.js": 26185, - "a.out.js.gz": 11171, + "a.out.js": 28536, + "a.out.js.gz": 12476, "a.out.nodebug.wasm": 17668, "a.out.nodebug.wasm.gz": 8921, - "total": 43853, - "total_gz": 20092, + "total": 46204, + "total_gz": 21397, "sent": [ "__syscall_stat64", "emscripten_resize_heap", diff --git a/test/codesize/test_codesize_hello_dylink_all.json b/test/codesize/test_codesize_hello_dylink_all.json index 8eaf79b3ee12a..120aba8d4b358 100644 --- a/test/codesize/test_codesize_hello_dylink_all.json +++ b/test/codesize/test_codesize_hello_dylink_all.json @@ -1,7 +1,7 @@ { - "a.out.js": 244343, - "a.out.nodebug.wasm": 577447, - "total": 821790, + "a.out.js": 246720, + "a.out.nodebug.wasm": 577444, + "total": 824164, "sent": [ "IMG_Init", "IMG_Load", From 124ac4e7e3756d8cba1e28b1333203812c137366 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Fri, 3 Apr 2026 13:14:38 -0700 Subject: [PATCH 9/9] Update code size --- test/codesize/test_codesize_hello_dylink_all.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/codesize/test_codesize_hello_dylink_all.json b/test/codesize/test_codesize_hello_dylink_all.json index 120aba8d4b358..5779b24e5e2b3 100644 --- a/test/codesize/test_codesize_hello_dylink_all.json +++ b/test/codesize/test_codesize_hello_dylink_all.json @@ -1,7 +1,7 @@ { "a.out.js": 246720, "a.out.nodebug.wasm": 577444, - "total": 824164, + "total": 824167, "sent": [ "IMG_Init", "IMG_Load",