diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 6f452ccd9c..11710ea952 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -36,6 +36,7 @@ env: BUN_GARBAGE_COLLECTOR_LEVEL: 1 BUN_FEATURE_FLAG_INTERNAL_FOR_TESTING: 1 CI: true + USE_LTO: 1 jobs: build-submodules: @@ -87,6 +88,7 @@ jobs: env: CPU_TARGET: ${{ inputs.cpu }} CCACHE_DIR: ccache + USE_LTO: 1 run: | .\scripts\env.ps1 ${{ contains(inputs.tag, '-baseline') && '-Baseline' || '' }} choco install -y nasm --version=2.16.01 @@ -172,6 +174,7 @@ jobs: env: CPU_TARGET: ${{ inputs.cpu }} CCACHE_DIR: ccache + USE_LTO: 1 run: | # $CANARY_REVISION = if (Test-Path build/.canary_revision) { Get-Content build/.canary_revision } else { "0" } $CANARY_REVISION = 0 @@ -181,6 +184,7 @@ jobs: cd build cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release ` -DNO_CODEGEN=1 ` + -DUSE_LTO=1 ` -DNO_CONFIGURE_DEPENDS=1 ` "-DCANARY=${CANARY_REVISION}" ` -DBUN_CPP_ONLY=1 ${{ contains(inputs.tag, '-baseline') && '-DUSE_BASELINE_BUILD=1' || '' }} @@ -276,6 +280,7 @@ jobs: -DNO_CONFIGURE_DEPENDS=1 ` "-DCANARY=${CANARY_REVISION}" ` -DBUN_LINK_ONLY=1 ` + -DUSE_LTO=1 ` "-DBUN_DEPS_OUT_DIR=$(Resolve-Path ../bun-deps)" ` "-DBUN_CPP_ARCHIVE=$(Resolve-Path ../bun-cpp/bun-cpp-objects.a)" ` "-DBUN_ZIG_OBJ_DIR=$(Resolve-Path ../bun-zig)" ` diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c2ac0d231..fdceab1972 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,8 +15,6 @@ set(CMAKE_C_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_INTERPROCEDURAL_OPTIMIZATION OFF) - # Should not start with v # Used in process.version, process.versions.node, napi, and elsewhere set(REPORTED_NODEJS_VERSION "22.3.0") @@ -324,6 +322,11 @@ option(USE_STATIC_LIBATOMIC "Statically link libatomic, requires the presence of option(USE_LTO "Enable Link-Time Optimization" ${DEFAULT_LTO}) +if(WIN32 AND USE_LTO) + set(CMAKE_LINKER_TYPE LLD) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION OFF) +endif() + option(BUN_TIDY_ONLY "Only run clang-tidy" OFF) option(BUN_TIDY_ONLY_EXTRA " Only run clang-tidy, with extra checks for local development" OFF) @@ -1080,6 +1083,8 @@ elseif(CMAKE_BUILD_TYPE STREQUAL "Release") set(LTO_LINK_FLAG "") if(USE_LTO) + target_compile_options(${bun} PUBLIC -Xclang -emit-llvm-bc) + # -emit-llvm seems to not be supported or under a different name on Windows. list(APPEND LTO_FLAG "-flto=full") list(APPEND LTO_LINK_FLAG "/LTCG") @@ -1120,10 +1125,6 @@ if(WIN32) # set_property(TARGET ${bun} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded") set_property(TARGET ${bun} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") - if(USE_LTO) - target_compile_options(${bun} PUBLIC "-Xclang -emit-llvm-bc") - endif() - target_compile_options(${bun} PUBLIC "/EHsc" "/GR-") target_link_options(${bun} PUBLIC "/STACK:0x1200000,0x100000" "/DEF:${BUN_SRC}/symbols.def" "/errorlimit:0") else() diff --git a/bench/bun.lockb b/bench/bun.lockb index 78eea70b0e..679ce8aba1 100755 Binary files a/bench/bun.lockb and b/bench/bun.lockb differ diff --git a/bench/package.json b/bench/package.json index 761fa8936e..6ea67f5c99 100644 --- a/bench/package.json +++ b/bench/package.json @@ -3,6 +3,7 @@ "dependencies": { "@babel/core": "^7.16.10", "@babel/preset-react": "^7.16.7", + "@babel/standalone": "^7.24.7", "@swc/core": "^1.2.133", "benchmark": "^2.1.4", "braces": "^3.0.2", diff --git a/bench/snippets/transpiler-2.mjs b/bench/snippets/transpiler-2.mjs new file mode 100644 index 0000000000..702fda9d18 --- /dev/null +++ b/bench/snippets/transpiler-2.mjs @@ -0,0 +1,14 @@ +import { bench, run } from "mitata"; +import { join } from "path"; + +const code = require("fs").readFileSync( + process.argv[2] || join(import.meta.dir, "../node_modules/@babel/standalone/babel.min.js"), +); + +const transpiler = new Bun.Transpiler({ minify: true }); + +bench("transformSync", () => { + transpiler.transformSync(code); +}); + +await run(); diff --git a/docs/guides/process/argv.md b/docs/guides/process/argv.md index 7804f5b621..3acb68e52f 100644 --- a/docs/guides/process/argv.md +++ b/docs/guides/process/argv.md @@ -13,7 +13,7 @@ console.log(Bun.argv); Running this file with arguments results in the following: ```sh -$ bun run cli.tsx --flag1 --flag2 value +$ bun run cli.ts --flag1 --flag2 value [ '/path/to/bun', '/path/to/cli.ts', '--flag1', '--flag2', 'value' ] ``` @@ -47,7 +47,7 @@ console.log(positionals); then it outputs ``` -$ bun run cli.tsx --flag1 --flag2 value +$ bun run cli.ts --flag1 --flag2 value { flag1: true, flag2: "value", diff --git a/docs/guides/runtime/cicd.md b/docs/guides/runtime/cicd.md index c6d6a36a3b..b94d22140a 100644 --- a/docs/guides/runtime/cicd.md +++ b/docs/guides/runtime/cicd.md @@ -13,7 +13,7 @@ jobs: steps: # ... - uses: actions/checkout@v4 -+ - uses: oven-sh/setup-bun@v1 ++ - uses: oven-sh/setup-bun@v2 # run any `bun` or `bunx` command + - run: bun install @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-latest steps: # ... - - uses: oven-sh/setup-bun@v1 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.0.11 # or "latest", "canary", ``` diff --git a/packages/bun-usockets/src/bsd.c b/packages/bun-usockets/src/bsd.c index 582aafbe9e..b736a340a0 100644 --- a/packages/bun-usockets/src/bsd.c +++ b/packages/bun-usockets/src/bsd.c @@ -42,6 +42,7 @@ #define HAS_MSGX #endif + /* We need to emulate sendmmsg, recvmmsg on platform who don't have it */ int bsd_sendmmsg(LIBUS_SOCKET_DESCRIPTOR fd, struct udp_sendbuf* sendbuf, int flags) { #if defined(_WIN32)// || defined(__APPLE__) @@ -397,7 +398,9 @@ int bsd_addr_get_port(struct bsd_addr_t *addr) { // called by dispatch_ready_poll LIBUS_SOCKET_DESCRIPTOR bsd_accept_socket(LIBUS_SOCKET_DESCRIPTOR fd, struct bsd_addr_t *addr) { LIBUS_SOCKET_DESCRIPTOR accepted_fd; - addr->len = sizeof(addr->mem); + + while (1) { + addr->len = sizeof(addr->mem); #if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) // Linux, FreeBSD @@ -405,12 +408,18 @@ LIBUS_SOCKET_DESCRIPTOR bsd_accept_socket(LIBUS_SOCKET_DESCRIPTOR fd, struct bsd #else // Windows, OS X accepted_fd = accept(fd, (struct sockaddr *) addr, &addr->len); - #endif - /* We cannot rely on addr since it is not initialized if failed */ - if (accepted_fd == LIBUS_SOCKET_ERROR) { - return LIBUS_SOCKET_ERROR; + if (UNLIKELY(IS_EINTR(accepted_fd))) { + continue; + } + + /* We cannot rely on addr since it is not initialized if failed */ + if (accepted_fd == LIBUS_SOCKET_ERROR) { + return LIBUS_SOCKET_ERROR; + } + + break; } internal_finalize_bsd_addr(addr); @@ -423,14 +432,22 @@ LIBUS_SOCKET_DESCRIPTOR bsd_accept_socket(LIBUS_SOCKET_DESCRIPTOR fd, struct bsd #endif } -int bsd_recv(LIBUS_SOCKET_DESCRIPTOR fd, void *buf, int length, int flags) { - return recv(fd, buf, length, flags); +ssize_t bsd_recv(LIBUS_SOCKET_DESCRIPTOR fd, void *buf, int length, int flags) { + while (1) { + ssize_t ret = recv(fd, buf, length, flags); + + if (UNLIKELY(IS_EINTR(ret))) { + continue; + } + + return ret; + } } #if !defined(_WIN32) #include -int bsd_write2(LIBUS_SOCKET_DESCRIPTOR fd, const char *header, int header_length, const char *payload, int payload_length) { +ssize_t bsd_write2(LIBUS_SOCKET_DESCRIPTOR fd, const char *header, int header_length, const char *payload, int payload_length) { struct iovec chunks[2]; chunks[0].iov_base = (char *)header; @@ -438,13 +455,21 @@ int bsd_write2(LIBUS_SOCKET_DESCRIPTOR fd, const char *header, int header_length chunks[1].iov_base = (char *)payload; chunks[1].iov_len = payload_length; - return writev(fd, chunks, 2); + while (1) { + ssize_t written = writev(fd, chunks, 2); + + if (UNLIKELY(IS_EINTR(written))) { + continue; + } + + return written; + } } #else -int bsd_write2(LIBUS_SOCKET_DESCRIPTOR fd, const char *header, int header_length, const char *payload, int payload_length) { - int written = bsd_send(fd, header, header_length, 0); +ssize_t bsd_write2(LIBUS_SOCKET_DESCRIPTOR fd, const char *header, int header_length, const char *payload, int payload_length) { + ssize_t written = bsd_send(fd, header, header_length, 0); if (written == header_length) { - int second_write = bsd_send(fd, payload, payload_length, 0); + ssize_t second_write = bsd_send(fd, payload, payload_length, 0); if (second_write > 0) { written += second_write; } @@ -453,26 +478,28 @@ int bsd_write2(LIBUS_SOCKET_DESCRIPTOR fd, const char *header, int header_length } #endif -int bsd_send(LIBUS_SOCKET_DESCRIPTOR fd, const char *buf, int length, int msg_more) { - +ssize_t bsd_send(LIBUS_SOCKET_DESCRIPTOR fd, const char *buf, int length, int msg_more) { + while (1) { // MSG_MORE (Linux), MSG_PARTIAL (Windows), TCP_NOPUSH (BSD) #ifndef MSG_NOSIGNAL #define MSG_NOSIGNAL 0 #endif -#ifdef MSG_MORE + #ifdef MSG_MORE + // for Linux we do not want signals + ssize_t rc = send(fd, buf, length, ((msg_more != 0) * MSG_MORE) | MSG_NOSIGNAL | MSG_DONTWAIT); + #else + // use TCP_NOPUSH + ssize_t rc = send(fd, buf, length, MSG_NOSIGNAL | MSG_DONTWAIT); + #endif - // for Linux we do not want signals - return send(fd, buf, length, ((msg_more != 0) * MSG_MORE) | MSG_NOSIGNAL | MSG_DONTWAIT); + if (UNLIKELY(IS_EINTR(rc))) { + continue; + } -#else - - // use TCP_NOPUSH - - return send(fd, buf, length, MSG_NOSIGNAL | MSG_DONTWAIT); - -#endif + return rc; + } } int bsd_would_block() { @@ -483,6 +510,23 @@ int bsd_would_block() { #endif } +static int us_internal_bind_and_listen(LIBUS_SOCKET_DESCRIPTOR listenFd, struct sockaddr *listenAddr, socklen_t listenAddrLength, int backlog) { + int result; + do + result = bind(listenFd, listenAddr, listenAddrLength); + while (IS_EINTR(result)); + + if (result == -1) { + return -1; + } + + do + result = listen(listenFd, backlog); + while (IS_EINTR(result)); + + return result; +} + inline __attribute__((always_inline)) LIBUS_SOCKET_DESCRIPTOR bsd_bind_listen_fd( LIBUS_SOCKET_DESCRIPTOR listenFd, struct addrinfo *listenAddr, @@ -512,7 +556,7 @@ inline __attribute__((always_inline)) LIBUS_SOCKET_DESCRIPTOR bsd_bind_listen_fd setsockopt(listenFd, IPPROTO_IPV6, IPV6_V6ONLY, (void *) &disabled, sizeof(disabled)); #endif - if (bind(listenFd, listenAddr->ai_addr, (socklen_t) listenAddr->ai_addrlen) || listen(listenFd, 512)) { + if (us_internal_bind_and_listen(listenFd, listenAddr->ai_addr, (socklen_t) listenAddr->ai_addrlen, 512)) { return LIBUS_SOCKET_ERROR; } @@ -690,7 +734,7 @@ static LIBUS_SOCKET_DESCRIPTOR internal_bsd_create_listen_socket_unix(const char unlink(path); #endif - if (bind(listenFd, (struct sockaddr *)server_address, addrlen) || listen(listenFd, 512)) { + if (us_internal_bind_and_listen(listenFd, (struct sockaddr *) server_address, (socklen_t) addrlen, 512)) { #if defined(_WIN32) int shouldSimulateENOENT = WSAGetLastError() == WSAENETDOWN; #endif @@ -925,7 +969,7 @@ static int bsd_do_connect_raw(LIBUS_SOCKET_DESCRIPTOR fd, struct sockaddr *addr, do { errno = 0; r = connect(fd, (struct sockaddr *)addr, namelen); - } while (r == -1 && errno == EINTR); + } while (IS_EINTR(r)); // connect() can return -1 with an errno of 0. // the errno is the correct one in that case. diff --git a/packages/bun-usockets/src/eventing/epoll_kqueue.c b/packages/bun-usockets/src/eventing/epoll_kqueue.c index 192d0972fa..374e9ce809 100644 --- a/packages/bun-usockets/src/eventing/epoll_kqueue.c +++ b/packages/bun-usockets/src/eventing/epoll_kqueue.c @@ -109,6 +109,51 @@ struct us_loop_t *us_timer_loop(struct us_timer_t *t) { return internal_cb->loop; } + +#if defined(LIBUS_USE_EPOLL) + +#include +static int has_epoll_pwait2 = -1; + +#ifndef SYS_epoll_pwait2 +// It's consistent on multiple architectures +// https://github.com/torvalds/linux/blob/9d1ddab261f3e2af7c384dc02238784ce0cf9f98/include/uapi/asm-generic/unistd.h#L795 +// https://github.com/google/gvisor/blob/master/test/syscalls/linux/epoll.cc#L48C1-L50C7 +#define SYS_epoll_pwait2 441 +#endif + +static ssize_t sys_epoll_pwait2(int epfd, struct epoll_event *events, int maxevents, const struct timespec *timeout, const sigset_t *sigmask, size_t sigsetsize) { + return syscall(SYS_epoll_pwait2, epfd, events, maxevents, timeout, sigmask, sigsetsize); +} + +static int bun_epoll_pwait2(int epfd, struct epoll_event *events, int maxevents, const struct timespec *timeout) { + int ret; + if (has_epoll_pwait2 != 0) { + do { + ret = sys_epoll_pwait2(epfd, events, maxevents, timeout, NULL, 0); + } while (IS_EINTR(ret)); + + if (LIKELY(ret != -1 || errno != ENOSYS)) { + return ret; + } + + has_epoll_pwait2 = 0; + } + + int timeoutMs = -1; + if (timeout) { + timeoutMs = timeout->tv_sec * 1000 + timeout->tv_nsec / 1000000; + } + + do { + ret = epoll_wait(epfd, events, maxevents, timeoutMs); + } while (IS_EINTR(ret)); + + return ret; +} + +#endif + /* Loop */ struct us_loop_t *us_create_loop(void *hint, void (*wakeup_cb)(struct us_loop_t *loop), void (*pre_cb)(struct us_loop_t *loop), void (*post_cb)(struct us_loop_t *loop), unsigned int ext_size) { struct us_loop_t *loop = (struct us_loop_t *) us_calloc(1, sizeof(struct us_loop_t) + ext_size); @@ -139,9 +184,11 @@ void us_loop_run(struct us_loop_t *loop) { /* Fetch ready polls */ #ifdef LIBUS_USE_EPOLL - loop->num_ready_polls = epoll_wait(loop->fd, loop->ready_polls, 1024, -1); + loop->num_ready_polls = bun_epoll_pwait2(loop->fd, loop->ready_polls, 1024, NULL); #else - loop->num_ready_polls = kevent64(loop->fd, NULL, 0, loop->ready_polls, 1024, 0, NULL); + do { + loop->num_ready_polls = kevent64(loop->fd, NULL, 0, loop->ready_polls, 1024, 0, NULL); + } while (IS_EINTR(loop->num_ready_polls)); #endif /* Iterate ready polls, dispatching them by type */ @@ -183,12 +230,6 @@ void us_loop_run(struct us_loop_t *loop) { } } -#if defined(LIBUS_USE_EPOLL) - -// static int has_epoll_pwait2 = 0; -// TODO: - -#endif void us_loop_run_bun_tick(struct us_loop_t *loop, const struct timespec* timeout) { if (loop->num_polls == 0) @@ -207,13 +248,12 @@ void us_loop_run_bun_tick(struct us_loop_t *loop, const struct timespec* timeout /* Fetch ready polls */ #ifdef LIBUS_USE_EPOLL - int timeoutMs = -1; - if (timeout) { - timeoutMs = timeout->tv_sec * 1000 + timeout->tv_nsec / 1000000; - } - loop->num_ready_polls = epoll_wait(loop->fd, loop->ready_polls, 1024, timeoutMs); + + loop->num_ready_polls = bun_epoll_pwait2(loop->fd, loop->ready_polls, 1024, timeout); #else - loop->num_ready_polls = kevent64(loop->fd, NULL, 0, loop->ready_polls, 1024, 0, timeout); + do { + loop->num_ready_polls = kevent64(loop->fd, NULL, 0, loop->ready_polls, 1024, 0, timeout); + } while (IS_EINTR(loop->num_ready_polls)); #endif /* Iterate ready polls, dispatching them by type */ @@ -296,7 +336,10 @@ int kqueue_change(int kqfd, int fd, int old_events, int new_events, void *user_d EV_SET64(&change_list[change_length++], fd, EVFILT_WRITE, (new_events & LIBUS_SOCKET_WRITABLE) ? EV_ADD : EV_DELETE, 0, 0, (uint64_t)(void*)user_data, 0, 0); } - int ret = kevent64(kqfd, change_list, change_length, change_list, change_length, KEVENT_FLAG_ERROR_EVENTS, NULL); + int ret; + do { + ret = kevent64(kqfd, change_list, change_length, change_list, change_length, KEVENT_FLAG_ERROR_EVENTS, NULL); + } while (IS_EINTR(ret)); // ret should be 0 in most cases (not guaranteed when removing async) @@ -332,7 +375,10 @@ void us_poll_start(struct us_poll_t *p, struct us_loop_t *loop, int events) { struct epoll_event event; event.events = events; event.data.ptr = p; - epoll_ctl(loop->fd, EPOLL_CTL_ADD, p->state.fd, &event); + int ret; + do { + ret = epoll_ctl(loop->fd, EPOLL_CTL_ADD, p->state.fd, &event); + } while (IS_EINTR(ret)); #else kqueue_change(loop->fd, p->state.fd, 0, events, p); #endif @@ -348,7 +394,10 @@ void us_poll_change(struct us_poll_t *p, struct us_loop_t *loop, int events) { struct epoll_event event; event.events = events; event.data.ptr = p; - epoll_ctl(loop->fd, EPOLL_CTL_MOD, p->state.fd, &event); + int rc; + do { + rc = epoll_ctl(loop->fd, EPOLL_CTL_MOD, p->state.fd, &event); + } while (IS_EINTR(rc)); #else kqueue_change(loop->fd, p->state.fd, old_events, events, p); #endif @@ -362,7 +411,10 @@ void us_poll_stop(struct us_poll_t *p, struct us_loop_t *loop) { int new_events = 0; #ifdef LIBUS_USE_EPOLL struct epoll_event event; - epoll_ctl(loop->fd, EPOLL_CTL_DEL, p->state.fd, &event); + int rc; + do { + rc = epoll_ctl(loop->fd, EPOLL_CTL_DEL, p->state.fd, &event); + } while (IS_EINTR(rc)); #else if (old_events) { kqueue_change(loop->fd, p->state.fd, old_events, new_events, NULL); @@ -373,12 +425,14 @@ void us_poll_stop(struct us_poll_t *p, struct us_loop_t *loop) { us_internal_loop_update_pending_ready_polls(loop, p, 0, old_events, new_events); } -unsigned int us_internal_accept_poll_event(struct us_poll_t *p) { +size_t us_internal_accept_poll_event(struct us_poll_t *p) { #ifdef LIBUS_USE_EPOLL int fd = us_poll_fd(p); uint64_t buf; - int read_length = read(fd, &buf, 8); - (void)read_length; + ssize_t read_length = 0; + do { + read_length = read(fd, &buf, 8); + } while (IS_EINTR(read_length)); return buf; #else /* Kqueue has no underlying FD for timers or user events */ @@ -467,7 +521,11 @@ void us_timer_close(struct us_timer_t *timer, int fallthrough) { struct kevent64_s event; EV_SET64(&event, (uint64_t) (void*) internal_cb, EVFILT_TIMER, EV_DELETE, 0, 0, (uint64_t)internal_cb, 0, 0); - kevent64(internal_cb->loop->fd, &event, 1, &event, 1, KEVENT_FLAG_ERROR_EVENTS, NULL); + int ret; + do { + ret = kevent64(internal_cb->loop->fd, &event, 1, &event, 1, KEVENT_FLAG_ERROR_EVENTS, NULL); + } while (IS_EINTR(ret)); + /* (regular) sockets are the only polls which are not freed immediately */ if(fallthrough){ @@ -486,7 +544,11 @@ void us_timer_set(struct us_timer_t *t, void (*cb)(struct us_timer_t *t), int ms struct kevent64_s event; uint64_t ptr = (uint64_t)(void*)internal_cb; EV_SET64(&event, ptr, EVFILT_TIMER, EV_ADD | (repeat_ms ? 0 : EV_ONESHOT), 0, ms, (uint64_t)internal_cb, 0, 0); - kevent64(internal_cb->loop->fd, &event, 1, &event, 1, KEVENT_FLAG_ERROR_EVENTS, NULL); + + int ret; + do { + ret = kevent64(internal_cb->loop->fd, &event, 1, &event, 1, KEVENT_FLAG_ERROR_EVENTS, NULL); + } while (IS_EINTR(ret)); } #endif @@ -581,7 +643,11 @@ void us_internal_async_close(struct us_internal_async *a) { struct kevent64_s event; uint64_t ptr = (uint64_t)(void*)internal_cb; EV_SET64(&event, ptr, EVFILT_MACHPORT, EV_DELETE, 0, 0, (uint64_t)(void*)internal_cb, 0,0); - kevent64(internal_cb->loop->fd, &event, 1, &event, 1, KEVENT_FLAG_ERROR_EVENTS, NULL); + + int ret; + do { + ret = kevent64(internal_cb->loop->fd, &event, 1, &event, 1, KEVENT_FLAG_ERROR_EVENTS, NULL); + } while (IS_EINTR(ret)); mach_port_deallocate(mach_task_self(), internal_cb->port); us_free(internal_cb->machport_buf); @@ -609,7 +675,10 @@ void us_internal_async_set(struct us_internal_async *a, void (*cb)(struct us_int event.ext[1] = MACHPORT_BUF_LEN; event.udata = (uint64_t)(void*)internal_cb; - int ret = kevent64(internal_cb->loop->fd, &event, 1, &event, 1, KEVENT_FLAG_ERROR_EVENTS, NULL); + int ret; + do { + ret = kevent64(internal_cb->loop->fd, &event, 1, &event, 1, KEVENT_FLAG_ERROR_EVENTS, NULL); + } while (IS_EINTR(ret)); if (UNLIKELY(ret == -1)) { abort(); diff --git a/packages/bun-usockets/src/eventing/libuv.c b/packages/bun-usockets/src/eventing/libuv.c index 242cd2eb66..b808795ef0 100644 --- a/packages/bun-usockets/src/eventing/libuv.c +++ b/packages/bun-usockets/src/eventing/libuv.c @@ -125,7 +125,7 @@ int us_poll_events(struct us_poll_t *p) { ((p->poll_type & POLL_TYPE_POLLING_OUT) ? LIBUS_SOCKET_WRITABLE : 0); } -unsigned int us_internal_accept_poll_event(struct us_poll_t *p) { return 0; } +size_t us_internal_accept_poll_event(struct us_poll_t *p) { return 0; } int us_internal_poll_type(struct us_poll_t *p) { return p->poll_type & POLL_TYPE_KIND_MASK; } diff --git a/packages/bun-usockets/src/internal/internal.h b/packages/bun-usockets/src/internal/internal.h index f875bb9a3f..f0a34a823d 100644 --- a/packages/bun-usockets/src/internal/internal.h +++ b/packages/bun-usockets/src/internal/internal.h @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +#pragma once #ifndef INTERNAL_H #define INTERNAL_H @@ -22,6 +22,10 @@ #ifndef __cplusplus #define alignas(x) __declspec(align(x)) #endif + +#include +typedef SSIZE_T ssize_t; + #else #include #endif @@ -52,6 +56,17 @@ void us_internal_loop_update_pending_ready_polls(struct us_loop_t *loop, #include "internal/eventing/libuv.h" #endif +#ifndef LIKELY +#define LIKELY(cond) __builtin_expect((_Bool)(cond), 1) +#define UNLIKELY(cond) __builtin_expect((_Bool)(cond), 0) +#endif + +#ifdef _WIN32 +#define IS_EINTR(rc) (rc == SOCKET_ERROR && WSAGetLastError() == WSAEINTR) +#else +#define IS_EINTR(rc) (rc == -1 && errno == EINTR) +#endif + /* Poll type and what it polls for */ enum { /* Three first bits */ @@ -118,7 +133,7 @@ void us_internal_async_set(struct us_internal_async *a, void us_internal_async_wakeup(struct us_internal_async *a); /* Eventing related */ -unsigned int us_internal_accept_poll_event(struct us_poll_t *p); +size_t us_internal_accept_poll_event(struct us_poll_t *p); int us_internal_poll_type(struct us_poll_t *p); void us_internal_poll_set_type(struct us_poll_t *p, int poll_type); diff --git a/packages/bun-usockets/src/internal/networking/bsd.h b/packages/bun-usockets/src/internal/networking/bsd.h index d3bc65e16d..9e9b421011 100644 --- a/packages/bun-usockets/src/internal/networking/bsd.h +++ b/packages/bun-usockets/src/internal/networking/bsd.h @@ -134,9 +134,9 @@ int bsd_addr_get_port(struct bsd_addr_t *addr); // called by dispatch_ready_poll LIBUS_SOCKET_DESCRIPTOR bsd_accept_socket(LIBUS_SOCKET_DESCRIPTOR fd, struct bsd_addr_t *addr); -int bsd_recv(LIBUS_SOCKET_DESCRIPTOR fd, void *buf, int length, int flags); -int bsd_send(LIBUS_SOCKET_DESCRIPTOR fd, const char *buf, int length, int msg_more); -int bsd_write2(LIBUS_SOCKET_DESCRIPTOR fd, const char *header, int header_length, const char *payload, int payload_length); +ssize_t bsd_recv(LIBUS_SOCKET_DESCRIPTOR fd, void *buf, int length, int flags); +ssize_t bsd_send(LIBUS_SOCKET_DESCRIPTOR fd, const char *buf, int length, int msg_more); +ssize_t bsd_write2(LIBUS_SOCKET_DESCRIPTOR fd, const char *header, int header_length, const char *payload, int payload_length); int bsd_would_block(); // return LIBUS_SOCKET_ERROR or the fd that represents listen socket diff --git a/scripts/env.ps1 b/scripts/env.ps1 index c88271c4d2..d91dfedadf 100755 --- a/scripts/env.ps1 +++ b/scripts/env.ps1 @@ -52,11 +52,16 @@ $CPUS = if ($env:CPUS) { $env:CPUS } else { (Get-CimInstance -Class Win32_Proces $CC = "clang-cl" $CXX = "clang-cl" -$CFLAGS = '/O2 /Zi' +$CFLAGS = '/O2 /Zi ' # $CFLAGS = '/O2 /Z7 /MT' -$CXXFLAGS = '/O2 /Zi' +$CXXFLAGS = '/O2 /Zi ' # $CXXFLAGS = '/O2 /Z7 /MT' +if ($env:USE_LTO -eq "1") { + $CXXFLAGS += " -fuse-ld=lld -flto -Xclang -emit-llvm-bc " + $CFLAGS += " -fuse-ld=lld -flto -Xclang -emit-llvm-bc " +} + $CPU_NAME = if ($Baseline) { "nehalem" } else { "haswell" }; $env:CPU_TARGET = $CPU_NAME @@ -71,6 +76,15 @@ $CMAKE_FLAGS = @( "-DCMAKE_C_FLAGS=$CFLAGS", "-DCMAKE_CXX_FLAGS=$CXXFLAGS" ) + +if ($env:USE_LTO -eq "1") { + if (Get-Command lld-lib -ErrorAction SilentlyContinue) { + $AR = Get-Command lld-lib -ErrorAction SilentlyContinue + $env:AR = $AR + $CMAKE_FLAGS += "-DCMAKE_AR=$AR" + } +} + $env:CC = "clang-cl" $env:CXX = "clang-cl" $env:CFLAGS = $CFLAGS diff --git a/scripts/env.sh b/scripts/env.sh index d4a26b5434..278ce207c0 100755 --- a/scripts/env.sh +++ b/scripts/env.sh @@ -43,7 +43,7 @@ export CMAKE_FLAGS=( ) CCACHE=$(which ccache || which sccache || echo "") -if [ -n "$CCACHE" ]; then +if [ -f "$CCACHE" ]; then CMAKE_FLAGS+=( -DCMAKE_C_COMPILER_LAUNCHER="$CCACHE" -DCMAKE_CXX_COMPILER_LAUNCHER="$CCACHE" diff --git a/src/bun.js/bindings/webcore/JSDOMFormData.cpp b/src/bun.js/bindings/webcore/JSDOMFormData.cpp index bb55ecd069..755944a96b 100644 --- a/src/bun.js/bindings/webcore/JSDOMFormData.cpp +++ b/src/bun.js/bindings/webcore/JSDOMFormData.cpp @@ -461,10 +461,7 @@ static inline JSC::EncodedJSValue jsDOMFormDataPrototypeFunction_set2Body(JSC::J auto name = convert(*lexicalGlobalObject, argument0.value()); RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); EnsureStillAliveScope argument1 = callFrame->uncheckedArgument(1); - EnsureStillAliveScope argument2 = callFrame->argument(2); - auto filename = argument2.value().isUndefined() ? String() : convert(*lexicalGlobalObject, argument2.value()); - RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); RefPtr blobValue = nullptr; if (argument1.value().inherits()) { @@ -473,8 +470,11 @@ static inline JSC::EncodedJSValue jsDOMFormDataPrototypeFunction_set2Body(JSC::J if (!blobValue) { throwTypeError(lexicalGlobalObject, throwScope, "Expected argument to be a Blob."_s); - RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); } + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + + auto filename = argument2.value().isUndefined() ? Blob__getFileNameString(blobValue->impl()).toWTFString(BunString::ZeroCopy) : convert(*lexicalGlobalObject, argument2.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.set(WTFMove(name), WTFMove(blobValue), WTFMove(filename)); }))); } diff --git a/src/bun.js/ipc.zig b/src/bun.js/ipc.zig index 1816ddca5f..d19004bcdc 100644 --- a/src/bun.js/ipc.zig +++ b/src/bun.js/ipc.zig @@ -23,7 +23,7 @@ pub const Mode = enum { /// This must match the behavior of node.js, and supports bun <--> node.js/etc communication. json, - const Map = std.StaticStringMap(Mode).initComptime(.{ + const Map = bun.ComptimeStringMap(Mode, .{ .{ "advanced", .advanced }, .{ "json", .json }, }); diff --git a/src/codegen/generate-classes.ts b/src/codegen/generate-classes.ts index 64d9beeba9..63efacf89b 100644 --- a/src/codegen/generate-classes.ts +++ b/src/codegen/generate-classes.ts @@ -1828,7 +1828,7 @@ pub const ${className(typeName)} = struct { /// Return the pointer to the wrapped object. /// If the object does not match the type, return null. pub fn fromJS(value: JSC.JSValue) ?*${typeName} { - JSC.markBinding(@src()); + if (comptime Environment.enable_logs) zig("${typeName}.fromJS", .{}); return ${symbolName(typeName, "fromJS")}(value); } @@ -1837,7 +1837,7 @@ pub const ${className(typeName)} = struct { /// If the object is a subclass of the type or has mutated the structure, return null. /// Note: this may return null for direct instances of the type if the user adds properties to the object. pub fn fromJSDirect(value: JSC.JSValue) ?*${typeName} { - JSC.markBinding(@src()); + if (comptime Environment.enable_logs) zig("${typeName}.fromJSDirect", .{}); return ${symbolName(typeName, "fromJSDirect")}(value); } @@ -1849,7 +1849,7 @@ pub const ${className(typeName)} = struct { /// Get the ${typeName} constructor value. /// This loads lazily from the global object. pub fn getConstructor(globalObject: *JSC.JSGlobalObject) JSC.JSValue { - JSC.markBinding(@src()); + if (comptime Environment.enable_logs) zig("${typeName}.getConstructor", .{}); return ${symbolName(typeName, "getConstructor")}(globalObject); } ` @@ -1861,7 +1861,7 @@ pub const ${className(typeName)} = struct { ? ` /// Create a new instance of ${typeName} pub fn toJS(this: *${typeName}, globalObject: *JSC.JSGlobalObject) JSC.JSValue { - JSC.markBinding(@src()); + if (comptime Environment.enable_logs) zig("${typeName}.toJS", .{}); if (comptime Environment.allow_assert) { const value__ = ${symbolName(typeName, "create")}(globalObject, this); @import("root").bun.assert(value__.as(${typeName}).? == this); // If this fails, likely a C ABI issue. diff --git a/src/js/node/timers.promises.ts b/src/js/node/timers.promises.ts index eb171941a5..e9b8a290af 100644 --- a/src/js/node/timers.promises.ts +++ b/src/js/node/timers.promises.ts @@ -84,10 +84,9 @@ function setTimeoutPromise(after = 1, value, options = {}) { signal.addEventListener("abort", onCancel); } }); - if (typeof onCancel !== "undefined") { - returnValue.finally(() => signal.removeEventListener("abort", onCancel)); - } - return returnValue; + return typeof onCancel !== "undefined" + ? returnValue.finally(() => signal.removeEventListener("abort", onCancel)) + : returnValue; } function setImmediatePromise(value, options = {}) { @@ -124,10 +123,9 @@ function setImmediatePromise(value, options = {}) { signal.addEventListener("abort", onCancel); } }); - if (typeof onCancel !== "undefined") { - returnValue.finally(() => signal.removeEventListener("abort", onCancel)); - } - return returnValue; + return typeof onCancel !== "undefined" + ? returnValue.finally(() => signal.removeEventListener("abort", onCancel)) + : returnValue; } function setIntervalPromise(after = 1, value, options = {}) { diff --git a/src/js_lexer_tables.zig b/src/js_lexer_tables.zig index c9107f1ab8..bc87fb0e60 100644 --- a/src/js_lexer_tables.zig +++ b/src/js_lexer_tables.zig @@ -7,6 +7,7 @@ const unicode = std.unicode; const default_allocator = bun.default_allocator; const string = @import("string_types.zig").string; const CodePoint = @import("string_types.zig").CodePoint; +const ComptimeStringMap = bun.ComptimeStringMap; pub const T = enum(u8) { t_end_of_file, @@ -159,7 +160,7 @@ pub const T = enum(u8) { } }; -pub const Keywords = std.StaticStringMap(T).initComptime(.{ +pub const Keywords = ComptimeStringMap(T, .{ .{ "break", .t_break }, .{ "case", .t_case }, .{ "catch", .t_catch }, @@ -198,7 +199,7 @@ pub const Keywords = std.StaticStringMap(T).initComptime(.{ .{ "with", .t_with }, }); -pub const StrictModeReservedWords = std.StaticStringMap(void).initComptime(.{ +pub const StrictModeReservedWords = ComptimeStringMap(void, .{ .{ "implements", {} }, .{ "interface", {} }, .{ "let", {} }, @@ -210,7 +211,7 @@ pub const StrictModeReservedWords = std.StaticStringMap(void).initComptime(.{ .{ "yield", {} }, }); -pub const StrictModeReservedWordsRemap = std.StaticStringMap(string).initComptime(.{ +pub const StrictModeReservedWordsRemap = ComptimeStringMap(string, .{ .{ "implements", "_implements" }, .{ "interface", "_interface" }, .{ "let", "_let" }, @@ -235,7 +236,7 @@ pub const PropertyModifierKeyword = enum { p_set, p_static, - pub const List = std.StaticStringMap(PropertyModifierKeyword).initComptime(.{ + pub const List = ComptimeStringMap(PropertyModifierKeyword, .{ .{ "abstract", .p_abstract }, .{ "async", .p_async }, .{ "declare", .p_declare }, @@ -250,7 +251,7 @@ pub const PropertyModifierKeyword = enum { }); }; -pub const TypeScriptAccessibilityModifier = std.StaticStringMap(void).initComptime(.{ +pub const TypeScriptAccessibilityModifier = ComptimeStringMap(void, .{ .{ "override", void }, .{ "private", void }, .{ "protected", void }, @@ -519,7 +520,7 @@ pub const TypescriptStmtKeyword = enum { ts_stmt_global, ts_stmt_declare, - pub const List = std.StaticStringMap(TypescriptStmtKeyword).initComptime(.{ + pub const List = ComptimeStringMap(TypescriptStmtKeyword, .{ .{ "type", TypescriptStmtKeyword.ts_stmt_type, @@ -552,7 +553,7 @@ pub const TypescriptStmtKeyword = enum { }; // Error: meta is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`. -pub const ChildlessJSXTags = std.StaticStringMap(void).initComptime(.{ +pub const ChildlessJSXTags = ComptimeStringMap(void, .{ .{ "area", void }, .{ "base", void }, .{ "br", void }, @@ -572,7 +573,7 @@ pub const ChildlessJSXTags = std.StaticStringMap(void).initComptime(.{ }); // In a microbenchmark, this outperforms -pub const jsxEntity = std.StaticStringMap(CodePoint).initComptime(.{ +pub const jsxEntity = ComptimeStringMap(CodePoint, .{ .{ "Aacute", @as(CodePoint, 0x00C1) }, .{ "aacute", @as(CodePoint, 0x00E1) }, .{ "Acirc", @as(CodePoint, 0x00C2) }, diff --git a/src/js_parser.zig b/src/js_parser.zig index ac6c5a6959..300ee19232 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -1013,7 +1013,7 @@ pub const TypeScript = struct { else => return null, } } - pub const IMap = std.StaticStringMap(Kind).initComptime(.{ + pub const IMap = bun.ComptimeStringMap(Kind, .{ .{ "unique", .unique }, .{ "abstract", .abstract }, .{ "asserts", .asserts }, @@ -1859,7 +1859,7 @@ pub const SideEffects = enum(u1) { }; } - pub fn simpifyUnusedExpr(p: anytype, expr: Expr) ?Expr { + pub fn simplifyUnusedExpr(p: anytype, expr: Expr) ?Expr { if (!p.options.features.dead_code_elimination) return expr; switch (expr.data) { .e_null, @@ -1894,12 +1894,12 @@ pub const SideEffects = enum(u1) { } }, .e_if => |__if__| { - __if__.yes = simpifyUnusedExpr(p, __if__.yes) orelse __if__.yes.toEmpty(); - __if__.no = simpifyUnusedExpr(p, __if__.no) orelse __if__.no.toEmpty(); + __if__.yes = simplifyUnusedExpr(p, __if__.yes) orelse __if__.yes.toEmpty(); + __if__.no = simplifyUnusedExpr(p, __if__.no) orelse __if__.no.toEmpty(); // "foo() ? 1 : 2" => "foo()" if (__if__.yes.isEmpty() and __if__.no.isEmpty()) { - return simpifyUnusedExpr(p, __if__.test_); + return simplifyUnusedExpr(p, __if__.test_); } // "foo() ? 1 : bar()" => "foo() || bar()" @@ -1927,7 +1927,7 @@ pub const SideEffects = enum(u1) { // such as "toString" or "valueOf". They must also never throw any exceptions. switch (un.op) { .un_void, .un_not => { - return simpifyUnusedExpr(p, un.value); + return simplifyUnusedExpr(p, un.value); }, .un_typeof => { // "typeof x" must not be transformed into if "x" since doing so could @@ -1937,7 +1937,7 @@ pub const SideEffects = enum(u1) { return null; } - return simpifyUnusedExpr(p, un.value); + return simplifyUnusedExpr(p, un.value); }, else => {}, @@ -1950,7 +1950,7 @@ pub const SideEffects = enum(u1) { // can be removed. The annotation causes us to ignore the target. if (call.can_be_unwrapped_if_unused) { if (call.args.len > 0) { - return Expr.joinAllWithCommaCallback(call.args.slice(), @TypeOf(p), p, comptime simpifyUnusedExpr, p.allocator); + return Expr.joinAllWithCommaCallback(call.args.slice(), @TypeOf(p), p, comptime simplifyUnusedExpr, p.allocator); } } }, @@ -1959,13 +1959,10 @@ pub const SideEffects = enum(u1) { switch (bin.op) { // These operators must not have any type conversions that can execute code // such as "toString" or "valueOf". They must also never throw any exceptions. - .bin_strict_eq, .bin_strict_ne, .bin_comma => { - return Expr.joinWithComma( - simpifyUnusedExpr(p, bin.left) orelse bin.left.toEmpty(), - simpifyUnusedExpr(p, bin.right) orelse bin.right.toEmpty(), - p.allocator, - ); - }, + .bin_strict_eq, + .bin_strict_ne, + .bin_comma, + => return simplifyUnusedBinaryCommaExpr(p, expr), // We can simplify "==" and "!=" even though they can call "toString" and/or // "valueOf" if we can statically determine that the types of both sides are @@ -1975,18 +1972,18 @@ pub const SideEffects = enum(u1) { .bin_loose_ne, => { if (isPrimitiveWithSideEffects(bin.left.data) and isPrimitiveWithSideEffects(bin.right.data)) { - return Expr.joinWithComma(simpifyUnusedExpr(p, bin.left) orelse bin.left.toEmpty(), simpifyUnusedExpr(p, bin.right) orelse bin.right.toEmpty(), p.allocator); + return Expr.joinWithComma(simplifyUnusedExpr(p, bin.left) orelse bin.left.toEmpty(), simplifyUnusedExpr(p, bin.right) orelse bin.right.toEmpty(), p.allocator); } }, .bin_logical_and, .bin_logical_or, .bin_nullish_coalescing => { - bin.right = simpifyUnusedExpr(p, bin.right) orelse bin.right.toEmpty(); + bin.right = simplifyUnusedExpr(p, bin.right) orelse bin.right.toEmpty(); // Preserve short-circuit behavior: the left expression is only unused if // the right expression can be completely removed. Otherwise, the left // expression is important for the branch. if (bin.right.isEmpty()) - return simpifyUnusedExpr(p, bin.left); + return simplifyUnusedExpr(p, bin.left); }, else => {}, @@ -2006,7 +2003,7 @@ pub const SideEffects = enum(u1) { for (properties_slice) |prop_| { var prop = prop_; if (prop_.kind != .spread) { - const value = simpifyUnusedExpr(p, prop.value.?); + const value = simplifyUnusedExpr(p, prop.value.?); if (value != null) { prop.value = value; } else if (!prop.flags.contains(.is_computed)) { @@ -2046,7 +2043,7 @@ pub const SideEffects = enum(u1) { ); } result = result.joinWithComma( - simpifyUnusedExpr(p, prop.value.?) orelse prop.value.?.toEmpty(), + simplifyUnusedExpr(p, prop.value.?) orelse prop.value.?.toEmpty(), p.allocator, ); } @@ -2078,7 +2075,7 @@ pub const SideEffects = enum(u1) { items, @TypeOf(p), p, - comptime simpifyUnusedExpr, + comptime simplifyUnusedExpr, p.allocator, ); }, @@ -2092,7 +2089,7 @@ pub const SideEffects = enum(u1) { call.args.slice(), @TypeOf(p), p, - comptime simpifyUnusedExpr, + comptime simplifyUnusedExpr, p.allocator, ); } @@ -2106,6 +2103,56 @@ pub const SideEffects = enum(u1) { return expr; } + const BinaryExpressionSimplifyVisitor = struct { + bin: *E.Binary, + }; + + /// + fn simplifyUnusedBinaryCommaExpr(p: anytype, expr: Expr) ?Expr { + if (Environment.allow_assert) { + assert(expr.data == .e_binary); + assert(switch (expr.data.e_binary.op) { + .bin_strict_eq, + .bin_strict_ne, + .bin_comma, + => true, + else => false, + }); + } + const stack: *std.ArrayList(BinaryExpressionSimplifyVisitor) = &p.binary_expression_simplify_stack; + const stack_bottom = stack.items.len; + defer stack.shrinkRetainingCapacity(stack_bottom); + + stack.append(.{ .bin = expr.data.e_binary }) catch bun.outOfMemory(); + + // Build stack up of expressions + var left: Expr = expr.data.e_binary.left; + while (left.data.as(.e_binary)) |left_bin| { + switch (left_bin.op) { + .bin_strict_eq, + .bin_strict_ne, + .bin_comma, + => { + stack.append(.{ .bin = left_bin }) catch bun.outOfMemory(); + left = left_bin.left; + }, + else => break, + } + } + + // Ride the stack downwards + var i = stack.items.len; + var result = simplifyUnusedExpr(p, left) orelse Expr.empty; + while (i > stack_bottom) { + i -= 1; + const top = stack.items[i]; + const visited_right = simplifyUnusedExpr(p, top.bin.right) orelse Expr.empty; + result = result.joinWithComma(visited_right, p.allocator); + } + + return if (result.isMissing()) Expr.empty else result; + } + fn findIdentifiers(binding: Binding, decls: *std.ArrayList(G.Decl)) void { switch (binding.data) { .b_identifier => { @@ -2560,7 +2607,7 @@ const AsyncPrefixExpression = enum(u2) { is_async, is_await, - const map = std.StaticStringMap(AsyncPrefixExpression).initComptime(.{ + const map = bun.ComptimeStringMap(AsyncPrefixExpression, .{ .{ "yield", .is_yield }, .{ "await", .is_await }, .{ "async", .is_async }, @@ -3340,10 +3387,25 @@ pub const Parser = struct { defer p.lexer.deinit(); - var binary_expression_stack_heap = std.heap.stackFallback(1024, bun.default_allocator); - p.binary_expression_stack = std.ArrayList(ParserType.BinaryExpressionVisitor).init(binary_expression_stack_heap.get()); + var binary_expression_stack_heap = std.heap.stackFallback(42 * @sizeOf(ParserType.BinaryExpressionVisitor), bun.default_allocator); + p.binary_expression_stack = std.ArrayList(ParserType.BinaryExpressionVisitor).initCapacity( + binary_expression_stack_heap.get(), + 41, // one less in case of unlikely alignment between the stack buffer and reality + ) catch unreachable; // stack allocation cannot fail defer p.binary_expression_stack.clearAndFree(); + var binary_expression_simplify_stack_heap = std.heap.stackFallback(48 * @sizeOf(SideEffects.BinaryExpressionSimplifyVisitor), bun.default_allocator); + p.binary_expression_simplify_stack = std.ArrayList(SideEffects.BinaryExpressionSimplifyVisitor).initCapacity( + binary_expression_simplify_stack_heap.get(), + 47, + ) catch unreachable; // stack allocation cannot fail + defer p.binary_expression_simplify_stack.clearAndFree(); + + if (Environment.allow_assert) { + bun.assert(binary_expression_stack_heap.fixed_buffer_allocator.ownsPtr(@ptrCast(p.binary_expression_stack.items))); + bun.assert(binary_expression_simplify_stack_heap.fixed_buffer_allocator.ownsPtr(@ptrCast(p.binary_expression_simplify_stack.items))); + } + // defer { // if (p.allocated_names_pool) |pool| { // pool.data = p.allocated_names; @@ -5218,7 +5280,9 @@ fn NewParser_( const_values: js_ast.Ast.ConstValuesMap = .{}, - binary_expression_stack: std.ArrayList(BinaryExpressionVisitor) = undefined, + // These are backed by stack fallback allocators in _parse, and are uninitialized until then. + binary_expression_stack: ListManaged(BinaryExpressionVisitor) = undefined, + binary_expression_simplify_stack: ListManaged(SideEffects.BinaryExpressionSimplifyVisitor) = undefined, /// We build up enough information about the TypeScript namespace hierarchy to /// be able to resolve scope lookups and property accesses for TypeScript enum @@ -7444,7 +7508,7 @@ fn NewParser_( // "(1, 2)" => "2" // "(sideEffects(), 2)" => "(sideEffects(), 2)" if (p.options.features.minify_syntax) { - e_.left = SideEffects.simpifyUnusedExpr(p, e_.left) orelse return e_.right; + e_.left = SideEffects.simplifyUnusedExpr(p, e_.left) orelse return e_.right; } }, .bin_loose_eq => { @@ -17327,7 +17391,7 @@ fn NewParser_( p.is_control_flow_dead = old; if (side_effects.side_effects == .could_have_side_effects) { - return Expr.joinWithComma(SideEffects.simpifyUnusedExpr(p, e_.test_) orelse p.newExpr(E.Missing{}, e_.test_.loc), e_.yes, p.allocator); + return Expr.joinWithComma(SideEffects.simplifyUnusedExpr(p, e_.test_) orelse p.newExpr(E.Missing{}, e_.test_.loc), e_.yes, p.allocator); } // "(1 ? fn : 2)()" => "fn()" @@ -17348,7 +17412,7 @@ fn NewParser_( // "(a, false) ? b : c" => "a, c" if (side_effects.side_effects == .could_have_side_effects) { - return Expr.joinWithComma(SideEffects.simpifyUnusedExpr(p, e_.test_) orelse p.newExpr(E.Missing{}, e_.test_.loc), e_.no, p.allocator); + return Expr.joinWithComma(SideEffects.simplifyUnusedExpr(p, e_.test_) orelse p.newExpr(E.Missing{}, e_.test_.loc), e_.no, p.allocator); } // "(1 ? fn : 2)()" => "fn()" @@ -18671,7 +18735,7 @@ fn NewParser_( for (props) |prop| { const key = prop.key.?.data.e_string.string(p.allocator) catch unreachable; const visited_value = p.visitExpr(prop.value.?); - const value = SideEffects.simpifyUnusedExpr(p, visited_value) orelse visited_value; + const value = SideEffects.simplifyUnusedExpr(p, visited_value) orelse visited_value; // We are doing `module.exports = { ... }` // lets rewrite it to a series of what will become export assignments @@ -19481,7 +19545,7 @@ fn NewParser_( } // simplify unused - data.value = SideEffects.simpifyUnusedExpr(p, data.value) orelse return; + data.value = SideEffects.simplifyUnusedExpr(p, data.value) orelse return; if (comptime FeatureFlags.unwrap_commonjs_to_esm) { if (is_top_level) { @@ -19693,7 +19757,7 @@ fn NewParser_( if (data.no == null or !SideEffects.shouldKeepStmtInDeadControlFlow(p, data.no.?, p.allocator)) { if (effects.side_effects == .could_have_side_effects) { // Keep the condition if it could have side effects (but is still known to be truthy) - if (SideEffects.simpifyUnusedExpr(p, data.test_)) |test_| { + if (SideEffects.simplifyUnusedExpr(p, data.test_)) |test_| { stmts.append(p.s(S.SExpr{ .value = test_ }, test_.loc)) catch unreachable; } } @@ -19707,7 +19771,7 @@ fn NewParser_( if (!SideEffects.shouldKeepStmtInDeadControlFlow(p, data.yes, p.allocator)) { if (effects.side_effects == .could_have_side_effects) { // Keep the condition if it could have side effects (but is still known to be truthy) - if (SideEffects.simpifyUnusedExpr(p, data.test_)) |test_| { + if (SideEffects.simplifyUnusedExpr(p, data.test_)) |test_| { stmts.append(p.s(S.SExpr{ .value = test_ }, test_.loc)) catch unreachable; } } diff --git a/src/open.zig b/src/open.zig index 3c867f9026..bb83d9b60d 100644 --- a/src/open.zig +++ b/src/open.zig @@ -66,7 +66,7 @@ pub const Editor = enum(u8) { const StringMap = std.EnumMap(Editor, string); const StringArrayMap = std.EnumMap(Editor, []const [:0]const u8); - const name_map = std.StaticStringMap(Editor).initComptime(.{ + const name_map = bun.ComptimeStringMap(Editor, .{ .{ "sublime", .sublime }, .{ "subl", .sublime }, .{ "vscode", .vscode }, diff --git a/src/options.zig b/src/options.zig index 67fbd950f0..9a8293b7c0 100644 --- a/src/options.zig +++ b/src/options.zig @@ -298,7 +298,7 @@ pub const ExternalModules = struct { "zlib", }; - pub const NodeBuiltinsMap = std.StaticStringMap(void).initComptime(.{ + pub const NodeBuiltinsMap = bun.ComptimeStringMap(void, .{ .{ "_http_agent", {} }, .{ "_http_client", {} }, .{ "_http_common", {} }, @@ -370,7 +370,7 @@ pub const ModuleType = enum { cjs, esm, - pub const List = std.StaticStringMap(ModuleType).initComptime(.{ + pub const List = bun.ComptimeStringMap(ModuleType, .{ .{ "commonjs", ModuleType.cjs }, .{ "module", ModuleType.esm }, }); diff --git a/src/resolver/tsconfig_json.zig b/src/resolver/tsconfig_json.zig index bc4c96927f..f5a61a0dee 100644 --- a/src/resolver/tsconfig_json.zig +++ b/src/resolver/tsconfig_json.zig @@ -67,7 +67,7 @@ pub const TSConfigJSON = struct { remove, invalid, - pub const List = std.StaticStringMap(ImportsNotUsedAsValue).initComptime(.{ + pub const List = bun.ComptimeStringMap(ImportsNotUsedAsValue, .{ .{ "preserve", .preserve }, .{ "error", .err }, .{ "remove", .remove }, diff --git a/test/bun.lockb b/test/bun.lockb index fc1f73cf79..b3cdc08715 100755 Binary files a/test/bun.lockb and b/test/bun.lockb differ diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index d30276f8ec..4baa0d9186 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -1301,6 +1301,14 @@ describe("bundler", () => { "-179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 /* g */", ], }); + // Stack overflow possibility + itBundled("edgecase/AwsCdkLib", { + files: { + "entry.js": `import * as aws from ${JSON.stringify(require.resolve("aws-cdk-lib"))}; aws;`, + }, + target: "bun", + run: true, + }); // TODO(@paperdave): test every case of this. I had already tested it manually, but it may break later const requireTranspilationListESM = [ diff --git a/test/js/bun/http/form-data-set-append.test.js b/test/js/bun/http/form-data-set-append.test.js new file mode 100644 index 0000000000..7123dd3468 --- /dev/null +++ b/test/js/bun/http/form-data-set-append.test.js @@ -0,0 +1,63 @@ +import { expect, test } from "bun:test"; + +// https://github.com/oven-sh/bun/issues/12325 + +test("formdata set with File works as expected", async () => { + const expected = ["617580375", "text-notes1.txt"]; + + using server = Bun.serve({ + port: 0, + fetch: async req => { + const data = await req.formData(); + const chat_id = data.get("chat_id"); + const document = data.get("document"); + expect(chat_id).toEqual(expected[0]); + expect(document.name).toEqual(expected[1]); + return new Response(""); + }, + }); + + async function sendDocument(body) { + const response = await fetch(server.url, { + method: "POST", + body: body, + }); + const text = await response.text(); + return text; + } + + const formDataSet = new FormData(); + formDataSet.set("chat_id", expected[0]); + formDataSet.set("document", new File(["some text notes 1"], expected[1])); + await sendDocument(formDataSet); +}); + +test("formdata apppend with File works as expected", async () => { + const expected = ["617580376", "text-notes2.txt"]; + + using server = Bun.serve({ + port: 0, + fetch: async req => { + const data = await req.formData(); + const chat_id = data.get("chat_id"); + const document = data.get("document"); + expect(chat_id).toEqual(expected[0]); + expect(document.name).toEqual(expected[1]); + return new Response(""); + }, + }); + + async function sendDocument(body) { + const response = await fetch(server.url, { + method: "POST", + body: body, + }); + const text = await response.text(); + return text; + } + + const formDataSet = new FormData(); + formDataSet.append("chat_id", expected[0]); + formDataSet.append("document", new File(["some text notes 2"], expected[1])); + await sendDocument(formDataSet); +}); diff --git a/test/js/node/timers.promises/timers.promises.test.ts b/test/js/node/timers.promises/timers.promises.test.ts new file mode 100644 index 0000000000..56e057cf9c --- /dev/null +++ b/test/js/node/timers.promises/timers.promises.test.ts @@ -0,0 +1,52 @@ +import { describe, test, it, expect } from "bun:test"; +import { setTimeout, setImmediate } from "node:timers/promises"; + +describe("setTimeout", () => { + it("abort() does not emit global error", async () => { + let unhandledRejectionCaught = false; + + const catchUnhandledRejection = () => { + unhandledRejectionCaught = true; + }; + process.on("unhandledRejection", catchUnhandledRejection); + + const c = new AbortController(); + + global.setTimeout(() => c.abort()); + + await setTimeout(100, undefined, { signal: c.signal }).catch(() => "aborted"); + + // let unhandledRejection to be fired + await setTimeout(100); + + process.off("unhandledRejection", catchUnhandledRejection); + + expect(c.signal.aborted).toBe(true); + expect(unhandledRejectionCaught).toBe(false); + }); +}); + +describe("setImmediate", () => { + it("abort() does not emit global error", async () => { + let unhandledRejectionCaught = false; + + const catchUnhandledRejection = () => { + unhandledRejectionCaught = true; + }; + process.on("unhandledRejection", catchUnhandledRejection); + + const c = new AbortController(); + + global.setImmediate(() => c.abort()); + + await setImmediate(undefined, { signal: c.signal }).catch(() => "aborted"); + + // let unhandledRejection to be fired + await setTimeout(100); + + process.off("unhandledRejection", catchUnhandledRejection); + + expect(c.signal.aborted).toBe(true); + expect(unhandledRejectionCaught).toBe(false); + }); +}); diff --git a/test/package.json b/test/package.json index 57edda9e72..3008ded2ec 100644 --- a/test/package.json +++ b/test/package.json @@ -14,6 +14,7 @@ "@resvg/resvg-js": "2.4.1", "@swc/core": "1.3.38", "@types/ws": "8.5.10", + "aws-cdk-lib": "2.148.0", "axios": "1.6.8", "body-parser": "1.20.2", "comlink": "4.4.1",